agoric 0.21.2-other-dev-3eb1a1d.0 → 0.21.2-other-dev-d15096d.0.d15096d

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.
@@ -4,191 +4,37 @@
4
4
  */
5
5
 
6
6
  // @ts-check
7
- import { makeWalletUtils } from '@agoric/client-utils';
8
- import { makeOfferSpecShape } from '@agoric/inter-protocol/src/auction/auctionBook.js';
9
- import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
10
- import { objectMap } from '@agoric/internal';
11
- import { M, matches } from '@endo/patterns';
12
- import { CommanderError, InvalidArgumentError } from 'commander';
13
- import { normalizeAddressWithOptions, pollBlocks } from '../lib/chain.js';
14
- import { getCurrent, outputActionAndHint, sendAction } from '../lib/wallet.js';
15
- import {
16
- asBoardRemote,
17
- bigintReplacer,
18
- makeAmountFormatter,
19
- } from '../lib/format.js';
20
- import { getNetworkConfig } from '../lib/network-config.js';
21
-
22
- const { values } = Object;
23
-
24
- const bidInvitationShape = harden({
25
- source: 'agoricContract',
26
- instancePath: ['auctioneer'],
27
- callPipe: [['makeBidInvitation', M.any()]],
28
- });
29
-
30
- /** @import {VBankAssetDetail} from '@agoric/vats/tools/board-utils.js'; */
31
- /** @import {TryExitOfferAction} from '@agoric/smart-wallet/src/smartWallet.js'; */
32
- /** @import {OfferSpec as BidSpec} from '@agoric/inter-protocol/src/auction/auctionBook.js' */
33
- /** @import {ScheduleNotification} from '@agoric/inter-protocol/src/auction/scheduler.js' */
34
- /** @import {BookDataNotification} from '@agoric/inter-protocol/src/auction/auctionBook.js' */
7
+ import { fetchEnvNetworkConfig, makeWalletUtils } from '@agoric/client-utils';
8
+ import { CommanderError } from 'commander';
9
+ import { bigintReplacer } from '../lib/format.js';
35
10
 
36
11
  /**
37
- * Format amounts, prices etc. based on brand board Ids, displayInfo
38
- *
39
- * @param {VBankAssetDetail[]} assets
12
+ * @import {VBankAssetDetail} from '@agoric/vats/tools/board-utils.js';
13
+ * @import {Timestamp} from '@agoric/time';
14
+ * @import {RelativeTimeRecord} from '@agoric/time';
15
+ * @import {OfferStatus} from '@agoric/smart-wallet/src/offers.js';
16
+ * @import {Writable} from 'stream';
17
+ * @import {createCommand} from 'commander';
18
+ * @import {execFileSync} from 'child_process';
40
19
  */
