@agoric/smart-wallet 0.5.4-other-dev-8f8782b.0 → 0.5.4-other-dev-3eb1a1d.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.
Files changed (41) hide show
  1. package/package.json +35 -26
  2. package/src/index.d.ts +2 -0
  3. package/src/index.d.ts.map +1 -0
  4. package/src/index.js +2 -0
  5. package/src/invitations.d.ts +14 -10
  6. package/src/invitations.d.ts.map +1 -1
  7. package/src/invitations.js +35 -32
  8. package/src/marshal-contexts.d.ts +44 -41
  9. package/src/marshal-contexts.d.ts.map +1 -1
  10. package/src/marshal-contexts.js +68 -61
  11. package/src/offerWatcher.d.ts +52 -0
  12. package/src/offerWatcher.d.ts.map +1 -0
  13. package/src/offerWatcher.js +329 -0
  14. package/src/offers.d.ts +7 -31
  15. package/src/offers.d.ts.map +1 -1
  16. package/src/offers.js +9 -183
  17. package/src/proposals/upgrade-wallet-factory2-proposal.d.ts +23 -0
  18. package/src/proposals/upgrade-wallet-factory2-proposal.d.ts.map +1 -0
  19. package/src/proposals/upgrade-wallet-factory2-proposal.js +60 -0
  20. package/src/proposals/upgrade-walletFactory-proposal.d.ts +1 -1
  21. package/src/proposals/upgrade-walletFactory-proposal.d.ts.map +1 -1
  22. package/src/proposals/upgrade-walletFactory-proposal.js +46 -23
  23. package/src/smartWallet.d.ts +101 -66
  24. package/src/smartWallet.d.ts.map +1 -1
  25. package/src/smartWallet.js +576 -216
  26. package/src/typeGuards.d.ts +1 -1
  27. package/src/types-index.d.ts +2 -0
  28. package/src/types-index.js +2 -0
  29. package/src/types.d.ts +35 -41
  30. package/src/types.d.ts.map +1 -0
  31. package/src/types.ts +90 -0
  32. package/src/utils.d.ts +17 -14
  33. package/src/utils.d.ts.map +1 -1
  34. package/src/utils.js +19 -6
  35. package/src/walletFactory.d.ts +24 -78
  36. package/src/walletFactory.d.ts.map +1 -1
  37. package/src/walletFactory.js +61 -37
  38. package/CHANGELOG.md +0 -180
  39. package/src/payments.d.ts +0 -20
  40. package/src/payments.d.ts.map +0 -1
  41. package/src/payments.js +0 -89
@@ -1,12 +1,16 @@
1
1
  // @ts-check
2
+ import { Fail, q } from '@endo/errors';
3
+ import { HandledPromise } from '@endo/eventual-send'; // TODO: convince tsc this isn't needed
4
+
2
5
  import { makeScalarMapStore } from '@agoric/store';
3
6
  import { Far, makeMarshal, Remotable } from '@endo/marshal';
4
- import { HandledPromise } from '@endo/eventual-send'; // TODO: convince tsc this isn't needed
5
7
  import { DEFAULT_PREFIX } from '@agoric/vats/src/lib-board.js';
6
8
 
7
- const { Fail, quote: q } = assert;
8
-
9
- /** @typedef {import('@agoric/vats/src/lib-board.js').BoardId} BoardId */
9
+ /**
10
+ * @import {PassableCap, RemotableObject} from '@endo/marshal';
11
+ * @import {Key} from '@endo/patterns';
12
+ * @import {BoardId} from '@agoric/vats/src/lib-board.js';
13
+ */
10
14
 
11
15
  /**
12
16
  * ID from a board made with { prefix: DEFAULT_PREFIX }
@@ -19,11 +23,10 @@ const isDefaultBoardId = specimen => {
19
23
  };
20
24
 
21
25
  /**
22
- * When marshaling a purse, payment, etc. we partition the slots
23
- * using prefixes.
26
+ * When marshaling a purse, payment, etc. we partition the slots using prefixes.
24
27
  *
25
- * @template {Record<string, IdTable<*,*>>} T
26
- * @typedef {`${string & keyof T}:${Digits}`} WalletSlot<T>
28
+ * @template {Record<string, IdTable<any, any>>} T
29
+ * @typedef {`${string & keyof T}:${Digits}`} WalletSlot
27
30
  */
