diff --git a/.github/workflows/test-all-packages.yml b/.github/workflows/test-all-packages.yml index 0c00ee23917..ec0b88ac680 100644 --- a/.github/workflows/test-all-packages.yml +++ b/.github/workflows/test-all-packages.yml @@ -264,6 +264,9 @@ jobs: - name: yarn test (casting) if: (success() || failure()) run: cd packages/casting && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT + - name: yarn test (client-utils) + if: (success() || failure()) + run: cd packages/client-utils && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT - name: yarn test (create-dapp) run: cd packages/create-dapp && yarn ${{ steps.vars.outputs.test }} | $TEST_COLLECT - name: yarn test (fast-usdc) diff --git a/packages/agoric-cli/package.json b/packages/agoric-cli/package.json index 5c7e8ddbbbc..b28ba182284 100644 --- a/packages/agoric-cli/package.json +++ b/packages/agoric-cli/package.json @@ -40,6 +40,7 @@ "@endo/errors": "^1.2.7", "@agoric/cache": "^0.3.2", "@agoric/casting": "^0.4.2", + "@agoric/client-utils": "^0.1.0", "@agoric/cosmic-proto": "^0.4.0", "@agoric/ertp": "^0.16.2", "@agoric/governance": "^0.10.3", diff --git a/packages/agoric-cli/src/commands/auction.js b/packages/agoric-cli/src/commands/auction.js index e07729a56f3..241b49ec28c 100644 --- a/packages/agoric-cli/src/commands/auction.js +++ b/packages/agoric-cli/src/commands/auction.js @@ -1,15 +1,18 @@ // @ts-check - +/* eslint-env node */ import { InvalidArgumentError } from 'commander'; import { Fail } from '@endo/errors'; -import { makeRpcUtils } from '../lib/rpc.js'; +import { makeRpcUtils } from '@agoric/client-utils'; import { outputActionAndHint } from '../lib/wallet.js'; +import { getNetworkConfig } from '../lib/network-config.js'; /** * @import {ParamTypesMap, ParamTypesMapFromRecord} from '@agoric/governance/src/contractGovernance/typedParamManager.js' * @import {ParamValueForType} from '@agoric/governance/src/types.js' */ +const networkConfig = await getNetworkConfig({ env: process.env, fetch }); + /** * @template {ParamTypesMap} M * @typedef {{ @@ -86,7 +89,10 @@ export const makeAuctionCommand = ( * }} opts */ async opts => { - const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch }); + const { agoricNames, readLatestHead } = await makeRpcUtils( + { fetch }, + networkConfig, + ); /** @type {{ current: AuctionParamRecord }} */ // @ts-expect-error XXX should runtime check? diff --git a/packages/agoric-cli/src/commands/gov.js b/packages/agoric-cli/src/commands/gov.js index 8a4b46d49e5..fd0efc504ef 100644 --- a/packages/agoric-cli/src/commands/gov.js +++ b/packages/agoric-cli/src/commands/gov.js @@ -1,10 +1,11 @@ // @ts-check /* eslint-disable func-names */ -/* global globalThis, process, setTimeout */ +/* eslint-env node */ +import { makeRpcUtils } from '@agoric/client-utils'; import { execFileSync as execFileSyncAmbient } from 'child_process'; import { Command, CommanderError } from 'commander'; import { normalizeAddressWithOptions, pollBlocks } from '../lib/chain.js'; -import { getNetworkConfig, makeRpcUtils } from '../lib/rpc.js'; +import { getNetworkConfig } from '../lib/network-config.js'; import { findContinuingIds, getCurrent, @@ -25,6 +26,8 @@ const collectValues = (val, memo) => { const defaultKeyring = process.env.AGORIC_KEYRING_BACKEND || 'test'; +const networkConfig = await getNetworkConfig({ env: process.env, fetch }); + /** * @param {import('anylogger').Logger} _logger * @param {{ @@ -40,10 +43,9 @@ export const makeGovCommand = (_logger, io = {}) => { const { // Allow caller to provide access explicitly, but // default to conventional ambient IO facilities. - env = process.env, stdout = process.stdout, stderr = process.stderr, - fetch = globalThis.fetch, + fetch = global.fetch, execFileSync = execFileSyncAmbient, delay = ms => new Promise(resolve => setTimeout(resolve, ms)), } = io; @@ -95,8 +97,7 @@ export const makeGovCommand = (_logger, io = {}) => { { toOffer, sendFrom, keyringBackend }, optUtils, ) { - const networkConfig = await getNetworkConfig(env); - const utils = await (optUtils || makeRpcUtils({ fetch })); + const utils = await (optUtils || makeRpcUtils({ fetch }, networkConfig)); const { agoricNames, readLatestHead } = utils; assert(keyringBackend, 'missing keyring-backend option'); @@ -264,7 +265,10 @@ export const makeGovCommand = (_logger, io = {}) => { ) .requiredOption('--for ', 'description of the invitation') .action(async opts => { - const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch }); + const { agoricNames, readLatestHead } = await makeRpcUtils( + { fetch }, + networkConfig, + ); const current = await getCurrent(opts.from, { readLatestHead }); const known = findContinuingIds(current, agoricNames); @@ -290,7 +294,10 @@ export const makeGovCommand = (_logger, io = {}) => { normalizeAddress, ) .action(async opts => { - const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch }); + const { agoricNames, readLatestHead } = await makeRpcUtils( + { fetch }, + networkConfig, + ); const current = await getCurrent(opts.from, { readLatestHead }); const found = findContinuingIds(current, agoricNames); @@ -326,7 +333,7 @@ export const makeGovCommand = (_logger, io = {}) => { normalizeAddress, ) .action(async function (opts, options) { - const utils = await makeRpcUtils({ fetch }); + const utils = await makeRpcUtils({ fetch }, networkConfig); const { readLatestHead } = utils; const info = await readLatestHead( diff --git a/packages/agoric-cli/src/commands/inter.js b/packages/agoric-cli/src/commands/inter.js index 702b2ae0f48..ac70eca77a9 100644 --- a/packages/agoric-cli/src/commands/inter.js +++ b/packages/agoric-cli/src/commands/inter.js @@ -5,25 +5,20 @@ // @ts-check import { CommanderError, InvalidArgumentError } from 'commander'; -// TODO: should get M from endo https://github.com/Agoric/agoric-sdk/issues/7090 +import { makeWalletUtils } from '@agoric/client-utils'; import { makeOfferSpecShape } from '@agoric/inter-protocol/src/auction/auctionBook.js'; import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; import { objectMap } from '@agoric/internal'; -import { M, matches } from '@agoric/store'; +import { M, matches } from '@endo/patterns'; import { normalizeAddressWithOptions, pollBlocks } from '../lib/chain.js'; +import { getNetworkConfig } from '../lib/network-config.js'; +import { getCurrent, outputActionAndHint, sendAction } from '../lib/wallet.js'; import { asBoardRemote, - bigintReplacer, makeAmountFormatter, + bigintReplacer, } from '../lib/format.js'; -import { getNetworkConfig } from '../lib/rpc.js'; -import { - getCurrent, - makeWalletUtils, - outputActionAndHint, - sendAction, -} from '../lib/wallet.js'; const { values } = Object; @@ -238,8 +233,8 @@ export const makeInterCommand = ( try { // XXX pass fetch to getNetworkConfig() explicitly // await null above makes this await safe - const networkConfig = await getNetworkConfig(env); - return makeWalletUtils({ fetch, execFileSync, delay }, networkConfig); + const networkConfig = await getNetworkConfig({ env, fetch }); + return makeWalletUtils({ fetch, delay }, networkConfig); } catch (err) { // CommanderError is a class constructor, and so // must be invoked with `new`. @@ -334,7 +329,7 @@ inter auction status * @param {string} from * @param {import('@agoric/smart-wallet/src/offers.js').OfferSpec} offer * @param {Awaited>} tools - * @param {boolean?} dryRun + * @param {boolean | undefined} dryRun */ const placeBid = async (from, offer, tools, dryRun = false) => { const { networkConfig, agoricNames, pollOffer } = tools; diff --git a/packages/agoric-cli/src/commands/oracle.js b/packages/agoric-cli/src/commands/oracle.js index 520de3ba710..43392ef912c 100644 --- a/packages/agoric-cli/src/commands/oracle.js +++ b/packages/agoric-cli/src/commands/oracle.js @@ -1,6 +1,11 @@ // @ts-check /* eslint-disable func-names */ /* eslint-env node */ +import { + makeRpcUtils, + makeWalletUtils, + storageHelper, +} from '@agoric/client-utils'; import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; import { oracleBrandFeedName } from '@agoric/inter-protocol/src/proposals/utils.js'; import { Fail } from '@endo/errors'; @@ -9,15 +14,14 @@ import * as cp from 'child_process'; import { Command } from 'commander'; import { inspect } from 'util'; import { normalizeAddressWithOptions } from '../lib/chain.js'; -import { bigintReplacer } from '../lib/format.js'; -import { getNetworkConfig, makeRpcUtils, storageHelper } from '../lib/rpc.js'; +import { getNetworkConfig } from '../lib/network-config.js'; import { getCurrent, - makeWalletUtils, outputAction, sendAction, sendHint, } from '../lib/wallet.js'; +import { bigintReplacer } from '../lib/format.js'; /** @import {PriceAuthority, PriceDescription, PriceQuote, PriceQuoteValue, PriceQuery,} from '@agoric/zoe/tools/types.js'; */ @@ -82,8 +86,8 @@ export const makeOracleCommand = (logger, io = {}) => { const rpcTools = async () => { // XXX pass fetch to getNetworkConfig() explicitly - const networkConfig = await getNetworkConfig(env); - const utils = await makeRpcUtils({ fetch }); + const networkConfig = await getNetworkConfig({ env: process.env, fetch }); + const utils = await makeRpcUtils({ fetch }, networkConfig); const lookupPriceAggregatorInstance = ([brandIn, brandOut]) => { const name = oracleBrandFeedName(brandIn, brandOut); @@ -267,10 +271,7 @@ export const makeOracleCommand = (logger, io = {}) => { ) => { const { readLatestHead, networkConfig, lookupPriceAggregatorInstance } = await rpcTools(); - const wutil = await makeWalletUtils( - { fetch, execFileSync, delay }, - networkConfig, - ); + const wutil = await makeWalletUtils({ fetch, delay }, networkConfig); const unitPrice = scaleDecimals(price); const feedPath = `published.priceFeed.${pair[0]}-${pair[1]}_price_feed`; diff --git a/packages/agoric-cli/src/commands/perf.js b/packages/agoric-cli/src/commands/perf.js index 199101a6fa4..ef4ee2b531e 100644 --- a/packages/agoric-cli/src/commands/perf.js +++ b/packages/agoric-cli/src/commands/perf.js @@ -7,21 +7,23 @@ import { makeFollower, makeLeaderFromRpcAddresses, } from '@agoric/casting'; +import { slotToRemotable } from '@agoric/internal/src/storage-test-utils.js'; +import { boardSlottingMarshaller } from '@agoric/vats/tools/board-utils.js'; import { Command } from 'commander'; import fs from 'fs'; import { exit } from 'process'; -import { slotToRemotable } from '@agoric/internal/src/storage-test-utils.js'; -import { boardSlottingMarshaller } from '@agoric/vats/tools/board-utils.js'; import { makeLeaderOptions } from '../lib/casting.js'; import { execSwingsetTransaction, normalizeAddressWithOptions, } from '../lib/chain.js'; -import { networkConfig } from '../lib/rpc.js'; +import { getNetworkConfig } from '../lib/network-config.js'; // tight for perf testing but less than this tends to hang. const SLEEP_SECONDS = 0.1; +const networkConfig = await getNetworkConfig({ env: process.env, fetch }); + /** * @param {import('anylogger').Logger} logger */ diff --git a/packages/agoric-cli/src/commands/psm.js b/packages/agoric-cli/src/commands/psm.js index 8b2af277d58..1d00ec839d0 100644 --- a/packages/agoric-cli/src/commands/psm.js +++ b/packages/agoric-cli/src/commands/psm.js @@ -1,11 +1,14 @@ // @ts-check /* eslint-disable func-names */ /* eslint-env node */ -import { Command } from 'commander'; +import { makeRpcUtils, storageHelper } from '@agoric/client-utils'; import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; -import { asPercent } from '../lib/format.js'; -import { makeRpcUtils, storageHelper } from '../lib/rpc.js'; +import { Command } from 'commander'; +import { getNetworkConfig } from '../lib/network-config.js'; import { outputExecuteOfferAction } from '../lib/wallet.js'; +import { asPercent } from '../lib/format.js'; + +const networkConfig = await getNetworkConfig({ env: process.env, fetch }); // Adapted from https://gist.github.com/dckc/8b5b2f16395cb4d7f2ff340e0bc6b610#file-psm-tool @@ -60,7 +63,7 @@ export const makePsmCommand = logger => { ); const rpcTools = async () => { - const utils = await makeRpcUtils({ fetch }); + const utils = await makeRpcUtils({ fetch }, networkConfig); const lookupPsmInstance = ([minted, anchor]) => { const name = `psm-${minted}-${anchor}`; diff --git a/packages/agoric-cli/src/commands/reserve.js b/packages/agoric-cli/src/commands/reserve.js index 4e55b921761..cb510283952 100644 --- a/packages/agoric-cli/src/commands/reserve.js +++ b/packages/agoric-cli/src/commands/reserve.js @@ -1,11 +1,14 @@ // @ts-check /* eslint-disable func-names */ /* eslint-env node */ +import { makeRpcUtils } from '@agoric/client-utils'; import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; import { Command } from 'commander'; -import { makeRpcUtils } from '../lib/rpc.js'; +import { getNetworkConfig } from '../lib/network-config.js'; import { outputActionAndHint } from '../lib/wallet.js'; +const networkConfig = await getNetworkConfig({ env: process.env, fetch }); + /** * @param {import('anylogger').Logger} _logger * @param {*} io @@ -29,7 +32,7 @@ export const makeReserveCommand = (_logger, io = {}) => { * }} opts */ async ({ collateralBrand, ...opts }) => { - const { agoricNames } = await makeRpcUtils({ fetch }); + const { agoricNames } = await makeRpcUtils({ fetch }, networkConfig); const offer = Offers.reserve.AddCollateral(agoricNames, { collateralBrandKey: collateralBrand, @@ -63,7 +66,7 @@ export const makeReserveCommand = (_logger, io = {}) => { 1, ) .action(async function (opts) { - const { agoricNames } = await makeRpcUtils({ fetch }); + const { agoricNames } = await makeRpcUtils({ fetch }, networkConfig); const reserveInstance = agoricNames.instance.reserve; assert(reserveInstance, 'missing reserve in names'); diff --git a/packages/agoric-cli/src/commands/test-upgrade.js b/packages/agoric-cli/src/commands/test-upgrade.js index d9d7c977bd6..b9dd5d6e76d 100644 --- a/packages/agoric-cli/src/commands/test-upgrade.js +++ b/packages/agoric-cli/src/commands/test-upgrade.js @@ -1,11 +1,12 @@ // @ts-check /* eslint-env node */ +import { makeWalletUtils } from '@agoric/client-utils'; import { Fail } from '@endo/errors'; import { CommanderError } from 'commander'; import { normalizeAddressWithOptions } from '../lib/chain.js'; +import { getNetworkConfig } from '../lib/network-config.js'; +import { sendAction } from '../lib/wallet.js'; import { bigintReplacer } from '../lib/format.js'; -import { getNetworkConfig } from '../lib/rpc.js'; -import { makeWalletUtils, sendAction } from '../lib/wallet.js'; /** * Make commands for testing. @@ -38,8 +39,8 @@ export const makeTestCommand = ( try { // XXX pass fetch to getNetworkConfig() explicitly // await null above makes this await safe - const networkConfig = await getNetworkConfig(env); - return makeWalletUtils({ fetch, execFileSync, delay }, networkConfig); + const networkConfig = await getNetworkConfig({ env, fetch }); + return makeWalletUtils({ fetch, delay }, networkConfig); } catch (err) { // CommanderError is a class constructor, and so // must be invoked with `new`. diff --git a/packages/agoric-cli/src/commands/vaults.js b/packages/agoric-cli/src/commands/vaults.js index 523b6170069..bc076b0f9bb 100644 --- a/packages/agoric-cli/src/commands/vaults.js +++ b/packages/agoric-cli/src/commands/vaults.js @@ -1,14 +1,17 @@ // @ts-check /* eslint-disable func-names */ /* eslint-env node */ -import { Command } from 'commander'; +import { makeRpcUtils } from '@agoric/client-utils'; import { lookupOfferIdForVault, Offers, } from '@agoric/inter-protocol/src/clientSupport.js'; +import { Command } from 'commander'; import { normalizeAddressWithOptions } from '../lib/chain.js'; -import { makeRpcUtils } from '../lib/rpc.js'; import { getCurrent, outputExecuteOfferAction } from '../lib/wallet.js'; +import { getNetworkConfig } from '../lib/network-config.js'; + +const networkConfig = await getNetworkConfig({ env: process.env, fetch }); /** * @param {import('anylogger').Logger} logger @@ -36,7 +39,7 @@ export const makeVaultsCommand = logger => { normalizeAddress, ) .action(async function (opts) { - const { readLatestHead } = await makeRpcUtils({ fetch }); + const { readLatestHead } = await makeRpcUtils({ fetch }, networkConfig); const current = await getCurrent(opts.from, { readLatestHead, @@ -61,7 +64,7 @@ export const makeVaultsCommand = logger => { .option('--collateralBrand ', 'Collateral brand key', 'ATOM') .action(async function (opts) { logger.warn('running with options', opts); - const { agoricNames } = await makeRpcUtils({ fetch }); + const { agoricNames } = await makeRpcUtils({ fetch }, networkConfig); const offer = Offers.vaults.OpenVault(agoricNames, { giveCollateral: opts.giveCollateral, @@ -96,7 +99,10 @@ export const makeVaultsCommand = logger => { .requiredOption('--vaultId ', 'Key of vault (e.g. vault1)') .action(async function (opts) { logger.warn('running with options', opts); - const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch }); + const { agoricNames, readLatestHead } = await makeRpcUtils( + { fetch }, + networkConfig, + ); const previousOfferId = await lookupOfferIdForVault( opts.vaultId, @@ -137,7 +143,10 @@ export const makeVaultsCommand = logger => { ) .action(async function (opts) { logger.warn('running with options', opts); - const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch }); + const { agoricNames, readLatestHead } = await makeRpcUtils( + { fetch }, + networkConfig, + ); const previousOfferId = await lookupOfferIdForVault( opts.vaultId, diff --git a/packages/agoric-cli/src/commands/wallet.js b/packages/agoric-cli/src/commands/wallet.js index 961b480ee3b..a40cd1d1744 100644 --- a/packages/agoric-cli/src/commands/wallet.js +++ b/packages/agoric-cli/src/commands/wallet.js @@ -8,11 +8,10 @@ import { makeLeader, makeLeaderFromRpcAddresses, } from '@agoric/casting'; +import { makeRpcUtils } from '@agoric/client-utils'; +import { execFileSync } from 'child_process'; import fs from 'fs'; import util from 'util'; -import { execFileSync } from 'child_process'; -import { fmtRecordOfLines, summarize } from '../lib/format.js'; -import { makeRpcUtils, networkConfig } from '../lib/rpc.js'; import { makeLeaderOptions } from '../lib/casting.js'; import { @@ -20,7 +19,11 @@ import { fetchSwingsetParams, normalizeAddressWithOptions, } from '../lib/chain.js'; +import { getNetworkConfig } from '../lib/network-config.js'; import { coalesceWalletState, getCurrent } from '../lib/wallet.js'; +import { summarize, fmtRecordOfLines } from '../lib/format.js'; + +const networkConfig = await getNetworkConfig({ env: process.env, fetch }); const SLEEP_SECONDS = 3; @@ -102,7 +105,7 @@ export const makeWalletCommand = async command => { .action(async function (opts) { const offerStr = fs.readFileSync(opts.file).toString(); - const { unserializer } = await makeRpcUtils({ fetch }); + const { unserializer } = await makeRpcUtils({ fetch }, networkConfig); const offerObj = unserializer.fromCapData(JSON.parse(offerStr)); console.log(offerObj); @@ -117,7 +120,7 @@ export const makeWalletCommand = async command => { .action(async function (opts) { const offerStr = fs.readFileSync(opts.offer).toString(); - const { unserializer } = await makeRpcUtils({ fetch }); + const { unserializer } = await makeRpcUtils({ fetch }, networkConfig); const offerObj = unserializer.fromCapData(JSON.parse(offerStr)); console.log(offerObj.offer.id); @@ -155,7 +158,7 @@ export const makeWalletCommand = async command => { .command('list') .description('list all wallets in vstorage') .action(async function () { - const { vstorage } = await makeRpcUtils({ fetch }); + const { vstorage } = await makeRpcUtils({ fetch }, networkConfig); const wallets = await vstorage.keys('published.wallet'); process.stdout.write(wallets.join('\n')); }); @@ -169,16 +172,18 @@ export const makeWalletCommand = async command => { normalizeAddress, ) .action(async function (opts) { - const { agoricNames, unserializer, readLatestHead } = await makeRpcUtils({ - fetch, - }); + const { agoricNames, unserializer, readLatestHead } = await makeRpcUtils( + { + fetch, + }, + networkConfig, + ); const leader = makeLeader(networkConfig.rpcAddrs[0]); const follower = await makeFollower( `:published.wallet.${opts.from}`, leader, { - // @ts-expect-error xxx unserializer, }, ); diff --git a/packages/agoric-cli/src/lib/chain.js b/packages/agoric-cli/src/lib/chain.js index 282650c31bf..ac8ecd8b0af 100644 --- a/packages/agoric-cli/src/lib/chain.js +++ b/packages/agoric-cli/src/lib/chain.js @@ -3,6 +3,10 @@ import { normalizeBech32 } from '@cosmjs/encoding'; import { execFileSync as execFileSyncAmbient } from 'child_process'; +/** + * @import {MinimalNetworkConfig} from '@agoric/client-utils'; + */ + const agdBinary = 'agd'; /** @@ -36,7 +40,7 @@ harden(normalizeAddressWithOptions); /** * @param {ReadonlyArray} swingsetArgs - * @param {import('./rpc.js').MinimalNetworkConfig & { + * @param {MinimalNetworkConfig & { * from: string, * fees?: string, * dryRun?: boolean, @@ -94,7 +98,7 @@ harden(execSwingsetTransaction); /** * - * @param {import('./rpc.js').MinimalNetworkConfig} net + * @param {MinimalNetworkConfig} net */ // TODO fetch by HTTP instead of shelling out https://github.com/Agoric/agoric-sdk/issues/9200 export const fetchSwingsetParams = net => { @@ -114,7 +118,7 @@ export const fetchSwingsetParams = net => { harden(fetchSwingsetParams); /** - * @param {import('./rpc.js').MinimalNetworkConfig & { + * @param {MinimalNetworkConfig & { * execFileSync: typeof import('child_process').execFileSync, * delay: (ms: number) => Promise, * period?: number, @@ -154,7 +158,7 @@ export const pollBlocks = opts => async lookup => { /** * @param {string} txhash - * @param {import('./rpc.js').MinimalNetworkConfig & { + * @param {MinimalNetworkConfig & { * execFileSync: typeof import('child_process').execFileSync, * delay: (ms: number) => Promise, * period?: number, diff --git a/packages/agoric-cli/src/lib/format.js b/packages/agoric-cli/src/lib/format.js index 78227bcd615..16ebe45308b 100644 --- a/packages/agoric-cli/src/lib/format.js +++ b/packages/agoric-cli/src/lib/format.js @@ -1,24 +1,10 @@ -// @ts-check - import { Fail, q } from '@endo/errors'; import { makeBoardRemote } from '@agoric/vats/tools/board-utils.js'; -/** @import {BoardRemote} from '@agoric/vats/tools/board-utils.js' */ -/** @import {VBankAssetDetail} from '@agoric/vats/tools/board-utils.js'; */ - /** - * Like @endo/nat but coerces - * - * @param {string} str - * @returns {bigint} + * @import {Amount, Brand} from '@agoric/ertp' + * @import {AgoricNamesRemotes, BoardRemote, VBankAssetDetail} from '@agoric/vats/tools/board-utils.js'; */ -export const Natural = str => { - const b = BigInt(str); - if (b < 0) { - throw RangeError(`${b} is negative`); - } - return b; -}; /** * JSON.stringify replacer to handle bigint @@ -117,7 +103,7 @@ export const fmtRecordOfLines = record => { * Summarize the offerStatuses of the state as user-facing informative tuples * * @param {import('@agoric/smart-wallet/src/utils.js').CoalescedWalletState} state - * @param {import('@agoric/vats/tools/board-utils.js').AgoricNamesRemotes} agoricNames + * @param {AgoricNamesRemotes} agoricNames */ export const offerStatusTuples = (state, agoricNames) => { const { offerStatuses } = state; @@ -174,7 +160,7 @@ export const offerStatusTuples = (state, agoricNames) => { /** * @param {import('@agoric/smart-wallet/src/smartWallet.js').CurrentWalletRecord} current * @param {ReturnType['state']} coalesced - * @param {import('@agoric/vats/tools/board-utils.js').AgoricNamesRemotes} agoricNames + * @param {AgoricNamesRemotes} agoricNames */ export const summarize = (current, coalesced, agoricNames) => { return { diff --git a/packages/agoric-cli/src/lib/network-config.js b/packages/agoric-cli/src/lib/network-config.js new file mode 100644 index 00000000000..ca0485383f8 --- /dev/null +++ b/packages/agoric-cli/src/lib/network-config.js @@ -0,0 +1,37 @@ +import { NonNullish } from '@agoric/internal'; + +export const networkConfigUrl = agoricNetSubdomain => + `https://${agoricNetSubdomain}.agoric.net/network-config`; +export const rpcUrl = agoricNetSubdomain => + `https://${agoricNetSubdomain}.rpc.agoric.net:443`; + +/** + * @param {string} str + * @param {{ fetch: typeof fetch }} io + * @returns {Promise} + */ +const fromAgoricNet = (str, { fetch }) => { + const [netName, chainName] = str.split(','); + if (chainName) { + return Promise.resolve({ chainName, rpcAddrs: [rpcUrl(netName)] }); + } + return fetch(networkConfigUrl(netName)).then(res => res.json()); +}; + +/** + * @param {{ env: typeof process.env, fetch: typeof fetch }} io + * @returns {Promise} + */ +export const getNetworkConfig = async ({ env, fetch }) => { + if (!('AGORIC_NET' in env) || env.AGORIC_NET === 'local') { + return { rpcAddrs: ['http://0.0.0.0:26657'], chainName: 'agoriclocal' }; + } + + return fromAgoricNet(NonNullish(env.AGORIC_NET), { fetch }).catch(err => { + throw Error( + `cannot get network config (${env.AGORIC_NET || 'local'}): ${ + err.message + }`, + ); + }); +}; diff --git a/packages/agoric-cli/src/lib/wallet.js b/packages/agoric-cli/src/lib/wallet.js index abb2f417b33..63ac4597373 100644 --- a/packages/agoric-cli/src/lib/wallet.js +++ b/packages/agoric-cli/src/lib/wallet.js @@ -1,14 +1,17 @@ // @ts-check /* eslint-env node */ -import { Fail } from '@endo/errors'; import { iterateReverse } from '@agoric/casting'; +import { boardSlottingMarshaller } from '@agoric/client-utils'; import { makeWalletStateCoalescer } from '@agoric/smart-wallet/src/utils.js'; -import { execSwingsetTransaction, pollBlocks, pollTx } from './chain.js'; -import { boardSlottingMarshaller, makeRpcUtils } from './rpc.js'; +import { Fail } from '@endo/errors'; +import { execSwingsetTransaction, pollTx } from './chain.js'; -/** @import {CurrentWalletRecord} from '@agoric/smart-wallet/src/smartWallet.js' */ -/** @import {AgoricNamesRemotes} from '@agoric/vats/tools/board-utils.js' */ +/** + * @import {CurrentWalletRecord} from '@agoric/smart-wallet/src/smartWallet.js'; + * @import {AgoricNamesRemotes} from '@agoric/vats/tools/board-utils.js'; + * @import {MinimalNetworkConfig, RpcUtils} from '@agoric/client-utils'; + */ const marshaller = boardSlottingMarshaller(); @@ -22,7 +25,7 @@ const emptyCurrentRecord = { /** * @param {string} addr - * @param {Pick} io + * @param {Pick} io * @returns {Promise} */ export const getCurrent = async (addr, { readLatestHead }) => { @@ -57,7 +60,7 @@ export const getCurrent = async (addr, { readLatestHead }) => { /** * @param {string} addr - * @param {Pick} io + * @param {Pick} io * @returns {Promise} */ export const getLastUpdate = (addr, { readLatestHead }) => { @@ -142,7 +145,7 @@ export const coalesceWalletState = async (follower, invitationBrand) => { * * @throws { Error & { code: number } } if transaction fails * @param {import('@agoric/smart-wallet/src/smartWallet.js').BridgeAction} bridgeAction - * @param {import('./rpc.js').MinimalNetworkConfig & { + * @param {MinimalNetworkConfig & { * from: string, * fees?: string, * verbose?: boolean, @@ -211,76 +214,3 @@ export const findContinuingIds = (current, agoricNames) => { } return found; }; - -export const makeWalletUtils = async ( - { fetch, execFileSync, delay }, - networkConfig, -) => { - const { agoricNames, fromBoard, readLatestHead, vstorage } = - await makeRpcUtils({ fetch }, networkConfig); - /** - * @param {string} from - * @param {number|string} [minHeight] - */ - const storedWalletState = async (from, minHeight = undefined) => { - const m = boardSlottingMarshaller(fromBoard.convertSlotToVal); - - const history = await vstorage.readFully( - `published.wallet.${from}`, - minHeight, - ); - - /** @type {{ Invitation: Brand<'set'> }} */ - // @ts-expect-error XXX how to narrow AssetKind to set? - const { Invitation } = agoricNames.brand; - const coalescer = makeWalletStateCoalescer(Invitation); - // update with oldest first - for (const txt of history.reverse()) { - const { body, slots } = JSON.parse(txt); - const record = m.fromCapData({ body, slots }); - coalescer.update(record); - } - const coalesced = coalescer.state; - harden(coalesced); - return coalesced; - }; - - /** - * Get OfferStatus by id, polling until available. - * - * @param {string} from - * @param {string|number} id - * @param {number|string} minHeight - * @param {boolean} [untilNumWantsSatisfied] - */ - const pollOffer = async ( - from, - id, - minHeight, - untilNumWantsSatisfied = false, - ) => { - const lookup = async () => { - const { offerStatuses } = await storedWalletState(from, minHeight); - const offerStatus = [...offerStatuses.values()].find(s => s.id === id); - if (!offerStatus) throw Error('retry'); - harden(offerStatus); - if (untilNumWantsSatisfied && !('numWantsSatisfied' in offerStatus)) { - throw Error('retry (no numWantsSatisfied yet)'); - } - return offerStatus; - }; - const retryMessage = 'offer not in wallet at block'; - const opts = { ...networkConfig, execFileSync, delay, retryMessage }; - return pollBlocks(opts)(lookup); - }; - - return { - networkConfig, - agoricNames, - fromBoard, - vstorage, - readLatestHead, - storedWalletState, - pollOffer, - }; -}; diff --git a/packages/agoric-cli/src/sdk-package-names.js b/packages/agoric-cli/src/sdk-package-names.js index 483c22208ad..70e5eec1242 100644 --- a/packages/agoric-cli/src/sdk-package-names.js +++ b/packages/agoric-cli/src/sdk-package-names.js @@ -9,6 +9,7 @@ export default [ "@agoric/builders", "@agoric/cache", "@agoric/casting", + "@agoric/client-utils", "@agoric/cosmic-proto", "@agoric/cosmic-swingset", "@agoric/cosmos", diff --git a/packages/agoric-cli/test/inter-cli.test.js b/packages/agoric-cli/test/inter-cli.test.js index 2a8023c15f0..ab3d6c92288 100644 --- a/packages/agoric-cli/test/inter-cli.test.js +++ b/packages/agoric-cli/test/inter-cli.test.js @@ -2,11 +2,11 @@ /* eslint-env node */ import '@endo/init'; import test from 'ava'; -import { createCommand, CommanderError } from 'commander'; +import { CommanderError, createCommand } from 'commander'; -import { Far } from '@endo/far'; +import { boardSlottingMarshaller, makeFromBoard } from '@agoric/client-utils'; import { makeParseAmount } from '@agoric/inter-protocol/src/clientSupport.js'; -import { boardSlottingMarshaller, makeFromBoard } from '../src/lib/rpc.js'; +import { Far } from '@endo/far'; import { fmtBid, makeInterCommand } from '../src/commands/inter.js'; diff --git a/packages/casting/src/main.js b/packages/casting/src/main.js index 6b0eee6ec01..f8fa0cb2f37 100644 --- a/packages/casting/src/main.js +++ b/packages/casting/src/main.js @@ -11,3 +11,4 @@ export * from './follower-cosmjs.js'; export * from './casting-spec.js'; export * from './leader.js'; export * from './iterable.js'; +export * from './makeHttpClient.js'; diff --git a/packages/casting/src/makeHttpClient.js b/packages/casting/src/makeHttpClient.js index e6fe7c1ec7e..a8fbd77b38b 100644 --- a/packages/casting/src/makeHttpClient.js +++ b/packages/casting/src/makeHttpClient.js @@ -25,7 +25,7 @@ const filterBadStatus = res => { * @param {typeof window.fetch} fetch * @returns {import('@cosmjs/tendermint-rpc').RpcClient} */ -export const makeHttpClient = (url, fetch) => { +export const makeTendermintRpcClient = (url, fetch) => { const headers = {}; // XXX needed? // based on cosmjs 0.30.1: @@ -54,3 +54,6 @@ export const makeHttpClient = (url, fetch) => { }, }); }; + +/** @deprecated use makeTendermintRpcClient */ +export const makeHttpClient = makeTendermintRpcClient; diff --git a/packages/casting/test/interpose-net-access.test.js b/packages/casting/test/interpose-net-access.test.js index f0a0110d32d..87913ff3461 100644 --- a/packages/casting/test/interpose-net-access.test.js +++ b/packages/casting/test/interpose-net-access.test.js @@ -12,7 +12,7 @@ import { QueryChildrenResponse, } from '@agoric/cosmic-proto/vstorage/query.js'; -import { makeHttpClient } from '../src/makeHttpClient.js'; +import { makeTendermintRpcClient } from '../src/makeHttpClient.js'; import { captureIO, replayIO, web1, web2 } from './net-access-fixture.js'; /** @type {import('ava').TestFn>>} */ @@ -43,7 +43,7 @@ const scenario1 = { test('interpose net access', async t => { const fetchMock = replayIO(web1); - const rpcClient = makeHttpClient(scenario1.endpoint, fetchMock); + const rpcClient = makeTendermintRpcClient(scenario1.endpoint, fetchMock); t.log('raw JSON RPC'); const res = await rpcClient.execute({ @@ -100,7 +100,7 @@ test(`vstorage query: Children (RECORDING: ${RECORDING})`, async t => { const { fetch: fetchMock, web } = io.recording ? captureIO(io.fetch) : { fetch: replayIO(web2), web: new Map() }; - const rpcClient = makeHttpClient(scenario2.endpoint, fetchMock); + const rpcClient = makeTendermintRpcClient(scenario2.endpoint, fetchMock); const tmClient = await Tendermint34Client.create(rpcClient); const qClient = new QueryClient(tmClient); diff --git a/packages/client-utils/CHANGELOG.md b/packages/client-utils/CHANGELOG.md new file mode 100644 index 00000000000..420e6f23d0e --- /dev/null +++ b/packages/client-utils/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log diff --git a/packages/client-utils/README.md b/packages/client-utils/README.md new file mode 100644 index 00000000000..8311a92d024 --- /dev/null +++ b/packages/client-utils/README.md @@ -0,0 +1,37 @@ +# Client Utils + +Utilities for building clients of an Agoric chain + +## Overview + +The Agoric chain takes mutations through signed messages and reveals state updates through vstorage. This package abstracts the calls to RPC nodes into a [CQRS](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation) interface. The commands are made mostly through an on-chain Smart Wallet and the queries through vstorage. + +## Design + +This package will be used in several kinds of clients: +- CLI (such as the `agoric` command) +- GUI (such as dapps) +- Tests (such as a3p-integration tests) + +As such the modules cannot assume they're running in Node. There are some ambient authorities in common in the above environments (e.g. `setTimeout`) but a further constraint is that these modules will not export ambient authority. Instead they will provide interfaces that are ergonomic for creating empowered objects in the client context. + +## Related packages + +### cli +`agoric` package has a command line UI (CLI) for working with an Agoric chain. It's in this repository at `packages/agoric-cli`. + +### rpc +`@agoric/rpc` is a small library that currently just has utilities for watching vstorage. This package avoids depending on `@agoric/rpc` for now because it: + - is in a separate repository ([ui-kit](https://github.com/Agoric/ui-kit/blob/main/packages/rpc)) so not part of agoric-sdk CI + - depends on `axios` and `vite` which are unnecessary constraints + +Some of the functionality in this package could make sense in that package, but for now it will be ignored. + +### cosmic-proto + +`@agoric/cosmic-proto` is a package that contains the protobuf stubs for the Agoric cosmos-sdk module. At various points it has also held generated RPC clients. Because that package is imported into contracts we've kept those out. They may end up in `@agoric/rpc` eventually. + +### * / clientSupport + +The `clientSupport.js` module of several packages. Some packages export this module with certain helpers that this package may abstract. + diff --git a/packages/client-utils/package.json b/packages/client-utils/package.json new file mode 100644 index 00000000000..65c393fe954 --- /dev/null +++ b/packages/client-utils/package.json @@ -0,0 +1,60 @@ +{ + "name": "@agoric/client-utils", + "version": "0.1.0", + "description": "Utilities for building Agoric clients", + "license": "Apache-2.0", + "type": "module", + "main": "src/main.js", + "files": [ + "src" + ], + "scripts": { + "build": "exit 0", + "prepack": "tsc --build tsconfig.build.json", + "postpack": "git clean -f '*.d.ts*' '*.tsbuildinfo'", + "test": "ava", + "test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js", + "test:xs": "exit 0", + "lint-fix": "yarn lint:eslint --fix", + "lint": "run-s --continue-on-error lint:*", + "lint:types": "tsc", + "lint:eslint": "eslint ." + }, + "devDependencies": { + "ava": "^5.3.0", + "c8": "^9.1.0", + "ts-blank-space": "^0.4.1" + }, + "dependencies": { + "@agoric/casting": "^0.4.2", + "@agoric/ertp": "^0.16.2", + "@agoric/internal": "^0.3.2", + "@agoric/smart-wallet": "^0.5.3", + "@agoric/vats": "^0.15.1", + "@cosmjs/stargate": "^0.32.3", + "@cosmjs/tendermint-rpc": "^0.32.3", + "@endo/common": "^1.2.7", + "@endo/errors": "^1.2.7", + "@endo/marshal": "^1.6.1", + "@endo/pass-style": "^1.4.6", + "@endo/patterns": "^1.4.6", + "@endo/promise-kit": "^1.1.7" + }, + "ava": { + "extensions": { + "js": true, + "ts": "module" + }, + "files": [ + "test/**/*.test.*" + ], + "nodeArguments": [ + "--import=ts-blank-space/register", + "--no-warnings" + ], + "require": [ + "@endo/init/legacy.js" + ], + "timeout": "20m" + } +} diff --git a/packages/client-utils/src/chain.js b/packages/client-utils/src/chain.js new file mode 100644 index 00000000000..b42314a63f3 --- /dev/null +++ b/packages/client-utils/src/chain.js @@ -0,0 +1,53 @@ +/** + * @import {MinimalNetworkConfig} from '@agoric/client-utils'; + */ + +import { StargateClient } from '@cosmjs/stargate'; +import { makeTendermint34Client, pickEndpoint } from './rpc.js'; + +/** + * @param {MinimalNetworkConfig} config + * @param {{ fetch: typeof window.fetch }} io + * @returns {Promise} + */ +export const makeStargateClient = async (config, { fetch }) => { + const url = pickEndpoint(config); + const tm = await makeTendermint34Client(url, { fetch }); + return StargateClient.create(tm); +}; + +/** + * @param {{ + * client: StargateClient, + * delay: (ms: number) => Promise, + * period?: number, + * retryMessage?: string, + * }} opts + * @returns {(l: (b: { time: string, height: number }) => Promise) => Promise} + */ +export const pollBlocks = opts => async lookup => { + const { client, delay, period = 3 * 1000 } = opts; + const { retryMessage } = opts; + + await null; // separate sync prologue + + for (;;) { + const status = await client.getBlock(); + const { + header: { time, height }, + } = status; + try { + // see await null above + const result = await lookup({ time, height }); + return result; + } catch (_err) { + console.error( + time, + retryMessage || 'not in block', + height, + 'retrying...', + ); + await delay(period); + } + } +}; diff --git a/packages/client-utils/src/main.js b/packages/client-utils/src/main.js new file mode 100644 index 00000000000..d22b4429d57 --- /dev/null +++ b/packages/client-utils/src/main.js @@ -0,0 +1,2 @@ +export * from './rpc.js'; +export * from './wallet-utils.js'; diff --git a/packages/agoric-cli/src/lib/rpc.js b/packages/client-utils/src/rpc.js similarity index 83% rename from packages/agoric-cli/src/lib/rpc.js rename to packages/client-utils/src/rpc.js index e633aabd93b..64df53ecee6 100644 --- a/packages/agoric-cli/src/lib/rpc.js +++ b/packages/client-utils/src/rpc.js @@ -1,64 +1,26 @@ -// @ts-check -/* eslint-env node */ - -import { NonNullish } from '@agoric/internal'; +/* global Buffer */ import { boardSlottingMarshaller, makeBoardRemote, } from '@agoric/vats/tools/board-utils.js'; +import { makeTendermintRpcClient } from '@agoric/casting'; +import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; export { boardSlottingMarshaller }; -export const networkConfigUrl = agoricNetSubdomain => - `https://${agoricNetSubdomain}.agoric.net/network-config`; -export const rpcUrl = agoricNetSubdomain => - `https://${agoricNetSubdomain}.rpc.agoric.net:443`; - /** * @typedef {{ rpcAddrs: string[], chainName: string }} MinimalNetworkConfig */ -/** - * @param {string} str - * @returns {Promise} - */ -const fromAgoricNet = str => { - const [netName, chainName] = str.split(','); - if (chainName) { - return Promise.resolve({ chainName, rpcAddrs: [rpcUrl(netName)] }); - } - return fetch(networkConfigUrl(netName)).then(res => res.json()); -}; - -/** - * @param {typeof process.env} env - * @returns {Promise} - */ -export const getNetworkConfig = async env => { - if (!('AGORIC_NET' in env) || env.AGORIC_NET === 'local') { - return { rpcAddrs: ['http://0.0.0.0:26657'], chainName: 'agoriclocal' }; - } - - return fromAgoricNet(NonNullish(env.AGORIC_NET)).catch(err => { - throw Error( - `cannot get network config (${env.AGORIC_NET || 'local'}): ${ - err.message - }`, - ); - }); -}; - -/** @type {MinimalNetworkConfig} */ -const networkConfig = await getNetworkConfig(process.env); -export { networkConfig }; -// console.warn('networkConfig', networkConfig); +// TODO distribute load +export const pickEndpoint = ({ rpcAddrs }) => rpcAddrs[0]; /** * @param {object} powers * @param {typeof window.fetch} powers.fetch * @param {MinimalNetworkConfig} config */ -export const makeVStorage = (powers, config = networkConfig) => { +export const makeVStorage = (powers, config) => { /** @param {string} path */ const getJSON = path => { const url = config.rpcAddrs[0] + path; @@ -172,6 +134,7 @@ export const makeVStorage = (powers, config = networkConfig) => { }; /** @typedef {ReturnType} VStorage */ +/** @deprecated */ export const makeFromBoard = () => { const cache = new Map(); const convertSlotToVal = (boardId, iface) => { @@ -186,6 +149,7 @@ export const makeFromBoard = () => { }; /** @typedef {ReturnType} IdMap */ +/** @deprecated */ export const storageHelper = { /** @param { string } txt */ parseCapData: txt => { @@ -225,6 +189,7 @@ export const storageHelper = { harden(storageHelper); /** + * @deprecated * @param {IdMap} ctx * @param {VStorage} vstorage * @returns {Promise} @@ -253,14 +218,14 @@ export const makeAgoricNames = async (ctx, vstorage) => { * @param {{ fetch: typeof window.fetch }} io * @param {MinimalNetworkConfig} config */ -export const makeRpcUtils = async ({ fetch }, config = networkConfig) => { +export const makeRpcUtils = async ({ fetch }, config) => { await null; try { const vstorage = makeVStorage({ fetch }, config); const fromBoard = makeFromBoard(); const agoricNames = await makeAgoricNames(fromBoard, vstorage); - const unserializer = boardSlottingMarshaller(fromBoard.convertSlotToVal); + const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); /** @type {(txt: string) => unknown} */ const unserializeHead = txt => @@ -273,9 +238,9 @@ export const makeRpcUtils = async ({ fetch }, config = networkConfig) => { return { agoricNames, fromBoard, + marshaller, readLatestHead, unserializeHead, - unserializer, vstorage, }; } catch (err) { @@ -283,3 +248,13 @@ export const makeRpcUtils = async ({ fetch }, config = networkConfig) => { } }; /** @typedef {Awaited>} RpcUtils */ + +/** + * @param {string} endpoint + * @param {{ fetch: typeof window.fetch }} io + * @returns {Promise} + */ +export const makeTendermint34Client = (endpoint, { fetch }) => { + const rpcClient = makeTendermintRpcClient(endpoint, fetch); + return Tendermint34Client.create(rpcClient); +}; diff --git a/packages/client-utils/src/wallet-utils.js b/packages/client-utils/src/wallet-utils.js new file mode 100644 index 00000000000..151291fbc54 --- /dev/null +++ b/packages/client-utils/src/wallet-utils.js @@ -0,0 +1,103 @@ +import { makeWalletStateCoalescer } from '@agoric/smart-wallet/src/utils.js'; +import { makeStargateClient, pollBlocks } from './chain.js'; +import { boardSlottingMarshaller, makeRpcUtils } from './rpc.js'; + +/** + * @import {Amount, Brand} from '@agoric/ertp/src/types.js' + * @import {MinimalNetworkConfig} from './rpc.js'; + */ + +/** + * + * @param {object} root0 + * @param {typeof globalThis.fetch} root0.fetch + * @param {(ms: number) => Promise} root0.delay + * @param {MinimalNetworkConfig} networkConfig + */ +export const makeWalletUtils = async ({ fetch, delay }, networkConfig) => { + const { agoricNames, fromBoard, marshaller, readLatestHead, vstorage } = + await makeRpcUtils({ fetch }, networkConfig); + const m = boardSlottingMarshaller(fromBoard.convertSlotToVal); + + const client = await makeStargateClient(networkConfig, { fetch }); + + /** + * @param {string} from + * @param {number|string} [minHeight] + */ + const storedWalletState = async (from, minHeight = undefined) => { + const history = await vstorage.readFully( + `published.wallet.${from}`, + minHeight, + ); + + /** @type {{ Invitation: Brand<'set'> }} */ + // @ts-expect-error XXX how to narrow AssetKind to set? + const { Invitation } = agoricNames.brand; + const coalescer = makeWalletStateCoalescer(Invitation); + // update with oldest first + for (const txt of history.reverse()) { + const { body, slots } = JSON.parse(txt); + const record = m.fromCapData({ body, slots }); + coalescer.update(record); + } + const coalesced = coalescer.state; + harden(coalesced); + return coalesced; + }; + + /** + * Get OfferStatus by id, polling until available. + * + * @param {string} from + * @param {string|number} id + * @param {number|string} minHeight + * @param {boolean} [untilNumWantsSatisfied] + */ + const pollOffer = async ( + from, + id, + minHeight, + untilNumWantsSatisfied = false, + ) => { + const poll = pollBlocks({ + client, + delay, + retryMessage: 'offer not in wallet at block', + }); + + const lookup = async () => { + const { offerStatuses } = await storedWalletState(from, minHeight); + const offerStatus = [...offerStatuses.values()].find(s => s.id === id); + if (!offerStatus) throw Error('retry'); + harden(offerStatus); + if (untilNumWantsSatisfied && !('numWantsSatisfied' in offerStatus)) { + throw Error('retry (no numWantsSatisfied yet)'); + } + return offerStatus; + }; + return poll(lookup); + }; + + /** + * @param {string} addr + * @returns {Promise} + */ + const getLastUpdate = addr => { + // @ts-expect-error cast + return readLatestHead(`published.wallet.${addr}`); + }; + + return { + networkConfig, + agoricNames, + fromBoard, + marshaller, + vstorage, + getLastUpdate, + readLatestHead, + storedWalletState, + pollOffer, + }; +}; +/** @typedef {Awaited>} WalletUtils */ diff --git a/packages/client-utils/test/exports.test.js b/packages/client-utils/test/exports.test.js new file mode 100644 index 00000000000..50e17472bf3 --- /dev/null +++ b/packages/client-utils/test/exports.test.js @@ -0,0 +1,10 @@ +/* eslint-disable import/no-extraneous-dependencies -- requiring the package itself to check exports map */ +// @ts-check + +import test from 'ava'; + +import * as index from '@agoric/client-utils'; + +test('index', t => { + t.snapshot(Object.keys(index).sort()); +}); diff --git a/packages/client-utils/test/snapshots/exports.test.js.md b/packages/client-utils/test/snapshots/exports.test.js.md new file mode 100644 index 00000000000..4c9278c387d --- /dev/null +++ b/packages/client-utils/test/snapshots/exports.test.js.md @@ -0,0 +1,20 @@ +# Snapshot report for `test/exports.test.js` + +The actual snapshot is saved in `exports.test.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## index + +> Snapshot 1 + + [ + 'boardSlottingMarshaller', + 'makeAgoricNames', + 'makeFromBoard', + 'makeRpcUtils', + 'makeVStorage', + 'makeWalletUtils', + 'pickEndpoint', + 'storageHelper', + ] diff --git a/packages/client-utils/test/snapshots/exports.test.js.snap b/packages/client-utils/test/snapshots/exports.test.js.snap new file mode 100644 index 00000000000..69755e0fad0 Binary files /dev/null and b/packages/client-utils/test/snapshots/exports.test.js.snap differ diff --git a/packages/client-utils/tsconfig.build.json b/packages/client-utils/tsconfig.build.json new file mode 100644 index 00000000000..548419bdcac --- /dev/null +++ b/packages/client-utils/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": [ + "./tsconfig.json", + "../../tsconfig-build-options.json" + ], + "exclude": [ + "scripts", + "test", + ] +} diff --git a/packages/client-utils/tsconfig.json b/packages/client-utils/tsconfig.json new file mode 100644 index 00000000000..f690da86a72 --- /dev/null +++ b/packages/client-utils/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": {}, + "include": [ + "scripts", + "src", + "test", + ], +} diff --git a/packages/cosmic-proto/scripts/codegen.cjs b/packages/cosmic-proto/scripts/codegen.cjs index 62d68c7451e..0de1c063495 100644 --- a/packages/cosmic-proto/scripts/codegen.cjs +++ b/packages/cosmic-proto/scripts/codegen.cjs @@ -1,3 +1,5 @@ +#!/usr/bin/env node +/* eslint-env node */ const { exec, spawnSync } = require('child_process'); const path = require('path'); const process = require('process'); @@ -28,7 +30,6 @@ function fixTypeImport(directory) { } if (stderr) { console.error(`Standard error: ${stderr}`); - return; } }); } diff --git a/packages/smart-wallet/package.json b/packages/smart-wallet/package.json index bd9cd503a8b..dd5f9378041 100644 --- a/packages/smart-wallet/package.json +++ b/packages/smart-wallet/package.json @@ -17,6 +17,7 @@ "lint:eslint": "eslint ." }, "devDependencies": { + "@agoric/casting": "^0.4.2", "@agoric/cosmic-proto": "^0.4.0", "@agoric/swingset-vat": "^0.32.2", "@endo/bundle-source": "^3.4.2", @@ -27,7 +28,6 @@ }, "dependencies": { "@endo/errors": "^1.2.7", - "@agoric/casting": "^0.4.2", "@agoric/ertp": "^0.16.2", "@agoric/internal": "^0.3.2", "@agoric/notifier": "^0.6.2", diff --git a/packages/smart-wallet/src/types.ts b/packages/smart-wallet/src/types.ts index 94350d9ef53..6100d4b96e8 100644 --- a/packages/smart-wallet/src/types.ts +++ b/packages/smart-wallet/src/types.ts @@ -8,10 +8,15 @@ import type { agoric } from '@agoric/cosmic-proto/agoric/bundle.js'; import type { AgoricNamesRemotes } from '@agoric/vats/tools/board-utils.js'; import type { PublicTopic } from '@agoric/zoe/src/contractSupport/topics.js'; +import type { Payment } from '@agoric/ertp'; import type { OfferSpec } from './offers.js'; declare const CapDataShape: unique symbol; +// Match the type in Zoe, which can't be imported because it's ambient. +// This omits the parameters that aren't used in this module. +type Invitation = Payment<'set'>; + /** * A petname can either be a plain string or a path for which the first element * is a petname for the origin, and the rest of the elements are a snapshot of