41
- const makeFormatters = assets => {
42
- const r4 = x => Math.round(x * 10_000) / 10_000;
43
-
44
- const br = asBoardRemote;
45
- const fmtAmtTuple = makeAmountFormatter(assets);
46
-
47
- /** @param {Amount} amt */
48
- const amount = amt => (([l, m]) => `${m} ${l}`)(fmtAmtTuple(br(amt)));
49
- /** @param {Record<string, Amount> | undefined} r */
50
- const record = r => (r ? objectMap(r, amount) : undefined);
51
- /** @param {Ratio} r */
52
- const price = r => {
53
- const [nl, nm] = fmtAmtTuple(br(r.numerator));
54
- const [dl, dm] = fmtAmtTuple(br(r.denominator));
55
- return `${r4(Number(nm) / Number(dm))} ${nl}/${dl}`;
56
- };
57
- /** @param {Ratio} r */
58
- const discount = r =>
59
- r4(100 - (Number(r.numerator.value) / Number(r.denominator.value)) * 100);
60
-
61
- // XXX real TimeMath.absValue requires real Remotable timerBrand
62
- /** @param {import('@agoric/time').Timestamp} ts */
63
- const absValue = ts => (typeof ts === 'bigint' ? ts : ts.absValue);
64
-
65
- /** @param {import('@agoric/time').Timestamp} tr */
66
- const absTime = tr => new Date(Number(absValue(tr)) * 1000).toISOString();
67
- /** @param {import('@agoric/time').RelativeTimeRecord} tr */
68
- const relTime = tr =>
69
- new Date(Number(tr.relValue) * 1000).toISOString().slice(11, 19);
70
-
71
- /** @param {bigint} bp */
72
- const basisPoints = bp => `${(Number(bp) / 100).toFixed(2)}%`;
73
-
74
- /**
75
- * @template T
76
- * @param {(_: T) => string} f
77
- * @returns { (x: T | null | undefined ) => string | undefined }
78
- */
79
- const maybe = f => x => (x ? f(x) : undefined);
80
-
81
- return {
82
- amount,
83
- amountOpt: maybe(amount),
84
- record,
85
- price,
86
- priceOpt: maybe(price),
87
- discount,
88
- absTime,
89
- absTimeOpt: maybe(absTime),
90
- relTime,
91
- basisPoints,
92
- };
93
- };
94
20
 
95
21
  /**
96
- * Dynamic check that an OfferStatus is also a BidSpec.
97
- *
98
- * @param {import('@agoric/smart-wallet/src/offers.js').OfferStatus} offerStatus
99
- * @param {import('@agoric/vats/tools/board-utils.js').AgoricNamesRemotes} agoricNames
100
- * @param {typeof console.warn} warn
101
- * returns null if offerStatus is not a BidSpec
102
- */
103
- const coerceBid = (offerStatus, agoricNames, warn) => {
104
- const { offerArgs } = offerStatus;
105
- /** @type {unknown} */
106
- const collateralBrand = /** @type {any} */ (offerArgs)?.maxBuy?.brand;
107
- if (!collateralBrand) {
108
- warn('mal-formed bid offerArgs', offerStatus.id, offerArgs);
109
- return null;
110
- }
111
- const bidSpecShape = makeOfferSpecShape(
112
- // @ts-expect-error XXX AssetKind narrowing?
113
- agoricNames.brand.IST,
114
- collateralBrand,
115
- );
116
- if (!matches(offerStatus.offerArgs, bidSpecShape)) {
117
- warn('mal-formed bid offerArgs', offerArgs);
118
- return null;
119
- }
120
-
121
- /**
122
- * @type {import('@agoric/smart-wallet/src/offers.js').OfferStatus &
123
- * { offerArgs: BidSpec}}
124
- */
125
- // @ts-expect-error dynamic cast
126
- const bid = offerStatus;
127
- return bid;
128
- };
129
-
130
- /**
131
- * Format amounts etc. in a BidSpec OfferStatus
132
- *
133
- * @param {import('@agoric/smart-wallet/src/offers.js').OfferStatus &
134
- * { offerArgs: BidSpec}} bid
135
- * @param {VBankAssetDetail[]} assets
136
- */
137
- export const fmtBid = (bid, assets) => {
138
- const fmt = makeFormatters(assets);
139
-
140
- const { offerArgs } = bid;
141
- /** @type {{ price: string } | { discount: number }} */
142
- const spec =
143
- 'offerPrice' in offerArgs
144
- ? { price: fmt.price(offerArgs.offerPrice) }
145
- : { discount: fmt.discount(offerArgs.offerBidScaling) };
146
-
147
- const {
148
- id,
149
- proposal: { give, want },
150
- offerArgs: { maxBuy },
151
- payouts,
152
- result,
153
- error,
154
- } = bid;
155
- const resultProp =
156
- !error && result && result !== 'UNPUBLISHED' ? { result } : {};
157
- const props = {
158
- ...(give ? { give: fmt.record(give) } : {}),
159
- ...(want ? { give: fmt.record(want) } : {}),
160
- ...(maxBuy ? { maxBuy: fmt.amount(maxBuy) } : {}),
161
- ...(payouts ? { payouts: fmt.record(payouts) } : resultProp),
162
- ...(error ? { error } : {}),
163
- };
164
- return harden({ id, ...spec, ...props });
165
- };
166
-
167
- /**
168
- * Make Inter Protocol liquidation bidding commands.
22
+ * Make Inter Protocol commands.
169
23
  *
170
24
  * @param {{
171
25
  * env: Partial<Record<string, string>>,
172
- * stdout: Pick<import('stream').Writable,'write'>,
173
- * stderr: Pick<import('stream').Writable,'write'>,
26
+ * stdout: Pick<Writable,'write'>,
27
+ * stderr: Pick<Writable,'write'>,
174
28
  * now: () => number,
175
29
  * createCommand: // Note: includes access to process.stdout, .stderr, .exit
176
- * typeof import('commander').createCommand,
177
- * execFileSync: typeof import('child_process').execFileSync,
30
+ * typeof createCommand,
31
+ * execFileSync: typeof execFileSync,
178
32
  * setTimeout: typeof setTimeout,
179
33
  * }} process
180
34
  * @param {{ fetch: typeof window.fetch }} net
181
35
  */
