@agoric/smart-wallet 0.5.3 → 0.5.4-calypso-dev-84eb287.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.
@@ -6,7 +6,11 @@ import { DEFAULT_PREFIX } from '@agoric/vats/src/lib-board.js';
6
6
 
7
7
  const { Fail, quote: q } = assert;
8
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
111
113
  *
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
114
+ * `makeMarshal()` is parameterized by the type of slots. Here we use a disjoint
115
+ * union of
116
+ *
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,48 @@
1
+ export function watchOfferOutcomes(watchers: OutcomeWatchers, seat: UserSeat): Promise<any[]>;
2
+ export function prepareOfferWatcher(baggage: import("@agoric/vat-data").Baggage): (walletHelper: any, deposit: any, offerSpec: OfferSpec, address: string, invitationAmount: import("@agoric/ertp/src/types.js").SetAmount<import("@endo/patterns").Key>, seatRef: UserSeat<unknown>) => import("@endo/exo").GuardedKit<{
3
+ helper: {
4
+ /**
5
+ * @param {Record<string, unknown>} offerStatusUpdates
6
+ */
7
+ updateStatus(offerStatusUpdates: Record<string, unknown>): void;
8
+ /**
9
+ * @param {string} offerId
10
+ * @param {Amount<'set'>} invitationAmount
11
+ * @param {import('./types.js').InvitationMakers} invitationMakers
12
+ * @param {import('./types.js').PublicSubscribers} publicSubscribers
13
+ */
14
+ onNewContinuingOffer(offerId: string, invitationAmount: Amount<"set">, invitationMakers: import("./types.js").InvitationMakers, publicSubscribers: import("./types.js").PublicSubscribers): void;
15
+ /** @param {Passable | ContinuingOfferResult} result */
16
+ publishResult(result: Passable | ContinuingOfferResult): void;
17
+ /**
18
+ * Called when the offer result promise rejects. The other two watchers
19
+ * are waiting for particular values out of Zoe but they settle at the
20
+ * same time and don't need their own error handling.
21
+ *
22
+ * @param {Error} err
23
+ */
24
+ handleError(err: Error): void;
25
+ };
26
+ /** @type {OutcomeWatchers['paymentWatcher']} */
27
+ paymentWatcher: OutcomeWatchers["paymentWatcher"];
28
+ /** @type {OutcomeWatchers['resultWatcher']} */
29
+ resultWatcher: OutcomeWatchers["resultWatcher"];
30
+ /** @type {OutcomeWatchers['numWantsWatcher']} */
31
+ numWantsWatcher: OutcomeWatchers["numWantsWatcher"];
32
+ }>;
33
+ /**
34
+ * <T, [UserSeat]
35
+ */
36
+ export type OfferPromiseWatcher<T extends unknown> = PromiseWatcher<T, [UserSeat]>;
37
+ export type OutcomeWatchers = {
38
+ resultWatcher: OfferPromiseWatcher<Passable>;
39
+ numWantsWatcher: OfferPromiseWatcher<number>;
40
+ paymentWatcher: OfferPromiseWatcher<PaymentPKeywordRecord>;
41
+ };
42
+ export type MakeOfferWatcher = ReturnType<typeof prepareOfferWatcher>;
43
+ export type OfferWatcher = ReturnType<MakeOfferWatcher>;
44
+ import type { OfferSpec } from "./offers.js";
45
+ import type { Passable } from '@endo/pass-style';
46
+ import type { ContinuingOfferResult } from "./types.js";
47
+ import type { PromiseWatcher } from '@agoric/swingset-liveslots';
48
+ //# sourceMappingURL=offerWatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offerWatcher.d.ts","sourceRoot":"","sources":["offerWatcher.js"],"names":[],"mappings":"AAwEO,6CAHI,eAAe,QACf,QAAQ,kBAWlB;AAkCM,6CAFI,OAAO,kBAAkB,EAAE,OAAO,iNAoHnB,WAAW;;QA3F7B;;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;IAwB3C,iDAAiD;qBAAtC,eAAe,CAAC,iBAAiB,CAAC;GA2BlD;;;;gCAzRkB,CAAC,oBACP,eAAe,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;8BAI7B;IACZ,aAAiB,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACjD,eAAmB,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACjD,cAAkB,EAAE,mBAAmB,CAAC,qBAAqB,CAAC,CAAC;CAC5D;+BAmRU,UAAU,CAAC,OAAO,mBAAmB,CAAC;2BACtC,UAAU,CAAC,gBAAgB,CAAC;+BApSd,aAAa;8BAEd,kBAAkB;2CADL,YAAY;oCAEnB,4BAA4B"}
@@ -0,0 +1,311 @@
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
+ import { heapVowTools } from '@agoric/vow/vat.js';
13
+
14
+ import { UNPUBLISHED_RESULT } from './offers.js';
15
+
16
+ const { when, allVows } = heapVowTools;
17
+
18
+ /**
19
+ * @import {OfferSpec} from "./offers.js";
20
+ * @import {ContinuingOfferResult} from "./types.js";
21
+ * @import {Passable} from '@endo/pass-style';
22
+ * @import {PromiseWatcher} from '@agoric/swingset-liveslots';
23
+ */
24
+
25
+ /**
26
+ * @template {any} T
27
+ * @typedef {PromiseWatcher<T, [UserSeat]>} OfferPromiseWatcher<T, [UserSeat]
28
+ */
29
+
30
+ /**
31
+ * @typedef {{
32
+ * resultWatcher: OfferPromiseWatcher<Passable>;
33
+ * numWantsWatcher: OfferPromiseWatcher<number>;
34
+ * paymentWatcher: OfferPromiseWatcher<PaymentPKeywordRecord>;
35
+ * }} OutcomeWatchers
36
+ */
37
+
38
+ /**
39
+ * @param {OutcomeWatchers} watchers
40
+ * @param {UserSeat} seat
41
+ */
42
+ const watchForOfferResult = ({ resultWatcher }, seat) => {
43
+ // NOTE: this enables upgrade of the contract, NOT upgrade of the smart-wallet. See #9308
44
+ const p = when(E(seat).getOfferResult());
45
+ watchPromise(p, resultWatcher, seat);
46
+ return p;
47
+ };
48
+
49
+ /**
50
+ * @param {OutcomeWatchers} watchers
51
+ * @param {UserSeat} seat
52
+ */
53
+ const watchForNumWants = ({ numWantsWatcher }, seat) => {
54
+ const p = E(seat).numWantsSatisfied();
55
+ watchPromise(p, numWantsWatcher, seat);
56
+ return p;
57
+ };
58
+
59
+ /**
60
+ * @param {OutcomeWatchers} watchers
61
+ * @param {UserSeat} seat
62
+ */
63
+ const watchForPayout = ({ paymentWatcher }, seat) => {
64
+ const p = E(seat).getPayouts();
65
+ watchPromise(p, paymentWatcher, seat);
66
+ return p;
67
+ };
68
+
69
+ /**
70
+ * @param {OutcomeWatchers} watchers
71
+ * @param {UserSeat} seat
72
+ */
73
+ export const watchOfferOutcomes = (watchers, seat) => {
74
+ // NOTE: this enables upgrade of the contract, NOT upgrade of the smart-wallet. See #9308
75
+ return when(
76
+ allVows([
77
+ watchForOfferResult(watchers, seat),
78
+ watchForNumWants(watchers, seat),
79
+ watchForPayout(watchers, seat),
80
+ ]),
81
+ );
82
+ };
83
+
84
+ const offerWatcherGuard = harden({
85
+ helper: M.interface('InstanceAdminStorage', {
86
+ updateStatus: M.call(M.any()).returns(),
87
+ onNewContinuingOffer: M.call(
88
+ M.or(M.number(), M.string()),
89
+ AmountShape,
90
+ M.any(),
91
+ )
92
+ .optional(M.record())
93
+ .returns(),
94
+ publishResult: M.call(M.any()).returns(),
95
+ handleError: M.call(M.error()).returns(),
96
+ }),
97
+ paymentWatcher: M.interface('paymentWatcher', {
98
+ onFulfilled: M.call(PaymentPKeywordRecordShape, SeatShape).returns(
99
+ M.promise(),
100
+ ),
101
+ onRejected: M.call(M.any(), SeatShape).returns(),
102
+ }),
103
+ resultWatcher: M.interface('resultWatcher', {
104
+ onFulfilled: M.call(M.any(), SeatShape).returns(),
105
+ onRejected: M.call(M.any(), SeatShape).returns(),
106
+ }),
107
+ numWantsWatcher: M.interface('numWantsWatcher', {
108
+ onFulfilled: M.call(M.number(), SeatShape).returns(),
109
+ onRejected: M.call(M.any(), SeatShape).returns(),
110
+ }),
111
+ });
112
+
113
+ /**
114
+ * @param {import('@agoric/vat-data').Baggage} baggage
115
+ */
116
+ export const prepareOfferWatcher = baggage => {
117
+ return prepareExoClassKit(
118
+ baggage,
119
+ 'OfferWatcher',
120
+ offerWatcherGuard,
121
+ /**
122
+ * @param {any} walletHelper
123
+ * @param {any} deposit
124
+ * @param {OfferSpec} offerSpec
125
+ * @param {string} address
126
+ * @param {Amount<'set'>} invitationAmount
127
+ * @param {UserSeat} seatRef
128
+ */
129
+ (walletHelper, deposit, offerSpec, address, invitationAmount, seatRef) => ({
130
+ walletHelper,
131
+ deposit,
132
+ status: offerSpec,
133
+ address,
134
+ invitationAmount,
135
+ seatRef,
136
+ }),
137
+ {
138
+ helper: {
139
+ /**
140
+ * @param {Record<string, unknown>} offerStatusUpdates
141
+ */
142
+ updateStatus(offerStatusUpdates) {
143
+ const { state } = this;
144
+ state.status = harden({ ...state.status, ...offerStatusUpdates });
145
+
146
+ state.walletHelper.updateStatus(state.status);
147
+ },
148
+ /**
149
+ * @param {string} offerId
150
+ * @param {Amount<'set'>} invitationAmount
151
+ * @param {import('./types.js').InvitationMakers} invitationMakers
152
+ * @param {import('./types.js').PublicSubscribers} publicSubscribers
153
+ */
154
+ onNewContinuingOffer(
155
+ offerId,
156
+ invitationAmount,
157
+ invitationMakers,
158
+ publicSubscribers,
159
+ ) {
160
+ const { state } = this;
161
+
162
+ void state.walletHelper.addContinuingOffer(
163
+ offerId,
164
+ invitationAmount,
165
+ invitationMakers,
166
+ publicSubscribers,
167
+ );
168
+ },
169
+
170
+ /** @param {Passable | ContinuingOfferResult} result */
171
+ publishResult(result) {
172
+ const { state, facets } = this;
173
+
174
+ const passStyle = passStyleOf(result);
175
+ // someday can we get TS to type narrow based on the passStyleOf result match?
176
+ switch (passStyle) {
177
+ case 'bigint':
178
+ case 'boolean':
179
+ case 'null':
180
+ case 'number':
181
+ case 'string':
182
+ case 'symbol':
183
+ case 'undefined':
184
+ facets.helper.updateStatus({ result });
185
+ break;
186
+ case 'copyRecord':
187
+ // @ts-expect-error narrowed by passStyle
188
+ if ('invitationMakers' in result) {
189
+ // save for continuing invitation offer
190
+
191
+ void facets.helper.onNewContinuingOffer(
192
+ String(state.status.id),
193
+ state.invitationAmount,
194
+ // @ts-expect-error narrowed by passStyle
195
+ result.invitationMakers,
196
+ result.publicSubscribers,
197
+ );
198
+ }
199
+ facets.helper.updateStatus({ result: UNPUBLISHED_RESULT });
200
+ break;
201
+ default:
202
+ // drop the result
203
+ facets.helper.updateStatus({ result: UNPUBLISHED_RESULT });
204
+ }
205
+ },
206
+ /**
207
+ * Called when the offer result promise rejects. The other two watchers
208
+ * are waiting for particular values out of Zoe but they settle at the
209
+ * same time and don't need their own error handling.
210
+ *
211
+ * @param {Error} err
212
+ */
213
+ handleError(err) {
214
+ const { facets } = this;
215
+ facets.helper.updateStatus({ error: err.toString() });
216
+ const { seatRef } = this.state;
217
+ void E.when(E(seatRef).hasExited(), hasExited => {
218
+ if (!hasExited) {
219
+ void E(seatRef).tryExit();
220
+ }
221
+ });
222
+ },
223
+ },
224
+
225
+ /** @type {OutcomeWatchers['paymentWatcher']} */
226
+ paymentWatcher: {
227
+ async onFulfilled(payouts) {
228
+ const { state, facets } = this;
229
+
230
+ // This will block until all payouts succeed, but user will be updated
231
+ // since each payout will trigger its corresponding purse notifier.
232
+ const amountPKeywordRecord = objectMap(payouts, paymentRef =>
233
+ E.when(paymentRef, payment => state.deposit.receive(payment)),
234
+ );
235
+ const amounts = await deeplyFulfilledObject(amountPKeywordRecord);
236
+ facets.helper.updateStatus({ payouts: amounts });
237
+ },
238
+ /**
239
+ * If promise disconnected, watch again. Or if there's an Error, handle
240
+ * it.
241
+ *
242
+ * @param {Error
243
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
244
+ * @param {UserSeat} seat
245
+ */
246
+ onRejected(reason, seat) {
247
+ const { facets } = this;
248
+ if (isUpgradeDisconnection(reason)) {
249
+ void watchForPayout(facets, seat);
250
+ } else {
251
+ facets.helper.handleError(reason);
252
+ }
253
+ },
254
+ },
255
+
256
+ /** @type {OutcomeWatchers['resultWatcher']} */
257
+ resultWatcher: {
258
+ onFulfilled(result) {
259
+ const { facets } = this;
260
+ facets.helper.publishResult(result);
261
+ },
262
+ /**
263
+ * If promise disconnected, watch again. Or if there's an Error, handle
264
+ * it.
265
+ *
266
+ * @param {Error
267
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
268
+ * @param {UserSeat} seat
269
+ */
270
+ onRejected(reason, seat) {
271
+ const { facets } = this;
272
+ if (isUpgradeDisconnection(reason)) {
273
+ void watchForOfferResult(facets, seat);
274
+ } else {
275
+ facets.helper.handleError(reason);
276
+ }
277
+ },
278
+ },
279
+
280
+ /** @type {OutcomeWatchers['numWantsWatcher']} */
281
+ numWantsWatcher: {
282
+ onFulfilled(numSatisfied) {
283
+ const { facets } = this;
284
+
285
+ facets.helper.updateStatus({ numWantsSatisfied: numSatisfied });
286
+ },
287
+ /**
288
+ * If promise disconnected, watch again.
289
+ *
290
+ * Errors are handled by the paymentWatcher because numWantsSatisfied()
291
+ * and getPayouts() settle the same (they await the same promise and
292
+ * then synchronously return a local value).
293
+ *
294
+ * @param {Error
295
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
296
+ * @param {UserSeat} seat
297
+ */
298
+ onRejected(reason, seat) {
299
+ const { facets } = this;
300
+ if (isUpgradeDisconnection(reason)) {
301
+ void watchForNumWants(facets, seat);
302
+ }
303
+ },
304
+ },
305
+ },
306
+ );
307
+ };
308
+ harden(prepareOfferWatcher);
309
+
310
+ /** @typedef {ReturnType<typeof prepareOfferWatcher>} MakeOfferWatcher */
311
+ /** @typedef {ReturnType<MakeOfferWatcher>} OfferWatcher */