28
31
  /**
29
32
  * @template {string} K
@@ -31,7 +34,7 @@ const isDefaultBoardId = specimen => {
31
34
  */
32
35
 
33
36
  /**
34
- * @template {Record<string, IdTable<*,*>>} T
37
+ * @template {Record<string, IdTable<any, any>>} T
35
38
  * @param {T} _tables
36
39
  * @param {string & keyof T} kind
37
40
  * @param {number} id
@@ -43,10 +46,10 @@ const makeWalletSlot = (_tables, kind, id) => {
43
46
  };
44
47
 
45
48
  /**
46
- * @template {Record<string, IdTable<*,*>>} T
49
+ * @template {Record<string, IdTable<any, any>>} T
47
50
  * @param {T} record
48
51
  * @param {(value: string, index: number, obj: string[]) => boolean} predicate
49
- * @returns {string & keyof T | undefined}
52
+ * @returns {(string & keyof T) | undefined}
50
53
  */
51
54
  const findKey = (record, predicate) => {
52
55
  const key = Object.keys(record).find(predicate);
@@ -54,10 +57,10 @@ const findKey = (record, predicate) => {
54
57
  };
55
58
 
56
59
  /**
57
- * @template {Record<string, IdTable<*,*>>} T
60
+ * @template {Record<string, IdTable<any, any>>} T
58
61
  * @param {T} tables
59
62
  * @param {string} slot
60
- * @returns {{ kind: undefined | string & keyof T, id: number }}
63
+ * @returns {{ kind: undefined | (string & keyof T); id: number }}
61
64
  */
62
65
  const parseWalletSlot = (tables, slot) => {
63
66
  const kind = findKey(tables, k => slot.startsWith(`${k}:`));
@@ -66,32 +69,30 @@ const parseWalletSlot = (tables, slot) => {
66
69
  };
67
70
 
68
71
  /**
69
- * Since KindSlots always include a colon and BoardIds never do,
70
- * we an mix them without confusion.
72
+ * Since KindSlots always include a colon and BoardIds never do, we an mix them
73
+ * without confusion.
71
74
  *
72
- * @template {Record<string, IdTable<*,*>>} T
73
- * @typedef {WalletSlot<T> | BoardId} MixedSlot<T>
75
+ * @template {Record<string, IdTable<any, any>>} T
76
+ * @typedef {WalletSlot<T> | BoardId} MixedSlot
74
77
  */
75
78
  /**
76
- * @typedef {`1` | `12` | `123`} Digits - 1 or more digits.
77
- * NOTE: the typescript definition here is more restrictive than
78
- * actual usage.
79
+ * @typedef {`1` | `12` | `123`} Digits - 1 or more digits. NOTE: the typescript
80
+ * definition here is more restrictive than actual usage.
79
81
  */
80
82
 
81
83
  /**
82
- * @template Slot
83
- * @template Val
84
- *
84
+ * @template {Key} Slot
85
+ * @template {PassableCap} Val
85
86
  * @typedef {{
86
- * bySlot: MapStore<Slot, Val>,
87
- * byVal: MapStore<Val, Slot>,
87
+ * bySlot: MapStore<Slot, Val>;
88
+ * byVal: MapStore<Val, Slot>;
88
89
  * }} IdTable<Value>
89
90
  */
90
91
 
91
92
  /**
92
- * @template Slot
93
- * @template Val
94
- * @param {IdTable<Slot, Val>} table
93
+ * @template {Key} Slot
94
+ * @template {PassableCap} Val
95
+ * @param {IdTable<Slot, PassableCap>} table
95
96
  * @param {Slot} slot
96
97
  * @param {Val} val
97
98
  */
@@ -101,17 +102,20 @@ const initSlotVal = (table, slot, val) => {
101
102
  };
102
103
 
103
104
  /**
104
- * Make context for exporting wallet data where brands etc. can be recognized by boardId.
105
- * Export for use outside the smart wallet.
105
+ * Make context for exporting wallet data where brands etc. can be recognized by
106
+ * boardId. Export for use outside the smart wallet.
106
107
  *
107
108
  * When serializing wallet state for, there's a tension between
108
109
  *
109
- * - keeping purses etc. closely held
110
- * - recognizing identity of brands also referenced in the state of contracts such as the AMM
110
+ * - keeping purses etc. closely held
111
+ * - recognizing identity of brands also referenced in the state of contracts such
112
+ * as the AMM
113
+ *
114
+ * `makeMarshal()` is parameterized by the type of slots. Here we use a disjoint
115
+ * union of
111
116
  *
112
- * `makeMarshal()` is parameterized by the type of slots. Here we use a disjoint union of
113
- * - board ids for widely shared objects
114
- * - kind:seq ids for closely held objects; for example purse:123
117
+ * - board ids for widely shared objects
118
+ * - kind:seq ids for closely held objects; for example purse:123
115
119
  */
116
120
  export const makeExportContext = () => {
117
121
  const walletObjects = {
@@ -126,27 +130,26 @@ export const makeExportContext = () => {
126
130
  byVal: makeScalarMapStore(),
127
131
  },
128
132
  // TODO: offer, contact, dapp
129
- /** @type {IdTable<number, unknown>} */
133
+ /** @type {IdTable<number, PassableCap>} */
130
134
  unknown: {
131
135
  bySlot: makeScalarMapStore(),
132
136
  byVal: makeScalarMapStore(),
133
137
  },
134
138
  };
135
- /** @type {IdTable<BoardId, unknown>} */
139
+ /** @type {IdTable<BoardId, PassableCap>} */
136
140
  const boardObjects = {
137
141
  bySlot: makeScalarMapStore(),
138
142
  byVal: makeScalarMapStore(),
139
143
  };
140
144
 
141
145
  /**
142
- * Look up the slot in mappings from published data
143
- * else try walletObjects that we have seen.
144
- *
145
- * @throws if not found (a slotToVal function typically
146
- * conjures a new identity)
146
+ * Look up the slot in mappings from published data else try walletObjects
147
+ * that we have seen.
147
148
  *
148
149
  * @param {MixedSlot<typeof walletObjects>} slot
149
150
  * @param {string} _iface
151
+ * @throws if not found (a slotToVal function typically conjures a new
152
+ * identity)
150
153
  */
151
154
  const slotToVal = (slot, _iface) => {
152
155
  if (isDefaultBoardId(slot) && boardObjects.bySlot.has(slot)) {
@@ -161,7 +164,7 @@ export const makeExportContext = () => {
161
164
  let unknownNonce = 0;
162
165
 
163
166
  /**
164
- * @param {unknown} val
167
+ * @param {PassableCap} val
165
168
  * @returns {MixedSlot<typeof walletObjects>}
166
169
  */
167
170
  const valToSlot = val => {
@@ -181,7 +184,7 @@ export const makeExportContext = () => {
181
184
  };
182
185
 
183
186
  /**
184
- * @template V
187
+ * @template {PassableCap} V
185
188
  * @param {string & keyof typeof walletObjects} kind
186
189
  * @param {IdTable<number, V>} table
187
190
  */
@@ -208,14 +211,14 @@ export const makeExportContext = () => {
208
211
  purseEntries: walletObjects.purse.bySlot.entries,
209
212
  /**
210
213
  * @param {BoardId} id
211
- * @param {unknown} val
214
+ * @param {RemotableObject} val
212
215
  */
213
216
  initBoardId: (id, val) => {
214
217
  initSlotVal(boardObjects, id, val);
215
218
  },
216
219
  /**
217
220
  * @param {BoardId} id
218
- * @param {unknown} val
221
+ * @param {RemotableObject} val
219
222
  */
220
223
  ensureBoardId: (id, val) => {
221
224
  if (boardObjects.byVal.has(val)) {
@@ -235,38 +238,38 @@ const defaultMakePresence = iface => {
235
238
  };
236
239
 
237
240
  /**
238
- * Make context for marshalling wallet or board data.
239
- * To be imported into the client, which never exports objects.
241
+ * Make context for marshalling wallet or board data. To be imported into the
242
+ * client, which never exports objects.
240
243
  *
241
- * @param {(iface: string) => unknown} [makePresence]
244
+ * @param {(iface: string) => PassableCap} [makePresence]
242
245
  */
243
246
  export const makeImportContext = (makePresence = defaultMakePresence) => {
244
247
  const walletObjects = {
245
- /** @type {IdTable<number, unknown>} */
248
+ /** @type {IdTable<number, PassableCap>} */
246
249
  purse: {
247
250
  bySlot: makeScalarMapStore(),
248
251
  byVal: makeScalarMapStore(),
249
252
  },
250
- /** @type {IdTable<number, unknown>} */
253
+ /** @type {IdTable<number, PassableCap>} */
251
254
  payment: {
252
255
  bySlot: makeScalarMapStore(),
253
256
  byVal: makeScalarMapStore(),
254
257
  },
255
- /** @type {IdTable<number, unknown>} */
258
+ /** @type {IdTable<number, PassableCap>} */
256
259
  unknown: {
257
260
  bySlot: makeScalarMapStore(),
258
261
  byVal: makeScalarMapStore(),
259
262
  },
260
263
  };
261
- /** @type {IdTable<BoardId, unknown>} */
264
+ /** @type {IdTable<BoardId, PassableCap>} */
262
265
  const boardObjects = {
263
266
  bySlot: makeScalarMapStore(),
264
267
  byVal: makeScalarMapStore(),
265
268
  };
266
269
 
267
270
  /**
268
- * @template Slot
269
- * @template Val
271
+ * @template {Key} Slot
272
+ * @template {PassableCap} Val
270
273
  * @param {IdTable<Slot, Val>} table
271
274
  * @param {Slot} slot
272
275
  * @param {string} iface
@@ -308,7 +311,7 @@ export const makeImportContext = (makePresence = defaultMakePresence) => {
308
311
 
309
312
  const valToSlot = {
310
313
  fromBoard: val => boardObjects.byVal.get(val),
311
- /** @param {unknown} val */
314
+ /** @param {PassableCap} val */
312
315
  fromMyWallet: val => {
313
316
  const kind = findKey(walletObjects, k => walletObjects[k].byVal.has(val));
314
317
  if (kind === undefined) {
@@ -334,14 +337,14 @@ export const makeImportContext = (makePresence = defaultMakePresence) => {
334
337
  return harden({
335
338
  /**
336
339
  * @param {BoardId} id
337
- * @param {unknown} val
340
+ * @param {PassableCap} val
338
341
  */
339
342
  initBoardId: (id, val) => {
340
343
  initSlotVal(boardObjects, id, val);
341
344
  },
342
345
  /**
343
346
  * @param {BoardId} id
344
- * @param {unknown} val
347
+ * @param {PassableCap} val
345
348
  */
346
349
  ensureBoardId: (id, val) => {
347
350
  if (boardObjects.byVal.has(val)) {
@@ -359,13 +362,17 @@ export const makeImportContext = (makePresence = defaultMakePresence) => {
359
362
  /**
360
363
  * @param {string} iface
361
364
  * @param {{
362
- * applyMethod: (target: unknown, method: string | symbol, args: unknown[]) => void,
363
- * applyFunction: (target: unknown, args: unknown[]) => void,
365
+ * applyMethod: (
366
+ * target: unknown,
367
+ * method: string | symbol,
368
+ * args: unknown[],
369
+ * ) => void;
370
+ * applyFunction: (target: unknown, args: unknown[]) => void;
364
371
  * }} handler
365
372
  */
366
373
  const makePresence = (iface, handler) => {
367
374
  let obj;
368
- // eslint-disable-next-line no-new
375
+
369
376
  void new HandledPromise((resolve, reject, resolveWithPresence) => {
370
377
  obj = resolveWithPresence(handler);
371
378
  });
@@ -0,0 +1,52 @@
1
+ export function makeWatchOfferOutcomes(vowTools: VowTools): (watchers: OutcomeWatchers, seat: UserSeat) => Promise<any[]>;
2
+ export function prepareOfferWatcher(baggage: Baggage, vowTools: VowTools): (walletHelper: any, deposit: {
3
+ receive: (payment: Payment) => Promise<Amount>;
4
+ }, offerSpec: OfferSpec, address: string, invitationAmount: import("@agoric/ertp").SetAmount<import("@endo/patterns").Key>, seatRef: UserSeat<unknown>) => import("@endo/exo").GuardedKit<{
5
+ helper: {
6
+ /**
7
+ * @param {Record<string, unknown>} offerStatusUpdates
8
+ */
9
+ updateStatus(offerStatusUpdates: Record<string, unknown>): void;
10
+ /**
11
+ * @param {string} offerId
12
+ * @param {Amount<'set'>} invitationAmount
13
+ * @param {import('./types.js').InvitationMakers} invitationMakers
14
+ * @param {import('./types.js').PublicSubscribers} publicSubscribers
15
+ */
16
+ onNewContinuingOffer(offerId: string, invitationAmount: Amount<"set">, invitationMakers: import("./types.js").InvitationMakers, publicSubscribers: import("./types.js").PublicSubscribers): void;
17
+ /** @param {Passable | ContinuingOfferResult} result */
18
+ publishResult(result: Passable | ContinuingOfferResult): void;
19
+ /**
20
+ * Called when the offer result promise rejects. The other two watchers
21
+ * are waiting for particular values out of Zoe but they settle at the
22
+ * same time and don't need their own error handling.
23
+ *
24
+ * @param {Error} err
25
+ */
26
+ handleError(err: Error): void;
27
+ };
28
+ /** @type {OutcomeWatchers['paymentWatcher']} */
29
+ paymentWatcher: OutcomeWatchers["paymentWatcher"];
30
+ /** @type {OutcomeWatchers['resultWatcher']} */
31
+ resultWatcher: OutcomeWatchers["resultWatcher"];
32
+ /** @type {OutcomeWatchers['numWantsWatcher']} */
33
+ numWantsWatcher: OutcomeWatchers["numWantsWatcher"];
34
+ }>;
35
+ /**
36
+ * <T, [UserSeat]
37
+ */
38
+ export type OfferPromiseWatcher<T extends unknown> = PromiseWatcher<T, [UserSeat]>;
39
+ export type OutcomeWatchers = {
40
+ resultWatcher: OfferPromiseWatcher<Passable>;
41
+ numWantsWatcher: OfferPromiseWatcher<number>;
42
+ paymentWatcher: OfferPromiseWatcher<PaymentPKeywordRecord>;
43
+ };
44
+ export type MakeOfferWatcher = ReturnType<typeof prepareOfferWatcher>;
45
+ export type OfferWatcher = ReturnType<MakeOfferWatcher>;
46
+ import type { VowTools } from '@agoric/vow';
47
+ import type { Baggage } from '@agoric/vat-data';
48
+ import type { OfferSpec } from './offers.js';
49
+ import type { Passable } from '@endo/pass-style';
50
+ import type { ContinuingOfferResult } from './types.js';
51
+ import type { PromiseWatcher } from '@agoric/swingset-liveslots';
52
+ //# sourceMappingURL=offerWatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offerWatcher.d.ts","sourceRoot":"","sources":["offerWatcher.js"],"names":[],"mappings":"AAwEO,iDADK,QAAQ,cAKP,eAAe,QACf,QAAQ,oBAiBpB;AAmCM,6CAHI,OAAO,YACP,QAAQ;aAWO,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC;;;QAgBvD;;WAEG;yCADQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;QAQlC;;;;;WAKG;sCAJQ,MAAM,oBACN,MAAM,CAAC,KAAK,CAAC,oBACb,OAAO,YAAY,EAAE,gBAAgB,qBACrC,OAAO,YAAY,EAAE,iBAAiB;QAkBjD,uDAAuD;8BAA3C,QAAQ,GAAG,qBAAqB;QAoC5C;;;;;;WAMG;yBADQ,KAAK;;IAclB,gDAAgD;oBAArC,eAAe,CAAC,gBAAgB,CAAC;IA+B5C,+CAA+C;mBAApC,eAAe,CAAC,eAAe,CAAC;IA0B3C,iDAAiD;qBAAtC,eAAe,CAAC,iBAAiB,CAAC;GA2BlD;;;;gCA5SkB,CAAC,oBACP,eAAe,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;8BAI7B;IACR,aAAa,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC7C,eAAe,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC7C,cAAc,EAAE,mBAAmB,CAAC,qBAAqB,CAAC,CAAC;CAC5D;+BAsSU,UAAU,CAAC,OAAO,mBAAmB,CAAC;2BACtC,UAAU,CAAC,gBAAgB,CAAC;8BApTV,aAAa;6BADnB,kBAAkB;+BAJhB,aAAa;8BAEd,kBAAkB;2CADL,YAAY;oCAEnB,4BAA4B"}
@@ -0,0 +1,329 @@
1
+ import { E, passStyleOf } from '@endo/far';
2
+
3
+ import { isUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';
4
+ import { prepareExoClassKit, watchPromise } from '@agoric/vat-data';
5
+ import { M } from '@agoric/store';
6
+ import {
7
+ PaymentPKeywordRecordShape,
8
+ SeatShape,
9
+ } from '@agoric/zoe/src/typeGuards.js';
10
+ import { AmountShape } from '@agoric/ertp/src/typeGuards.js';
11
+ import { deeplyFulfilledObject, objectMap } from '@agoric/internal';
12
+
13
+ import { UNPUBLISHED_RESULT } from './offers.js';
14
+
15
+ /**
16
+ * @import {OfferSpec} from './offers.js';
17
+ * @import {ContinuingOfferResult} from './types.js';
18
+ * @import {Passable} from '@endo/pass-style';
19
+ * @import {PromiseWatcher} from '@agoric/swingset-liveslots';
20
+ * @import {Baggage} from '@agoric/vat-data';
21
+ * @import {Vow, VowTools} from '@agoric/vow';
22
+ */
23
+
24
+ /**
25
+ * @template {any} T
26
+ * @typedef {PromiseWatcher<T, [UserSeat]>} OfferPromiseWatcher<T, [UserSeat]
27
+ */
28
+
29
+ /**
30
+ * @typedef {{
31
+ * resultWatcher: OfferPromiseWatcher<Passable>;
32
+ * numWantsWatcher: OfferPromiseWatcher<number>;
33
+ * paymentWatcher: OfferPromiseWatcher<PaymentPKeywordRecord>;
34
+ * }} OutcomeWatchers
35
+ */
36
+
37
+ /** @param {VowTools} vowTools */
38
+ const makeWatchForOfferResult = ({ watch }) => {
39
+ /**
40
+ * @param {OutcomeWatchers} watchers
41
+ * @param {UserSeat} seat
42
+ * @returns {Vow<void>} Vow that resolves when offer result has been processed
43
+ * by the resultWatcher
44
+ */
45
+ const watchForOfferResult = ({ resultWatcher }, seat) => {
46
+ // Offer result may be anything, including a Vow, so watch it durably.
47
+ return watch(E(seat).getOfferResult(), resultWatcher, seat);
48
+ };
49
+ return watchForOfferResult;
50
+ };
51
+
52
+ /**
53
+ * @param {OutcomeWatchers} watchers
54
+ * @param {UserSeat} seat
55
+ */
56
+ const watchForNumWants = ({ numWantsWatcher }, seat) => {
57
+ const p = E(seat).numWantsSatisfied();
58
+ watchPromise(p, numWantsWatcher, seat);
59
+ return p;
60
+ };
61
+
62
+ /**
63
+ * @param {OutcomeWatchers} watchers
64
+ * @param {UserSeat} seat
65
+ */
66
+ const watchForPayout = ({ paymentWatcher }, seat) => {
67
+ const p = E(seat).getPayouts();
68
+ watchPromise(p, paymentWatcher, seat);
69
+ return p;
70
+ };
71
+
72
+ /** @param {VowTools} vowTools */
73
+ export const makeWatchOfferOutcomes = vowTools => {
74
+ const watchForOfferResult = makeWatchForOfferResult(vowTools);
75
+ const { when, allVows } = vowTools;
76
+ /**
77
+ * @param {OutcomeWatchers} watchers
78
+ * @param {UserSeat} seat
79
+ */
80
+ const watchOfferOutcomes = (watchers, seat) => {
81
+ // Use `when` to get a promise from the vow.
82
+ // Unlike `asPromise` this doesn't warn in case of disconnections, which is
83
+ // fine since we actually handle the outcome durably through the watchers.
84
+ // Only the `executeOffer` caller relies on the settlement of this promise,
85
+ // and only in tests.
86
+ return when(
87
+ allVows([
88
+ watchForOfferResult(watchers, seat),
89
+ watchForNumWants(watchers, seat),
90
+ watchForPayout(watchers, seat),
91
+ ]),
92
+ );
93
+ };
94
+ return watchOfferOutcomes;
95
+ };
96
+
97
+ const offerWatcherGuard = harden({
98
+ helper: M.interface('InstanceAdminStorage', {
99
+ updateStatus: M.call(M.any()).returns(),
100
+ onNewContinuingOffer: M.call(
101
+ M.or(M.number(), M.string()),
102
+ AmountShape,
103
+ M.any(),
104
+ )
105
+ .optional(M.record())
106
+ .returns(),
107
+ publishResult: M.call(M.any()).returns(),
108
+ handleError: M.call(M.error()).returns(),
109
+ }),
110
+ paymentWatcher: M.interface('paymentWatcher', {
111
+ onFulfilled: M.call(PaymentPKeywordRecordShape, SeatShape).returns(
112
+ M.promise(),
113
+ ),
114
+ onRejected: M.call(M.any(), SeatShape).returns(),
115
+ }),
116
+ resultWatcher: M.interface('resultWatcher', {
117
+ onFulfilled: M.call(M.any(), SeatShape).returns(),
118
+ onRejected: M.call(M.any(), SeatShape).returns(),
119
+ }),
120
+ numWantsWatcher: M.interface('numWantsWatcher', {
121
+ onFulfilled: M.call(M.number(), SeatShape).returns(),
122
+ onRejected: M.call(M.any(), SeatShape).returns(),
123
+ }),
124
+ });
125
+
126
+ /**
127
+ * @param {Baggage} baggage
128
+ * @param {VowTools} vowTools
129
+ */
130
+ export const prepareOfferWatcher = (baggage, vowTools) => {
131
+ const watchForOfferResult = makeWatchForOfferResult(vowTools);
132
+ return prepareExoClassKit(
133
+ baggage,
134
+ 'OfferWatcher',
135
+ offerWatcherGuard,
136
+ // XXX walletHelper is `any` because the helper facet is too nested to export its type
137
+ /**
138
+ * @param {any} walletHelper
139
+ * @param {{ receive: (payment: Payment) => Promise<Amount> }} deposit
140
+ * @param {OfferSpec} offerSpec
141
+ * @param {string} address
142
+ * @param {Amount<'set'>} invitationAmount
143
+ * @param {UserSeat} seatRef
144
+ */
145
+ (walletHelper, deposit, offerSpec, address, invitationAmount, seatRef) => ({
146
+ walletHelper,
147
+ deposit,
148
+ status: offerSpec,
149
+ address,
150
+ invitationAmount,
151
+ seatRef,
152
+ }),
153
+ {
154
+ helper: {
155
+ /**
156
+ * @param {Record<string, unknown>} offerStatusUpdates
157
+ */
158
+ updateStatus(offerStatusUpdates) {
159
+ const { state } = this;
160
+ state.status = harden({ ...state.status, ...offerStatusUpdates });
161
+
162
+ state.walletHelper.updateStatus(state.status);
163
+ },
164
+ /**
165
+ * @param {string} offerId
166
+ * @param {Amount<'set'>} invitationAmount
167
+ * @param {import('./types.js').InvitationMakers} invitationMakers
168
+ * @param {import('./types.js').PublicSubscribers} publicSubscribers
169
+ */
170
+ onNewContinuingOffer(
171
+ offerId,
172
+ invitationAmount,
173
+ invitationMakers,
174
+ publicSubscribers,
175
+ ) {
176
+ const { state } = this;
177
+
178
+ void state.walletHelper.addContinuingOffer(
179
+ offerId,
180
+ invitationAmount,
181
+ invitationMakers,
182
+ publicSubscribers,
183
+ );
184
+ },
185
+
186
+ /** @param {Passable | ContinuingOfferResult} result */
187
+ publishResult(result) {
188
+ const { state, facets } = this;
189
+
190
+ const passStyle = passStyleOf(result);
191
+ // someday can we get TS to type narrow based on the passStyleOf result match?
192
+ switch (passStyle) {
193
+ case 'bigint':
194
+ case 'boolean':
195
+ case 'null':
196
+ case 'number':
197
+ case 'string':
198
+ case 'symbol':
199
+ case 'undefined':
200
+ facets.helper.updateStatus({ result });
201
+ break;
202
+ case 'copyRecord':
203
+ // @ts-expect-error narrowed by passStyle
204
+ if ('invitationMakers' in result) {
205
+ // save for continuing invitation offer
206
+
207
+ void facets.helper.onNewContinuingOffer(
208
+ String(state.status.id),
209
+ state.invitationAmount,
210
+ // @ts-expect-error narrowed by passStyle
211
+ result.invitationMakers,
212
+ result.publicSubscribers,
213
+ );
214
+ }
215
+ facets.helper.updateStatus({ result: UNPUBLISHED_RESULT });
216
+ break;
217
+ default:
218
+ // drop the result
219
+ facets.helper.updateStatus({ result: UNPUBLISHED_RESULT });
220
+ }
221
+ },
222
+ /**
223
+ * Called when the offer result promise rejects. The other two watchers
224
+ * are waiting for particular values out of Zoe but they settle at the
225
+ * same time and don't need their own error handling.
226
+ *
227
+ * @param {Error} err
228
+ */
229
+ handleError(err) {
230
+ const { facets } = this;
231
+ facets.helper.updateStatus({ error: err.toString() });
232
+ const { seatRef } = this.state;
233
+ void E.when(E(seatRef).hasExited(), hasExited => {
234
+ if (!hasExited) {
235
+ void E(seatRef).tryExit();
236
+ }
237
+ });
238
+ },
239
+ },
240
+
241
+ /** @type {OutcomeWatchers['paymentWatcher']} */
242
+ paymentWatcher: {
243
+ async onFulfilled(payouts) {
244
+ const { state, facets } = this;
245
+
246
+ // This will block until all payouts succeed, but user will be updated
247
+ // since each payout will trigger its corresponding purse notifier.
248
+ const amountPKeywordRecord = objectMap(payouts, paymentRef =>
249
+ E.when(paymentRef, payment => state.deposit.receive(payment)),
250
+ );
251
+ const amounts = await deeplyFulfilledObject(amountPKeywordRecord);
252
+ facets.helper.updateStatus({ payouts: amounts });
253
+ },
254
+ /**
255
+ * If promise disconnected, watch again. Or if there's an Error, handle
256
+ * it.
257
+ *
258
+ * @param {Error
259
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
260
+ * @param {UserSeat} seat
261
+ */
262
+ onRejected(reason, seat) {
263
+ const { facets } = this;
264
+ if (isUpgradeDisconnection(reason)) {
265
+ void watchForPayout(facets, seat);
266
+ } else {
267
+ facets.helper.handleError(reason);
268
+ }
269
+ },
270
+ },
271
+
272
+ /** @type {OutcomeWatchers['resultWatcher']} */
273
+ resultWatcher: {
274
+ onFulfilled(result) {
275
+ const { facets } = this;
276
+ facets.helper.publishResult(result);
277
+ },
278
+ /**
279
+ * If promise disconnected, watch again. Or if there's an Error, handle
280
+ * it.
281
+ *
282
+ * @param {Error
283
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
284
+ * @param {UserSeat} seat
285
+ */
286
+ onRejected(reason, seat) {
287
+ const { facets } = this;
288
+ if (isUpgradeDisconnection(reason)) {
289
+ void watchForOfferResult(facets, seat);
290
+ } else {
291
+ facets.helper.handleError(reason);
292
+ }
293
+ // throw so the vow watcher propagates the rejection
294
+ throw reason;
295
+ },
296
+ },
297
+
298
+ /** @type {OutcomeWatchers['numWantsWatcher']} */
299
+ numWantsWatcher: {
300
+ onFulfilled(numSatisfied) {
301
+ const { facets } = this;
302
+
303
+ facets.helper.updateStatus({ numWantsSatisfied: numSatisfied });
304
+ },
305
+ /**
306
+ * If promise disconnected, watch again.
307
+ *
308
+ * Errors are handled by the paymentWatcher because numWantsSatisfied()
309
+ * and getPayouts() settle the same (they await the same promise and
310
+ * then synchronously return a local value).
311
+ *
312
+ * @param {Error
313
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
314
+ * @param {UserSeat} seat
315
+ */
316
+ onRejected(reason, seat) {
317
+ const { facets } = this;
318
+ if (isUpgradeDisconnection(reason)) {
319
+ void watchForNumWants(facets, seat);
320
+ }
321
+ },
322
+ },
323
+ },
324
+ );
325
+ };
326
+ harden(prepareOfferWatcher);
327
+
328
+ /** @typedef {ReturnType<typeof prepareOfferWatcher>} MakeOfferWatcher */
329
+ /** @typedef {ReturnType<MakeOfferWatcher>} OfferWatcher */