agoric 0.18.4 → 0.18.5-dev-800c2bb.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/follow.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // @ts-check
2
2
  import process from 'process';
3
- import { Far, getInterfaceOf } from '@endo/marshal';
4
- import { decodeToJustin } from '@endo/marshal/src/marshal-justin.js';
3
+ import {
4
+ Far,
5
+ getInterfaceOf,
6
+ decodeToJustin,
7
+ makeMarshal,
8
+ } from '@endo/marshal';
5
9
 
6
10
  import {
7
11
  iterateLatest,
@@ -12,11 +16,39 @@ import {
12
16
  } from '@agoric/casting';
13
17
  import { makeLeaderOptions } from './lib/casting.js';
14
18
 
19
+ const makeCapDataToQclass = () => {
20
+ const valToSlot = new WeakMap();
21
+ const slotToVal = new Map();
22
+ const convertValToSlot = val => {
23
+ return valToSlot.get(val);
24
+ };
25
+ const convertSlotToVal = (slot, iface) => {
26
+ if (slotToVal.has(slot)) {
27
+ return slotToVal.get(slot);
28
+ }
29
+ const debugName = iface.startsWith('Alleged: ')
30
+ ? iface.slice('Alleged: '.length)
31
+ : iface;
32
+ const remotable = Far(debugName, {});
33
+
34
+ valToSlot.set(remotable, slot);
35
+ slotToVal.set(slot, remotable);
36
+ return remotable;
37
+ };
38
+ const { toCapData, fromCapData } = makeMarshal(
39
+ convertValToSlot,
40
+ convertSlotToVal,
41
+ );
42
+ const capDataToQclass = capdata => toCapData(fromCapData(capdata));
43
+ return capDataToQclass;
44
+ };
45
+
15
46
  export default async function followerMain(progname, rawArgs, powers, opts) {
16
47
  const { anylogger } = powers;
17
48
  const console = anylogger('agoric:follower');
18
49
 
19
50
  const {
51
+ firstValueOnly,
20
52
  proof,
21
53
  output,
22
54
  bootstrap = 'http://localhost:26657',
@@ -35,11 +67,12 @@ export default async function followerMain(progname, rawArgs, powers, opts) {
35
67
  switch (output) {
36
68
  case 'justinlines':
37
69
  case 'justin': {
70
+ const capDataToQclass = makeCapDataToQclass();
38
71
  followerOptions.unserializer = null;
39
72
  const pretty = !output.endsWith('lines');
40
- formatOutput = ({ body, slots }) => {
73
+ formatOutput = capdata => {
74
+ const { body, slots } = capDataToQclass(capdata);
41
75
  const encoded = JSON.parse(body);
42
- // @ts-expect-error Expected 1-2 arguments, but got 3.
43
76
  return decodeToJustin(encoded, pretty, slots);
44
77
  };
45
78
  break;
@@ -121,6 +154,9 @@ export default async function followerMain(progname, rawArgs, powers, opts) {
121
154
  value,
122
155
  )}\n`,
123
156
  );
157
+ if (firstValueOnly) {
158
+ return;
159
+ }
124
160
  }
125
161
  }),
126
162
  );
package/src/init.js CHANGED
@@ -2,8 +2,10 @@ import chalk from 'chalk';
2
2
  import { makePspawn } from './helpers.js';
3
3
 
4
4
  // Ambient types. Needed only for dev but this does a runtime import.
5
+ // https://github.com/Agoric/agoric-sdk/issues/6512
5
6
  import '@endo/captp/src/types.js';
6
7
  import '@agoric/swingset-vat/exported.js';
8
+ import '@agoric/swingset-vat/src/vats/network/types.js';
7
9
 
8
10
  // Use either an absolute template URL, or find it relative to DAPP_URL_BASE.
9
11
  const gitURL = (relativeOrAbsoluteURL, base) => {
@@ -38,15 +40,14 @@ export default async function initMain(_progname, rawArgs, priv, opts) {
38
40
  dappBranch = ['-b', opts.dappBranch];
39
41
  }
40
42
 
41
- if (
42
- await pspawn(
43
- 'git',
44
- ['clone', '--origin=upstream', dappURL, DIR, ...dappBranch],
45
- {
46
- stdio: 'inherit',
47
- },
48
- )
49
- ) {
43
+ const exitStatus = await pspawn(
44
+ 'git',
45
+ ['clone', '--origin=upstream', dappURL, DIR, ...dappBranch],
46
+ {
47
+ stdio: 'inherit',
48
+ },
49
+ );
50
+ if (exitStatus) {
50
51
  throw Error('cannot clone');
51
52
  }
52
53
 
@@ -55,7 +56,7 @@ export default async function initMain(_progname, rawArgs, priv, opts) {
55
56
 
56
57
  let topLevelName;
57
58
  const subdirs = ['', 'api/', 'contract/', 'ui/', '_agstate/agoric-servers/'];
58
- for (const dir of subdirs) {
59
+ for await (const dir of subdirs) {
59
60
  const path = `${DIR}/${dir}package.json`;
60
61
  log('rewriting ', path);
61
62
 
package/src/install.js CHANGED
@@ -59,7 +59,7 @@ export default async function installMain(progname, rawArgs, powers, opts) {
59
59
  }
60
60
 
61
61
  const yarnInstallEachWorktree = async (phase, ...flags) => {
62
- for (const workTree of workTrees) {
62
+ for await (const workTree of workTrees) {
63
63
  log.info(`yarn install ${phase} in ${workTree}`);
64
64
  // eslint-disable-next-line no-await-in-loop
65
65
  const yarnInstall = await pspawn(
@@ -226,15 +226,14 @@ export default async function installMain(progname, rawArgs, powers, opts) {
226
226
  }
227
227
  }
228
228
  } else {
229
+ const subdirPackageJsonExists = async subd => {
230
+ const exists = await fs.stat(`${subd}/package.json`).catch(_ => false);
231
+ return exists && subd;
232
+ };
229
233
  const existingSubdirs = await Promise.all(
230
234
  ['.', '_agstate/agoric-servers', 'contract', 'api', 'ui']
231
235
  .sort()
232
- .map(async subd => {
233
- const exists = await fs
234
- .stat(`${subd}/package.json`)
235
- .catch(_ => false);
236
- return exists && subd;
237
- }),
236
+ .map(subdirPackageJsonExists),
238
237
  );
239
238
  subdirs = existingSubdirs.filter(subd => subd);
240
239
  }
@@ -254,22 +253,21 @@ export default async function installMain(progname, rawArgs, powers, opts) {
254
253
  await fs.symlink(sdkRoot, sdkWorktree);
255
254
  }
256
255
 
257
- await Promise.all(
258
- subdirs.map(async subdir => {
259
- const nm = `${subdir}/node_modules`;
260
- log(chalk.bold.green(`removing ${nm} link`));
261
- await fs.unlink(nm).catch(_ => {});
256
+ const removeNodeModulesSymlinks = async subdir => {
257
+ const nm = `${subdir}/node_modules`;
258
+ log(chalk.bold.green(`removing ${nm} link`));
259
+ await fs.unlink(nm).catch(_ => {});
262
260
 
263
- // Remove all the package links.
264
- // This is needed to prevent yarn errors when installing new versions of
265
- // linked modules (like `@agoric/zoe`).
266
- await Promise.all(
267
- [...sdkPackageToPath.keys()].map(async pjName =>
268
- fs.unlink(`${nm}/${pjName}`).catch(_ => {}),
269
- ),
270
- );
271
- }),
272
- );
261
+ // Remove all the package links.
262
+ // This is needed to prevent yarn errors when installing new versions of
263
+ // linked modules (like `@agoric/zoe`).
264
+ await Promise.all(
265
+ [...sdkPackageToPath.keys()].map(async pjName =>
266
+ fs.unlink(`${nm}/${pjName}`).catch(_ => {}),
267
+ ),
268
+ );
269
+ };
270
+ await Promise.all(subdirs.map(removeNodeModulesSymlinks));
273
271
  } else {
274
272
  DEFAULT_SDK_PACKAGE_NAMES.forEach(name => sdkPackageToPath.set(name, null));
275
273
  }
@@ -310,17 +308,14 @@ export default async function installMain(progname, rawArgs, powers, opts) {
310
308
  );
311
309
 
312
310
  const sdkPackages = [...sdkPackageToPath.keys()].sort();
313
- for (const subdir of subdirs) {
314
- // eslint-disable-next-line no-await-in-loop
311
+ for await (const subdir of subdirs) {
315
312
  const exists = await fs.stat(`${subdir}/package.json`).catch(_ => false);
316
- if (
317
- exists &&
318
- // eslint-disable-next-line no-await-in-loop
319
- (await pspawn('yarn', [...linkFlags, 'link', ...sdkPackages], {
313
+ const exitStatus = await (exists &&
314
+ pspawn('yarn', [...linkFlags, 'link', ...sdkPackages], {
320
315
  stdio: 'inherit',
321
316
  cwd: subdir,
322
- }))
323
- ) {
317
+ }));
318
+ if (exitStatus) {
324
319
  log.error('Cannot yarn link', ...sdkPackages);
325
320
  return 1;
326
321
  }
@@ -48,14 +48,14 @@ export const makeJsonHttpClient = ({ http }) => {
48
48
  const responseBytes = Buffer.concat(chunks);
49
49
  const responseText = textDecoder.decode(responseBytes);
50
50
  if (response.statusCode !== 200) {
51
- throw new Error(`${responseText}`);
51
+ throw Error(`${responseText}`);
52
52
  }
53
53
  // May throw: becomes rejection below.
54
54
  const responseBody = JSON.parse(responseText);
55
55
  resolve(responseBody);
56
56
  })().catch(cause => {
57
57
  reject(
58
- new Error(`JSON HTTP RPC failed with error: ${cause.message}`, {
58
+ Error(`JSON HTTP RPC failed with error: ${cause.message}`, {
59
59
  cause,
60
60
  }),
61
61
  );
package/src/json.js CHANGED
@@ -12,7 +12,7 @@ export const parseLocatedJson = (source, location) => {
12
12
  return JSON.parse(source);
13
13
  } catch (error) {
14
14
  if (error instanceof SyntaxError) {
15
- throw new SyntaxError(`Cannot parse JSON from ${location}, ${error}`);
15
+ throw SyntaxError(`Cannot parse JSON from ${location}, ${error}`);
16
16
  }
17
17
  throw error;
18
18
  }
@@ -3,8 +3,10 @@
3
3
  import { delay, exponentialBackoff, randomBackoff } from '@agoric/casting';
4
4
 
5
5
  // TODO: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
6
- /** @param {{log: (...args: any) => void, sleep: number, jitter: number }} config
7
- * @returns {import('@agoric/casting').LeaderOptions} */
6
+ /**
7
+ * @param {{log: (...args: any) => void, sleep: number, jitter: number }} config
8
+ * @returns {import('@agoric/casting').LeaderOptions}
9
+ */
8
10
  export const makeLeaderOptions = ({ log, sleep, jitter }) => {
9
11
  return {
10
12
  retryCallback: (where, e, attempt) => {
package/src/lib/chain.js CHANGED
@@ -1,60 +1,174 @@
1
1
  // @ts-check
2
2
  /* global process */
3
3
  import { normalizeBech32 } from '@cosmjs/encoding';
4
- import { execSync } from 'child_process';
4
+ import { execFileSync as execFileSyncAmbient } from 'child_process';
5
5
 
6
- export const normalizeAddress = literalOrName => {
6
+ const agdBinary = 'agd';
7
+
8
+ /**
9
+ * @param {string} literalOrName
10
+ * @param {{ keyringBackend?: string }} opts
11
+ * @param {{ execFileSync: typeof execFileSyncAmbient }} [io]
12
+ */
13
+ export const normalizeAddressWithOptions = (
14
+ literalOrName,
15
+ { keyringBackend = undefined } = {},
16
+ io = { execFileSync: execFileSyncAmbient },
17
+ ) => {
7
18
  try {
8
19
  return normalizeBech32(literalOrName);
9
20
  } catch (_) {
10
21
  // not an address so try as a key
11
- const buff = execSync(`agd keys show --address ${literalOrName}`);
22
+ const backendOpt = keyringBackend
23
+ ? [`--keyring-backend=${keyringBackend}`]
24
+ : [];
25
+ const buff = io.execFileSync(agdBinary, [
26
+ `keys`,
27
+ ...backendOpt,
28
+ `show`,
29
+ `--address`,
30
+ literalOrName,
31
+ ]);
12
32
  return normalizeBech32(buff.toString().trim());
13
33
  }
14
34
  };
15
- harden(normalizeAddress);
35
+ harden(normalizeAddressWithOptions);
16
36
 
17
37
  /**
18
- * SECURITY: closes over process and child_process
19
- *
20
- * @param {string} swingsetArgs
21
- * @param {import('./rpc').MinimalNetworkConfig} net
22
- * @param {string} from
23
- * @param {boolean} [dryRun]
24
- * @param {{home: string, backend: string}} [keyring]
38
+ * @param {ReadonlyArray<string>} swingsetArgs
39
+ * @param {import('./rpc').MinimalNetworkConfig & {
40
+ * from: string,
41
+ * dryRun?: boolean,
42
+ * verbose?: boolean,
43
+ * keyring?: {home?: string, backend: string}
44
+ * stdout?: Pick<import('stream').Writable, 'write'>
45
+ * execFileSync?: typeof import('child_process').execFileSync
46
+ * }} opts
25
47
  */
26
- export const execSwingsetTransaction = (
27
- swingsetArgs,
28
- net,
29
- from,
30
- dryRun = false,
31
- keyring = undefined,
32
- ) => {
33
- const { chainName, rpcAddrs } = net;
34
-
35
- const homeOpt = keyring?.home ? `--home=${keyring.home}` : '';
48
+ export const execSwingsetTransaction = (swingsetArgs, opts) => {
49
+ const {
50
+ from,
51
+ dryRun = false,
52
+ verbose = true,
53
+ keyring = undefined,
54
+ chainName,
55
+ rpcAddrs,
56
+ stdout = process.stdout,
57
+ execFileSync = execFileSyncAmbient,
58
+ } = opts;
59
+ const homeOpt = keyring?.home ? [`--home=${keyring.home}`] : [];
36
60
  const backendOpt = keyring?.backend
37
- ? `--keyring-backend=${keyring.backend}`
38
- : '';
39
- const cmd = `agd --node=${rpcAddrs[0]} --chain-id=${chainName} ${homeOpt} ${backendOpt} --from=${from} tx swingset ${swingsetArgs}`;
61
+ ? [`--keyring-backend=${keyring.backend}`]
62
+ : [];
63
+ const cmd = [`--node=${rpcAddrs[0]}`, `--chain-id=${chainName}`].concat(
64
+ homeOpt,
65
+ backendOpt,
66
+ [`--from=${from}`, 'tx', 'swingset'],
67
+ swingsetArgs,
68
+ );
40
69
 
41
70
  if (dryRun) {
42
- process.stdout.write('Run this interactive command in shell:\n\n');
43
- process.stdout.write(cmd);
44
- process.stdout.write('\n');
71
+ stdout.write(`Run this interactive command in shell:\n\n`);
72
+ stdout.write(`${agdBinary} `);
73
+ stdout.write(cmd.join(' '));
74
+ stdout.write('\n');
45
75
  } else {
46
- const yesCmd = `${cmd} --yes`;
47
- console.log('Executing ', yesCmd);
48
- execSync(yesCmd);
76
+ const yesCmd = cmd.concat(['--yes']);
77
+ if (verbose) console.log('Executing ', yesCmd);
78
+ return execFileSync(agdBinary, yesCmd, { encoding: 'utf-8' });
49
79
  }
50
80
  };
51
81
  harden(execSwingsetTransaction);
52
82
 
53
83
  // xxx rpc should be able to query this by HTTP without shelling out
54
84
  export const fetchSwingsetParams = net => {
55
- const { chainName, rpcAddrs } = net;
56
- const cmd = `agd --node=${rpcAddrs[0]} --chain-id=${chainName} query swingset params --output --json`;
57
- const buffer = execSync(cmd);
85
+ const { chainName, rpcAddrs, execFileSync = execFileSyncAmbient } = net;
86
+ const cmd = [
87
+ `--node=${rpcAddrs[0]}`,
88
+ `--chain-id=${chainName}`,
89
+ 'query',
90
+ 'swingset',
91
+ 'params',
92
+ '--output',
93
+ '--json',
94
+ ];
95
+ const buffer = execFileSync(agdBinary, cmd);
58
96
  return JSON.parse(buffer.toString());
59
97
  };
60
98
  harden(fetchSwingsetParams);
99
+
100
+ /**
101
+ * @param {import('./rpc').MinimalNetworkConfig & {
102
+ * execFileSync: typeof import('child_process').execFileSync,
103
+ * delay: (ms: number) => Promise<void>,
104
+ * period?: number,
105
+ * retryMessage?: string,
106
+ * }} opts
107
+ * @returns {<T>(l: (b: { time: string, height: string }) => Promise<T>) => Promise<T>}
108
+ */
109
+ export const pollBlocks = opts => async lookup => {
110
+ const { execFileSync, delay, rpcAddrs, period = 3 * 1000 } = opts;
111
+ const { retryMessage } = opts;
112
+
113
+ const nodeArgs = [`--node=${rpcAddrs[0]}`];
114
+
115
+ await null; // separate sync prologue
116
+
117
+ for (;;) {
118
+ const sTxt = execFileSync(agdBinary, ['status', ...nodeArgs]);
119
+ const status = JSON.parse(sTxt.toString());
120
+ const {
121
+ SyncInfo: { latest_block_time: time, latest_block_height: height },
122
+ } = status;
123
+ try {
124
+ // see await null above
125
+ // eslint-disable-next-line @jessie.js/no-nested-await, no-await-in-loop
126
+ const result = await lookup({ time, height });
127
+ return result;
128
+ } catch (_err) {
129
+ console.error(
130
+ time,
131
+ retryMessage || 'not in block',
132
+ height,
133
+ 'retrying...',
134
+ );
135
+ // eslint-disable-next-line @jessie.js/no-nested-await, no-await-in-loop
136
+ await delay(period);
137
+ }
138
+ }
139
+ };
140
+
141
+ /**
142
+ * @param {string} txhash
143
+ * @param {import('./rpc').MinimalNetworkConfig & {
144
+ * execFileSync: typeof import('child_process').execFileSync,
145
+ * delay: (ms: number) => Promise<void>,
146
+ * period?: number,
147
+ * }} opts
148
+ */
149
+ export const pollTx = async (txhash, opts) => {
150
+ const { execFileSync, rpcAddrs, chainName } = opts;
151
+
152
+ const nodeArgs = [`--node=${rpcAddrs[0]}`];
153
+ const outJson = ['--output', 'json'];
154
+
155
+ const lookup = async () => {
156
+ const out = execFileSync(
157
+ agdBinary,
158
+ [
159
+ 'query',
160
+ 'tx',
161
+ txhash,
162
+ `--chain-id=${chainName}`,
163
+ ...nodeArgs,
164
+ ...outJson,
165
+ ],
166
+ { stdio: ['ignore', 'pipe', 'ignore'] },
167
+ );
168
+ // XXX this type is defined in a .proto file somewhere
169
+ /** @type {{ height: string, txhash: string, code: number, timestamp: string }} */
170
+ const info = JSON.parse(out.toString());
171
+ return info;
172
+ };
173
+ return pollBlocks({ ...opts, retryMessage: 'tx not in block' })(lookup);
174
+ };
package/src/lib/format.js CHANGED
@@ -1,14 +1,12 @@
1
1
  // @ts-check
2
- /* eslint-disable @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */
2
+ import { makeBoardRemote } from '@agoric/vats/tools/board-utils.js';
3
3
  // eslint-disable-next-line no-unused-vars -- typeof below
4
4
  import { makeAgoricNames } from './rpc.js';
5
5
 
6
6
  // ambient types
7
- import '@agoric/ertp/src/types.js';
7
+ import '@agoric/ertp/src/types-ambient.js';
8
8
 
9
- /** @typedef {import('@agoric/smart-wallet/src/offers').OfferStatus} OfferStatus */
10
-
11
- export const COSMOS_UNIT = 1_000_000n;
9
+ /** @typedef {import('@agoric/vats/tools/board-utils.js').BoardRemote} BoardRemote */
12
10
 
13
11
  /**
14
12
  * Like @endo/nat but coerces
@@ -19,44 +17,55 @@ export const COSMOS_UNIT = 1_000_000n;
19
17
  export const Natural = str => {
20
18
  const b = BigInt(str);
21
19
  if (b < 0) {
22
- throw new RangeError(`${b} is negative`);
20
+ throw RangeError(`${b} is negative`);
23
21
  }
24
22
  return b;
25
23
  };
26
24
 
25
+ /**
26
+ * JSON.stringify replacer to handle bigint
27
+ *
28
+ * @param {unknown} k
29
+ * @param {unknown} v
30
+ */
31
+ export const bigintReplacer = (k, v) => (typeof v === 'bigint' ? `${v}` : v);
32
+
33
+ /** @type {import('@agoric/vats/tools/board-utils.js').VBankAssetDetail} */
27
34
  // eslint-disable-next-line no-unused-vars
28
35
  const exampleAsset = {
29
- brand: { boardId: 'board0425', iface: 'Alleged: BLD brand' },
36
+ // @ts-expect-error cast
37
+ brand: makeBoardRemote({ boardId: 'board0425', iface: 'Alleged: BLD brand' }),
30
38
  displayInfo: { assetKind: 'nat', decimalPlaces: 6 },
31
- issuer: { boardId: null, iface: undefined },
39
+ // @ts-expect-error cast
40
+ issuer: makeBoardRemote({ boardId: null, iface: undefined }),
32
41
  petname: 'Agoric staking token',
33
42
  };
34
- /** @typedef {import('@agoric/smart-wallet/src/smartWallet').BrandDescriptor & {brand: import('./rpc').RpcRemote}} AssetDescriptor */
43
+ /** @typedef {import('@agoric/vats/tools/board-utils.js').VBankAssetDetail } AssetDescriptor */
35
44
 
36
- /** @param {AssetDescriptor[]} assets */
45
+ /**
46
+ * @param {AssetDescriptor[]} assets
47
+ * @returns {(a: Amount & { brand: BoardRemote }) => [string, number | any[]]}
48
+ */
37
49
  export const makeAmountFormatter = assets => amt => {
50
+ const { brand, value } = amt;
51
+ const boardId = brand.getBoardId();
52
+ const asset = assets.find(a => a.brand.getBoardId() === boardId);
53
+ if (!asset) return [boardId, Number(value)]; // don't crash
38
54
  const {
39
- brand: { boardId },
40
- value,
41
- } = amt;
42
- const asset = assets.find(a => a.brand.boardId === boardId);
43
- if (!asset) return [NaN, boardId];
44
- const {
45
- petname,
46
55
  displayInfo: { assetKind, decimalPlaces = 0 },
56
+ issuerName,
47
57
  } = asset;
48
- const name = Array.isArray(petname) ? petname.join('.') : petname;
49
58
  switch (assetKind) {
50
59
  case 'nat':
51
- /** @type {[petname: string, qty: number]} */
52
- return [name, Number(value) / 10 ** decimalPlaces];
60
+ return [issuerName, Number(value) / 10 ** decimalPlaces];
53
61
  case 'set':
62
+ assert(Array.isArray(value));
54
63
  if (value[0]?.handle?.iface?.includes('InvitationHandle')) {
55
- return [name, value.map(v => v.description)];
64
+ return [issuerName, value.map(v => v.description)];
56
65
  }
57
- return [name, value];
66
+ return [issuerName, value];
58
67
  default:
59
- return [name, ['?']];
68
+ return [issuerName, [NaN]];
60
69
  }
61
70
  };
62
71
 
@@ -66,15 +75,31 @@ export const asPercent = ratio => {
66
75
  return (100 * Number(numerator.value)) / Number(denominator.value);
67
76
  };
68
77
 
78
+ const { Fail, quote: q } = assert;
79
+
80
+ const isObject = x => typeof x === 'object' && x !== null;
81
+
82
+ /**
83
+ * @param {Amount} x
84
+ * @returns {Amount & { brand: BoardRemote }}
85
+ */
86
+ export const asBoardRemote = x => {
87
+ isObject(x) || Fail`not object: ${q(x)}`;
88
+ isObject(x.brand) || Fail`not object: ${q(x.brand)}`;
89
+ 'getBoardId' in x.brand || Fail`missing getBoardId: ${q(x.brand)}`;
90
+ // @ts-expect-error dynamic check
91
+ return x;
92
+ };
93
+
69
94
  /**
70
95
  * Summarize the balances array as user-facing informative tuples
71
-
96
+ *
72
97
  * @param {import('@agoric/smart-wallet/src/smartWallet').CurrentWalletRecord['purses']} purses
73
98
  * @param {AssetDescriptor[]} assets
74
99
  */
75
100
  export const purseBalanceTuples = (purses, assets) => {
76
101
  const fmt = makeAmountFormatter(assets);
77
- return purses.map(b => fmt(b.balance));
102
+ return purses.map(b => fmt(asBoardRemote(b.balance)));
78
103
  };
79
104
 
80
105
  /**
@@ -100,12 +125,8 @@ export const fmtRecordOfLines = record => {
100
125
  * @param {Awaited<ReturnType<typeof makeAgoricNames>>} agoricNames
101
126
  */
102
127
  export const offerStatusTuples = (state, agoricNames) => {
103
- const { brands, offerStatuses } = state;
104
- const fmt = makeAmountFormatter(
105
- // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */
106
- // @ts-ignore xxx RpcRemote
107
- [...brands.values()],
108
- );
128
+ const { offerStatuses } = state;
129
+ const fmt = makeAmountFormatter(Object.values(agoricNames.vbankAsset));
109
130
  const fmtRecord = r =>
110
131
  r
111
132
  ? Object.fromEntries(
@@ -116,9 +137,10 @@ export const offerStatusTuples = (state, agoricNames) => {
116
137
  assert(o);
117
138
  let when = '??';
118
139
  try {
119
- when = new Date(o.id).toISOString();
140
+ const timestamp = String(o.id).replace(/\D+/g, '');
141
+ when = new Date(Number(timestamp)).toISOString();
120
142
  } catch {
121
- console.debug('offer id', o.id, 'not a timestamp');
143
+ console.debug('offer id', o.id, 'does not contain a timestamp');
122
144
  }
123
145
  const {
124
146
  proposal: { give, want },
@@ -136,7 +158,6 @@ export const offerStatusTuples = (state, agoricNames) => {
136
158
  } = o;
137
159
  // xxx could be O(1)
138
160
  const entry = Object.entries(agoricNames.instance).find(
139
- // @ts-ignore minimarshal types are off by a bit
140
161
  ([_name, candidate]) => candidate === instance,
141
162
  );
142
163
  const instanceName = entry ? entry[0] : '???';
@@ -156,27 +177,21 @@ export const offerStatusTuples = (state, agoricNames) => {
156
177
  };
157
178
 
158
179
  /**
159
- *
160
180
  * @param {import('@agoric/smart-wallet/src/smartWallet').CurrentWalletRecord} current
161
181
  * @param {ReturnType<import('@agoric/smart-wallet/src/utils.js').makeWalletStateCoalescer>['state']} coalesced
162
182
  * @param {Awaited<ReturnType<typeof makeAgoricNames>>} agoricNames
163
183
  */
164
184
  export const summarize = (current, coalesced, agoricNames) => {
165
185
  return {
166
- lastOfferId: [current.lastOfferId],
167
186
  purses: purseBalanceTuples(
168
- // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */
169
- // @ts-ignore xxx RpcRemote
170
187
  [...current.purses.values()],
171
- // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */
172
- // @ts-ignore xxx RpcRemote
173
- [...current.brands.values()],
188
+ Object.values(agoricNames.vbankAsset),
174
189
  ),
175
- usedInvitations: Object.entries(current.offerToUsedInvitation).map(
190
+ usedInvitations: current.offerToUsedInvitation.map(
176
191
  ([offerId, invitationAmt]) => [
177
192
  agoricNames.reverse[invitationAmt.value[0].instance.boardId],
178
193
  invitationAmt.value[0].description,
179
- Number(offerId),
194
+ offerId,
180
195
  ],
181
196
  ),
182
197
  offers: offerStatusTuples(coalesced, agoricNames),