diff --git a/packages/agoric-cli/src/inter.js b/packages/agoric-cli/src/inter.js index 27ffd730e828..889129b54dbf 100644 --- a/packages/agoric-cli/src/inter.js +++ b/packages/agoric-cli/src/inter.js @@ -2,10 +2,19 @@ import '@endo/init'; import { makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/ratio.js'; import { createCommand } from 'commander'; -import { getNetworkConfig, makeRpcUtils } from './lib/rpc.js'; -import { outputExecuteOfferAction } from './lib/wallet.js'; +import { makeFollower, makeLeader } from '@agoric/casting'; +import { M, matches } from '@agoric/store'; +import { + boardSlottingMarshaller, + getNetworkConfig, + makeRpcUtils, +} from './lib/rpc.js'; +import { coalesceWalletState, outputExecuteOfferAction } from './lib/wallet.js'; +import { normalizeAddressWithOptions } from './lib/chain.js'; +import { makeAmountFormatter } from './lib/format.js'; const { Fail } = assert; +const { entries, fromEntries, values } = Object; const Offers = { auction: { @@ -45,7 +54,7 @@ const Offers = { }); const wantCollateral = collateral.make(opts.wantCollateral); const offerArgs = harden({ - want: wantCollateral, + want: { Collateral: wantCollateral }, offerPrice: makeRatioFromAmounts( proposal.give.Currency, wantCollateral, @@ -55,8 +64,8 @@ const Offers = { id: opts.offerId, invitationSpec: { source: 'agoricContract', - instancePath: ['auction'], - callPipe: [['getBidInvitation', [collateral.brand]]], + instancePath: ['auctioneer'], + callPipe: [['makeBidInvitation', [collateral.brand]]], }, proposal, offerArgs, @@ -65,6 +74,32 @@ const Offers = { }, }; +const mapValues = (obj, fn) => + fromEntries(entries(obj).map(([prop, val]) => [prop, fn(val)])); + +export const fmtBid = (bid, assets) => { + const fmtAmtTuple = makeAmountFormatter(assets); + const fmtAmt = amt => (([l, m]) => `${m}${l}`)(fmtAmtTuple(amt)); + const fmtRecord = r => (r ? mapValues(r, fmtAmt) : undefined); + + const { + id, + error, + proposal: { give }, + offerArgs: { want, offerPrice }, // TODO: other kind of bid + payouts, + } = bid; + const amounts = { + give: give ? fmtRecord(give) : undefined, + want: want ? fmtRecord(want) : undefined, + price: offerPrice + ? `${fmtAmt(offerPrice.numerator)}/${fmtAmt(offerPrice.denominator)}}` + : undefined, + payouts: fmtRecord(payouts), + }; + return harden({ id, ...amounts, error }); +}; + /** * @param {{ argv: string[], env: Partial>, * stdout: typeof process.stdout, clock: () => number, @@ -81,9 +116,12 @@ export const main = async ({ argv, env, stdout, clock }, { fetch }) => { ); const config = await getNetworkConfig(env); - const { agoricNames } = await makeRpcUtils({ fetch }, config); + const { agoricNames, fromBoard } = await makeRpcUtils({ fetch }, config); + + const auctionCmd = interCmd + .command('auction') + .description('auction commands'); - const auctionCmd = interCmd.command('auction'); auctionCmd .command('bid') .description('bid on collateral') @@ -96,5 +134,50 @@ export const main = async ({ argv, env, stdout, clock }, { fetch }) => { outputExecuteOfferAction(offer, stdout); }); // TODO: discount + const normalizeAddress = literalOrName => + normalizeAddressWithOptions(literalOrName, auctionCmd.opts()); + + auctionCmd + .command('list') + .description('list bids') + .requiredOption( + '--from
', + 'wallet address literal or name', + normalizeAddress, + ) + .action(async opts => { + const unserializer = boardSlottingMarshaller(fromBoard.convertSlotToVal); + + const networkConfig = await getNetworkConfig(env); + const leader = makeLeader(networkConfig.rpcAddrs[0]); + const follower = await makeFollower( + `:published.wallet.${opts.from}`, + leader, + { + // @ts-expect-error xxx + unserializer, + }, + ); + + const coalesced = await coalesceWalletState( + follower, + agoricNames.brand.Invitation, + ); + const bidInvitationShape = harden({ + source: 'agoricContract', + instancePath: ['auctioneer'], + callPipe: [['makeBidInvitation', M.any()]], + }); + for (const offerStatus of coalesced.offerStatuses.values()) { + harden(offerStatus); // coalesceWalletState should do this + // console.debug(offerStatus.invitationSpec); + if (!matches(offerStatus.invitationSpec, bidInvitationShape)) continue; + + const info = fmtBid(offerStatus, values(agoricNames.vbankAsset)); + stdout.write(JSON.stringify(info)); + stdout.write('\n'); + } + }); // TODO: discount + interCmd.parse(argv); }; diff --git a/packages/agoric-cli/src/lib/format.js b/packages/agoric-cli/src/lib/format.js index f2b8c99703d1..a0efde7db502 100644 --- a/packages/agoric-cli/src/lib/format.js +++ b/packages/agoric-cli/src/lib/format.js @@ -34,10 +34,8 @@ const exampleAsset = { /** @param {AssetDescriptor[]} assets */ export const makeAmountFormatter = assets => amt => { - const { - brand: { boardId }, - value, - } = amt; + const { brand, value } = amt; + const boardId = brand.getBoardId(); const asset = assets.find(a => a.brand.getBoardId() === boardId); if (!asset) return [NaN, boardId]; const { diff --git a/packages/agoric-cli/test/test-inter.js b/packages/agoric-cli/test/test-inter.js new file mode 100644 index 000000000000..5f66624f92b1 --- /dev/null +++ b/packages/agoric-cli/test/test-inter.js @@ -0,0 +1,79 @@ +import '@endo/init'; +import test from 'ava'; +import { fmtBid } from '../src/inter.js'; + +const brand = { + IbcATOM: { getBoardId: () => 'board00848' }, + IST: { getBoardId: () => 'board0566' }, +}; + +const agoricNames = { + brand, + + vbankAsset: { + uist: { + brand: brand.IST, + denom: 'uist', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: {}, + issuerName: 'IST', + proposedName: 'Agoric stable local currency', + }, + + 'ibc/toyatom': { + brand: brand.IbcATOM, + denom: 'ibc/toyatom', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: {}, + issuerName: 'IbcATOM', + proposedName: 'ATOM', + }, + }, +}; + +const offerStatus1 = { + error: 'Error: "nameKey" not found: (a string)', + id: 1678990150266, + invitationSpec: { + callPipe: [['getBidInvitation', [brand.IbcATOM]]], + instancePath: ['auctioneer'], + source: 'agoricContract', + }, + offerArgs: { + offerPrice: { + denominator: { brand: brand.IbcATOM, value: 2000000n }, + numerator: { brand: brand.IST, value: 20000000n }, + }, + want: { + Collateral: { brand: brand.IbcATOM, value: 2000000n }, + }, + }, + proposal: { + give: { + Currency: { brand: brand.IbcATOM, value: 20000000n }, + }, + }, +}; + +test('formatBid', t => { + const { values } = Object; + const actual = fmtBid(offerStatus1, values(agoricNames.vbankAsset)); + t.deepEqual(actual, { + id: 1678990150266, + error: 'Error: "nameKey" not found: (a string)', + give: { + Currency: '20IbcATOM', + }, + payouts: undefined, + price: '20IST/2IbcATOM}', + want: { + Collateral: '2IbcATOM', + }, + }); +});