182
36
  export const makeInterCommand = (
183
- {
184
- env,
185
- stdout,
186
- stderr,
187
- now,
188
- setTimeout,
189
- execFileSync: rawExec,
190
- createCommand,
191
- },
37
+ { env, stdout, setTimeout, createCommand },
192
38
  { fetch },
193
39
  ) => {
194
40
  const interCmd = createCommand('inter')
@@ -206,20 +52,6 @@ export const makeInterCommand = (
206
52
  env.AGORIC_KEYRING_BACKEND,
207
53
  );
208
54
 
209
- /** @type {typeof import('child_process').execFileSync} */
210
- // @ts-expect-error execFileSync is overloaded
211
- const execFileSync = (file, args, ...opts) => {
212
- try {
213
- return rawExec(file, args, ...opts);
214
- } catch (err) {
215
- // InvalidArgumentError is a class constructor, and so
216
- // must be invoked with `new`.
217
- throw new InvalidArgumentError(
218
- `${err.message}: is ${file} in your $PATH?`,
219
- );
220
- }
221
- };
222
-
223
55
  /** @param {number} ms */
224
56
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
225
57
  const show = (info, indent = false) =>
@@ -232,7 +64,7 @@ export const makeInterCommand = (
232
64
  try {
233
65
  // XXX pass fetch to getNetworkConfig() explicitly
234
66
  // await null above makes this await safe
235
- const networkConfig = await getNetworkConfig({ env, fetch });
67
+ const networkConfig = await fetchEnvNetworkConfig({ env, fetch });
236
68
  return makeWalletUtils({ fetch, delay }, networkConfig);
237
69
  } catch (err) {
238
70
  // CommanderError is a class constructor, and so
@@ -241,351 +73,6 @@ export const makeInterCommand = (
241
73
  }
242
74
  };
243
75
 
244
- const auctionCmd = interCmd
245
- .command('auction')
246
- .description('auction commands');
247
- auctionCmd
248
- .command('status')
249
- .description(
250
- `show auction status in JSON format
251
-
252
- For example:
253
-
254
- inter auction status
255
- {
256
- "schedule": {
257
- "activeStartTime": "2023-04-19T22:50:02.000Z",
258
- "nextStartTime": "2023-04-19T23:00:02.000Z",
259
- "nextDescendingStepTime": "2023-04-19T22:51:02.000Z"
260
- },
261
- "book0": {
262
- "startPrice": "12.34 IST/ATOM",
263
- "currentPriceLevel": "11.723 IST/ATOM",
264
- "startCollateral": "0 ATOM",
265
- "collateralAvailable": "0 ATOM"
266
- },
267
- "params": {
268
- "DiscountStep": "5.00%",
269
- "ClockStep": "00:00:20",
270
- "LowestRate": "45.00%"
271
- }
272
- }
273
- `,
274
- )
275
- .option('--book <number>', 'Auction Book', Number, 0)
276
- .action(
277
- async (
278
- /**
279
- * @type {{
280
- * book: number,
281
- * }}
282
- */ opts,
283
- ) => {
284
- const { agoricNames, readPublished } = await tryMakeUtils();
285
-
286
- const [schedule, book, { current: params }] = await Promise.all([
287
- readPublished('auction.schedule'),
288
- readPublished(`auction.book${opts.book}`),
289
- readPublished('auction.governance'),
290
- ]);
291
-
292
- const fmt = makeFormatters(Object.values(agoricNames.vbankAsset));
293
- const info = {
294
- schedule: {
295
- activeStartTime: fmt.absTimeOpt(schedule.activeStartTime),
296
- nextStartTime: fmt.absTimeOpt(schedule.nextStartTime),
297
- nextDescendingStepTime: fmt.absTimeOpt(
298
- schedule.nextDescendingStepTime,
299
- ),
300
- },
301
- [`book${opts.book}`]: {
302
- startPrice: fmt.priceOpt(book.startPrice),
303
- currentPriceLevel: fmt.priceOpt(book.currentPriceLevel),
304
- startProceedsGoal: fmt.amountOpt(book.startProceedsGoal),
305
- remainingProceedsGoal: fmt.amountOpt(book.remainingProceedsGoal),
306
- proceedsRaised: fmt.amountOpt(book.proceedsRaised),
307
- startCollateral: fmt.amount(book.startCollateral),
308
- collateralAvailable: fmt.amountOpt(book.collateralAvailable),
309
- },
310
- params: {
311
- DiscountStep: fmt.basisPoints(params.DiscountStep.value),
312
- ClockStep: fmt.relTime(params.ClockStep.value),
313
- LowestRate: fmt.basisPoints(params.LowestRate.value),
314
- },
315
- };
316
-
317
- show(info, true);
318
- },
319
- );
320
-
321
- const bidCmd = interCmd
322
- .command('bid')
323
- .description('auction bidding commands');
324
-
325
- /**
326
- * @param {string} from
327
- * @param {import('@agoric/smart-wallet/src/offers.js').OfferSpec} offer
328
- * @param {Awaited<ReturnType<tryMakeUtils>>} tools
329
- * @param {boolean | undefined} dryRun
330
- */
331
- const placeBid = async (from, offer, tools, dryRun = false) => {
332
- const { networkConfig, agoricNames, pollOffer } = tools;
333
- const io = { ...networkConfig, execFileSync, delay, stdout };
334
-
335
- const { home, keyringBackend: backend, fees } = interCmd.opts();
336
- const result = await sendAction(
337
- { method: 'executeOffer', offer },
338
- { keyring: { home, backend }, from, fees, verbose: false, dryRun, ...io },
339
- );
340
- if (dryRun) {
341
- return;
342
- }
343
-
344
- assert(result); // Not dry-run
345
- const { timestamp, txhash, height } = result;
346
- console.error('bid is broadcast:');
347
- show({ timestamp, height, offerId: offer.id, txhash });
348
- const found = await pollOffer(from, offer.id, height);
349
- // TODO: command to wait 'till bid exits?
350
- const bid = coerceBid(found, agoricNames, console.warn);
351
- if (!bid) {
352
- console.warn('malformed bid', found);
353
- return;
354
- }
355
- const info = fmtBid(bid, values(agoricNames.vbankAsset));
356
- show(info);
357
- };
358
-
359
- /** @param {string} literalOrName */
360
- const normalizeAddress = literalOrName =>
361
- normalizeAddressWithOptions(literalOrName, interCmd.opts(), {
362
- execFileSync,
363
- });
364
-
365
- /**
366
- * @typedef {{
367
- * give: string,
368
- * maxBuy: string,
369
- * wantMinimum?: string,
370
- * offerId: string,
371
- * from: string,
372
- * generateOnly?: boolean,
373
- * dryRun?: boolean,
374
- * }} SharedBidOpts
375
- */
376
-
377
- /** @param {ReturnType<createCommand>} cmd */
378
- const withSharedBidOptions = cmd =>
379
- cmd
380
- .requiredOption(
381
- '--from <address>',
382
- 'wallet address literal or name',
383
- normalizeAddress,
384
- )
385
- .requiredOption('--give <amount>', 'IST to bid')
386
- .option(
387
- '--maxBuy <amount>',
388
- 'max Collateral wanted',
389
- String,
390
- '1_000_000ATOM',
391
- )
392
- .option(
393
- '--wantMinimum <amount>',
394
- 'only transact a bid that supplies this much collateral',
395
- )
396
- .option('--offer-id <string>', 'Offer id', String, `bid-${now()}`)
397
- .option('--generate-only', 'print wallet action only')
398
- .option('--dry-run', 'dry run only');
399
-
400
- withSharedBidOptions(bidCmd.command('by-price'))
401
- .description('Place a bid on collateral by price.')
402
- .requiredOption('--price <number>', 'bid price (IST/Collateral)', Number)
403
- .action(
404
- /**
405
- * @param {SharedBidOpts & {
406
- * price: number,
407
- * }} opts
408
- */
409
- async ({ generateOnly, dryRun, ...opts }) => {
410
- const tools = await tryMakeUtils();
411
-
412
- const offer = Offers.auction.Bid(tools.agoricNames, opts);
413
-
414
- if (generateOnly) {
415
- outputActionAndHint(
416
- { method: 'executeOffer', offer },
417
- { stdout, stderr },
418
- );
419
- return;
420
- }
421
-
422
- await placeBid(opts.from, offer, tools, dryRun);
423
- },
424
- );
425
-
426
- /** @param {string} v */
427
- const parsePercent = v => {
428
- const p = Number(v);
429
- if (!(p >= -100 && p <= 100)) {
430
- // InvalidArgumentError is a class constructor, and so
431
- // must be invoked with `new`.
432
- throw new InvalidArgumentError('must be between -100 and 100');
433
- }
434
- return p / 100;
435
- };
436
-
437
- withSharedBidOptions(bidCmd.command('by-discount'))
438
- .description(
439
- `Place a bid on collateral based on discount from oracle price.`,
440
- )
441
- .requiredOption(
442
- '--discount <percent>',
443
- 'bid discount (0 to 100) or markup (0 to -100) %',
444
- parsePercent,
445
- )
446
- .action(
447
- /**
448
- * @param {SharedBidOpts & {
449
- * discount: number,
450
- * }} opts
451
- */
452
- async ({ generateOnly, ...opts }) => {
453
- const tools = await tryMakeUtils();
454
-
455
- const offer = Offers.auction.Bid(tools.agoricNames, opts);
456
- if (generateOnly) {
457
- outputActionAndHint(
458
- { method: 'executeOffer', offer },
459
- { stdout, stderr },
460
- );
461
- return;
462
- }
463
- await placeBid(opts.from, offer, tools);
464
- },
465
- );
466
-
467
- bidCmd
468
- .command('cancel')
469
- .description('Try to exit a bid offer')
470
- .argument('id', 'offer id (as from bid list)')
471
- .requiredOption(
472
- '--from <address>',
473
- 'wallet address literal or name',
474
- normalizeAddress,
475
- )
476
- .option('--generate-only', 'print wallet action only')
477
- .action(
478
- /**
479
- * @param {string} id
480
- * @param {{
481
- * from: string,
482
- * generateOnly?: boolean,
483
- * }} opts
484
- */
485
- async (id, { from, generateOnly }) => {
486
- /** @type {TryExitOfferAction} */
487
- const action = { method: 'tryExitOffer', offerId: id };
488
-
489
- if (generateOnly) {
490
- outputActionAndHint(action, { stdout, stderr });
491
- return;
492
- }
493
-
494
- const { networkConfig, readPublished } = await tryMakeUtils();
495
-
496
- const current = await getCurrent(from, { readPublished });
497
- const liveIds = current.liveOffers.map(([i, _s]) => i);
498
- if (!liveIds.includes(id)) {
499
- // InvalidArgumentError is a class constructor, and so
500
- // must be invoked with `new`.
501
- throw new InvalidArgumentError(
502
- `${id} not in live offer ids: ${liveIds}`,
503
- );
504
- }
505
-
506
- const io = { ...networkConfig, execFileSync, delay, stdout };
507
-
508
- const { home, keyringBackend: backend } = interCmd.opts();
509
- const result = await sendAction(action, {
510
- keyring: { home, backend },
511
- from,
512
- verbose: false,
513
- ...io,
514
- });
515
- assert(result); // not dry-run
516
- const { timestamp, txhash, height } = result;
517
- console.error('cancel action is broadcast:');
518
- show({ timestamp, height, offerId: id, txhash });
519
-
520
- const checkGone = async blockInfo => {
521
- const pollResult = await getCurrent(from, { readPublished });
522
- const found = pollResult.liveOffers.find(([i, _]) => i === id);
523
- if (found) throw Error('retry');
524
- return blockInfo;
525
- };
526
- const blockInfo = await pollBlocks({
527
- retryMessage: 'offer still live in block',
528
- ...networkConfig,
529
- execFileSync,
530
- delay,
531
- })(checkGone);
532
- console.error('bid', id, 'is no longer live');
533
- show(blockInfo);
534
- },
535
- );
536
-
537
- bidCmd
538
- .command('list')
539
- .description(
540
- `Show status of bid offers.
541
-
542
- For example:
543
-
544
- $ inter bid list --from my-acct
545
- {"id":"bid-1679677228803","price":"9 IST/ATOM","give":{"Bid":"50IST"},"want":"5ATOM"}
546
- {"id":"bid-1679677312341","discount":10,"give":{"Bid":"200IST"},"want":"1ATOM"}
547
- `,
548
- )
549
- .requiredOption(
550
- '--from <address>',
551
- 'wallet address literal or name',
552
- normalizeAddress,
553
- )
554
- .option('--all', 'show exited bids as well')
555
- .action(
556
- /**
557
- * @param {{
558
- * from: string,
559
- * all?: boolean,
560
- * }} opts
561
- */
562
- async opts => {
563
- const { agoricNames, readPublished, storedWalletState } =
564
- await tryMakeUtils();
565
-
566
- const [current, state] = await Promise.all([
567
- getCurrent(opts.from, { readPublished }),
568
- storedWalletState(opts.from),
569
- ]);
570
- const entries = opts.all
571
- ? state.offerStatuses.entries()
572
- : current.liveOffers;
573
- for (const [id, spec] of entries) {
574
- const offerStatus = state.offerStatuses.get(id) || spec;
575
- harden(offerStatus); // coalesceWalletState should do this
576
- // console.debug(offerStatus.invitationSpec);
577
- if (!matches(offerStatus.invitationSpec, bidInvitationShape))
578
- continue;
579
-
580
- const bid = coerceBid(offerStatus, agoricNames, console.warn);
581
- if (!bid) continue;
582
-
583
- const info = fmtBid(bid, values(agoricNames.vbankAsset));
584
- show(info);
585
- }
586
- },
587
- );
588
-
589
76
  const assetCmd = interCmd
590
77
  .command('vbank')
591
78
  .description('vbank asset commands');