@agoric/smart-wallet 0.5.4-other-dev-8f8782b.0 → 0.5.4-other-dev-fbe72e7.0.fbe72e7

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 (40) hide show
  1. package/package.json +43 -29
  2. package/src/index.d.ts +2 -0
  3. package/src/index.d.ts.map +1 -0
  4. package/src/index.js +6 -0
  5. package/src/invitations.d.ts +14 -10
  6. package/src/invitations.d.ts.map +1 -1
  7. package/src/invitations.js +39 -33
  8. package/src/marshal-contexts.d.ts +45 -41
  9. package/src/marshal-contexts.d.ts.map +1 -1
  10. package/src/marshal-contexts.js +69 -61
  11. package/src/offerWatcher.d.ts +54 -0
  12. package/src/offerWatcher.d.ts.map +1 -0
  13. package/src/offerWatcher.js +343 -0
  14. package/src/offers.d.ts +60 -30
  15. package/src/offers.d.ts.map +1 -1
  16. package/src/offers.js +38 -182
  17. package/src/proposals/upgrade-wallet-factory2-proposal.js +61 -0
  18. package/src/proposals/upgrade-walletFactory-proposal.js +46 -23
  19. package/src/smartWallet.d.ts +132 -68
  20. package/src/smartWallet.d.ts.map +1 -1
  21. package/src/smartWallet.js +718 -217
  22. package/src/typeGuards.d.ts +1 -1
  23. package/src/typeGuards.js +29 -1
  24. package/src/types-index.d.ts +2 -0
  25. package/src/types-index.js +2 -0
  26. package/src/types.d.ts +36 -41
  27. package/src/types.d.ts.map +1 -0
  28. package/src/types.ts +90 -0
  29. package/src/utils.d.ts +17 -14
  30. package/src/utils.d.ts.map +1 -1
  31. package/src/utils.js +19 -6
  32. package/src/walletFactory.d.ts +24 -78
  33. package/src/walletFactory.d.ts.map +1 -1
  34. package/src/walletFactory.js +64 -37
  35. package/CHANGELOG.md +0 -180
  36. package/src/payments.d.ts +0 -20
  37. package/src/payments.d.ts.map +0 -1
  38. package/src/payments.js +0 -89
  39. package/src/proposals/upgrade-walletFactory-proposal.d.ts +0 -17
  40. package/src/proposals/upgrade-walletFactory-proposal.d.ts.map +0 -1
@@ -0,0 +1,343 @@
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
+ * @import {PaymentPKeywordRecord, Proposal, UserSeat, ZoeService} from '@agoric/zoe';
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
+ /** @param {VowTools} vowTools */
39
+ const makeWatchForOfferResult = ({ watch }) => {
40
+ /**
41
+ * @param {OutcomeWatchers} watchers
42
+ * @param {UserSeat} seat
43
+ * @returns {Vow<void>} Vow that resolves when offer result has been processed
44
+ * by the resultWatcher
45
+ */
46
+ const watchForOfferResult = ({ resultWatcher }, seat) => {
47
+ // Offer result may be anything, including a Vow, so watch it durably.
48
+ return watch(E(seat).getOfferResult(), resultWatcher, seat);
49
+ };
50
+ return watchForOfferResult;
51
+ };
52
+
53
+ /**
54
+ * @param {OutcomeWatchers} watchers
55
+ * @param {UserSeat} seat
56
+ */
57
+ const watchForNumWants = ({ numWantsWatcher }, seat) => {
58
+ const p = E(seat).numWantsSatisfied();
59
+ watchPromise(p, numWantsWatcher, seat);
60
+ return p;
61
+ };
62
+
63
+ /**
64
+ * @param {OutcomeWatchers} watchers
65
+ * @param {UserSeat} seat
66
+ */
67
+ const watchForPayout = ({ paymentWatcher }, seat) => {
68
+ const p = E(seat).getPayouts();
69
+ watchPromise(p, paymentWatcher, seat);
70
+ return p;
71
+ };
72
+
73
+ /** @param {VowTools} vowTools */
74
+ export const makeWatchOfferOutcomes = vowTools => {
75
+ const watchForOfferResult = makeWatchForOfferResult(vowTools);
76
+ const { when, allVows } = vowTools;
77
+ /**
78
+ * @param {OutcomeWatchers} watchers
79
+ * @param {UserSeat} seat
80
+ */
81
+ const watchOfferOutcomes = (watchers, seat) => {
82
+ // Use `when` to get a promise from the vow.
83
+ // Unlike `asPromise` this doesn't warn in case of disconnections, which is
84
+ // fine since we actually handle the outcome durably through the watchers.
85
+ // Only the `executeOffer` caller relies on the settlement of this promise,
86
+ // and only in tests.
87
+ return when(
88
+ allVows([
89
+ watchForOfferResult(watchers, seat),
90
+ watchForNumWants(watchers, seat),
91
+ watchForPayout(watchers, seat),
92
+ ]),
93
+ );
94
+ };
95
+ return watchOfferOutcomes;
96
+ };
97
+
98
+ const offerWatcherGuard = harden({
99
+ helper: M.interface('InstanceAdminStorage', {
100
+ updateStatus: M.call(M.any()).returns(),
101
+ onNewContinuingOffer: M.call(
102
+ M.or(M.number(), M.string()),
103
+ AmountShape,
104
+ M.any(),
105
+ )
106
+ .optional(M.record())
107
+ .returns(),
108
+ publishResult: M.call(M.any()).returns(),
109
+ handleError: M.call(M.error()).returns(),
110
+ }),
111
+ paymentWatcher: M.interface('paymentWatcher', {
112
+ onFulfilled: M.call(PaymentPKeywordRecordShape, SeatShape).returns(
113
+ M.promise(),
114
+ ),
115
+ onRejected: M.call(M.any(), SeatShape).returns(),
116
+ }),
117
+ resultWatcher: M.interface('resultWatcher', {
118
+ onFulfilled: M.call(M.any(), SeatShape).returns(),
119
+ onRejected: M.call(M.any(), SeatShape).returns(),
120
+ }),
121
+ numWantsWatcher: M.interface('numWantsWatcher', {
122
+ onFulfilled: M.call(M.number(), SeatShape).returns(),
123
+ onRejected: M.call(M.any(), SeatShape).returns(),
124
+ }),
125
+ });
126
+
127
+ /**
128
+ * @param {Baggage} baggage
129
+ * @param {VowTools} vowTools
130
+ */
131
+ export const prepareOfferWatcher = (baggage, vowTools) => {
132
+ const watchForOfferResult = makeWatchForOfferResult(vowTools);
133
+ return prepareExoClassKit(
134
+ baggage,
135
+ 'OfferWatcher',
136
+ offerWatcherGuard,
137
+ // XXX walletHelper is `any` because the helper facet is too nested to export its type
138
+ /**
139
+ * @param {any} walletHelper
140
+ * @param {{ receive: (payment: Payment) => Promise<Amount> }} deposit
141
+ * @param {OfferSpec} offerSpec
142
+ * @param {string} address
143
+ * @param {Amount<'set'>} invitationAmount
144
+ * @param {UserSeat} seatRef
145
+ */
146
+ (walletHelper, deposit, offerSpec, address, invitationAmount, seatRef) => ({
147
+ walletHelper,
148
+ deposit,
149
+ status: offerSpec,
150
+ address,
151
+ invitationAmount,
152
+ seatRef,
153
+ }),
154
+ {
155
+ helper: {
156
+ /**
157
+ * @param {Record<string, unknown>} offerStatusUpdates
158
+ */
159
+ updateStatus(offerStatusUpdates) {
160
+ const { state } = this;
161
+ state.status = harden({ ...state.status, ...offerStatusUpdates });
162
+
163
+ state.walletHelper.updateStatus(state.status);
164
+ },
165
+ /**
166
+ * @param {string} offerId
167
+ * @param {Amount<'set'>} invitationAmount
168
+ * @param {import('./types.js').InvitationMakers} invitationMakers
169
+ * @param {import('./types.js').PublicSubscribers} publicSubscribers
170
+ */
171
+ onNewContinuingOffer(
172
+ offerId,
173
+ invitationAmount,
174
+ invitationMakers,
175
+ publicSubscribers,
176
+ ) {
177
+ const { state } = this;
178
+
179
+ void state.walletHelper.addContinuingOffer(
180
+ offerId,
181
+ invitationAmount,
182
+ invitationMakers,
183
+ publicSubscribers,
184
+ );
185
+ },
186
+
187
+ /** @param {Passable | ContinuingOfferResult} result */
188
+ publishResult(result) {
189
+ const { state, facets } = this;
190
+
191
+ const passStyle = passStyleOf(result);
192
+ // someday can we get TS to type narrow based on the passStyleOf result match?
193
+ switch (passStyle) {
194
+ case 'bigint':
195
+ case 'boolean':
196
+ case 'null':
197
+ case 'number':
198
+ case 'string':
199
+ case 'symbol':
200
+ case 'undefined':
201
+ facets.helper.updateStatus({ result });
202
+ break;
203
+ case 'copyRecord':
204
+ // @ts-expect-error narrowed by passStyle
205
+ if ('invitationMakers' in result) {
206
+ // save for continuing invitation offer
207
+
208
+ void facets.helper.onNewContinuingOffer(
209
+ String(state.status.id),
210
+ state.invitationAmount,
211
+ // @ts-expect-error narrowed by passStyle
212
+ result.invitationMakers,
213
+ result.publicSubscribers,
214
+ );
215
+ }
216
+ facets.helper.updateStatus({ result: UNPUBLISHED_RESULT });
217
+ break;
218
+ default:
219
+ // drop the result
220
+ facets.helper.updateStatus({ result: UNPUBLISHED_RESULT });
221
+ }
222
+ },
223
+ /**
224
+ * Called when the offer result promise rejects. The other two watchers
225
+ * are waiting for particular values out of Zoe but they settle at the
226
+ * same time and don't need their own error handling.
227
+ *
228
+ * @param {Error} err
229
+ */
230
+ handleError(err) {
231
+ const { facets } = this;
232
+ facets.helper.updateStatus({ error: err.toString() });
233
+ const { seatRef } = this.state;
234
+ void E.when(E(seatRef).hasExited(), hasExited => {
235
+ if (!hasExited) {
236
+ void E(seatRef).tryExit();
237
+ }
238
+ });
239
+ },
240
+ },
241
+
242
+ /** @type {OutcomeWatchers['paymentWatcher']} */
243
+ paymentWatcher: {
244
+ async onFulfilled(payouts) {
245
+ const { state, facets } = this;
246
+
247
+ // This will block until all payouts succeed, but user will be updated
248
+ // since each payout will trigger its corresponding purse notifier.
249
+ const amountPKeywordRecord = objectMap(payouts, paymentRef =>
250
+ E.when(paymentRef, payment => state.deposit.receive(payment)),
251
+ );
252
+ const amounts = await deeplyFulfilledObject(amountPKeywordRecord);
253
+ facets.helper.updateStatus({ payouts: amounts });
254
+ },
255
+ /**
256
+ * If promise disconnected, watch again. Or if there's an Error, handle
257
+ * it.
258
+ *
259
+ * @param {Error
260
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
261
+ * @param {UserSeat} seat
262
+ */
263
+ onRejected(reason, seat) {
264
+ const { facets } = this;
265
+ if (isUpgradeDisconnection(reason)) {
266
+ void watchForPayout(facets, seat);
267
+ } else {
268
+ facets.helper.handleError(reason);
269
+ }
270
+ },
271
+ },
272
+
273
+ /** @type {OutcomeWatchers['resultWatcher']} */
274
+ resultWatcher: {
275
+ onFulfilled(result) {
276
+ const { facets } = this;
277
+ const { walletHelper } = this.state;
278
+ const { saveResult } = this.state.status;
279
+ if (saveResult) {
280
+ // Note: result is passable and storable since it was received from another vat
281
+ const name = walletHelper.saveEntry(saveResult, result);
282
+ facets.helper.updateStatus({
283
+ result: {
284
+ name,
285
+ passStyle: passStyleOf(result),
286
+ },
287
+ });
288
+ } else {
289
+ facets.helper.publishResult(result);
290
+ }
291
+ },
292
+ /**
293
+ * If promise disconnected, watch again. Or if there's an Error, handle
294
+ * it.
295
+ *
296
+ * @param {Error
297
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
298
+ * @param {UserSeat} seat
299
+ */
300
+ onRejected(reason, seat) {
301
+ const { facets } = this;
302
+ if (isUpgradeDisconnection(reason)) {
303
+ void watchForOfferResult(facets, seat);
304
+ } else {
305
+ facets.helper.handleError(reason);
306
+ }
307
+ // throw so the vow watcher propagates the rejection
308
+ throw reason;
309
+ },
310
+ },
311
+
312
+ /** @type {OutcomeWatchers['numWantsWatcher']} */
313
+ numWantsWatcher: {
314
+ onFulfilled(numSatisfied) {
315
+ const { facets } = this;
316
+
317
+ facets.helper.updateStatus({ numWantsSatisfied: numSatisfied });
318
+ },
319
+ /**
320
+ * If promise disconnected, watch again.
321
+ *
322
+ * Errors are handled by the paymentWatcher because numWantsSatisfied()
323
+ * and getPayouts() settle the same (they await the same promise and
324
+ * then synchronously return a local value).
325
+ *
326
+ * @param {Error
327
+ * | import('@agoric/internal/src/upgrade-api.js').UpgradeDisconnection} reason
328
+ * @param {UserSeat} seat
329
+ */
330
+ onRejected(reason, seat) {
331
+ const { facets } = this;
332
+ if (isUpgradeDisconnection(reason)) {
333
+ void watchForNumWants(facets, seat);
334
+ }
335
+ },
336
+ },
337
+ },
338
+ );
339
+ };
340
+ harden(prepareOfferWatcher);
341
+
342
+ /** @typedef {ReturnType<typeof prepareOfferWatcher>} MakeOfferWatcher */
343
+ /** @typedef {ReturnType<MakeOfferWatcher>} OfferWatcher */
package/src/offers.d.ts CHANGED
@@ -1,51 +1,81 @@
1
+ /**
2
+ * @import {Proposal} from '@agoric/zoe';
3
+ * @import {Passable} from '@endo/pass-style';
4
+ */
1
5
  /**
2
6
  * @typedef {number | string} OfferId
3
7
  */
8
+ /**
9
+ * @typedef {object} ResultPlan
10
+ * @property {string} name by which to save the item
11
+ * @property {boolean} [overwrite=false] whether to overwrite an existing item.
12
+ * If false and there is a conflict, the contract will autogen a similar
13
+ * name.
14
+ */
4
15
  /**
5
16
  * @typedef {{
6
- * id: OfferId,
7
- * invitationSpec: import('./invitations').InvitationSpec,
8
- * proposal: Proposal,
9
- * offerArgs?: unknown
17
+ * targetName: string;
18
+ * method: string;
19
+ * args: Passable[];
20
+ * saveResult?: ResultPlan;
21
+ * id?: number | string;
22
+ * }} InvokeEntryMessage
23
+ */
24
+ /**
25
+ * @typedef {{
26
+ * id: OfferId;
27
+ * invitationSpec: import('./invitations.js').InvitationSpec;
28
+ * proposal: Proposal;
29
+ * offerArgs?: any;
30
+ * saveResult?: ResultPlan;
10
31
  * }} OfferSpec
32
+ * If `saveResult` is provided, the result of the invocation will be saved to
33
+ * the specified location. Otherwise it will be published directly to vstorage
34
+ * (or 'UNPUBLISHED' if it cannot be).
11
35
  */
12
36
  /** Value for "result" field when the result can't be published */
13
37
  export const UNPUBLISHED_RESULT: "UNPUBLISHED";
14
- export function makeOfferExecutor({ zoe, depositFacet, invitationIssuer, powers, onStatusChange, onNewContinuingOffer, }: {
15
- zoe: ERef<ZoeService>;
16
- depositFacet: {
17
- receive: (payment: any) => Promise<Amount>;
18
- };
19
- invitationIssuer: ERef<Issuer<'set'>>;
20
- powers: {
21
- logger: Pick<Console, 'info' | 'error'>;
22
- invitationFromSpec: (spec: import('./invitations').InvitationSpec) => ERef<Invitation>;
23
- purseForBrand: (brand: Brand) => Promise<import('./types').RemotePurse>;
24
- };
25
- onStatusChange: (status: OfferStatus) => void;
26
- onNewContinuingOffer: (offerId: string, invitationAmount: Amount<'set'>, invitationMakers: import('./types').RemoteInvitationMakers, publicSubscribers: import('./types').PublicSubscribers | import('@agoric/zoe/src/contractSupport').TopicsRecord) => Promise<void>;
27
- }): {
38
+ export type OfferId = number | string;
39
+ export type ResultPlan = {
40
+ /**
41
+ * by which to save the item
42
+ */
43
+ name: string;
28
44
  /**
29
- * Take an offer description provided in capData, augment it with payments and call zoe.offer()
30
- *
31
- * @param {OfferSpec} offerSpec
32
- * @param {(seatRef: UserSeat) => void} onSeatCreated
33
- * @returns {Promise<void>} when the offer has been sent to Zoe; payouts go into this wallet's purses
34
- * @throws if any parts of the offer are determined to be invalid before calling Zoe's `offer()`
45
+ * whether to overwrite an existing item.
46
+ * If false and there is a conflict, the contract will autogen a similar
47
+ * name.
35
48
  */
36
- executeOffer(offerSpec: OfferSpec, onSeatCreated: (seatRef: UserSeat) => void): Promise<void>;
49
+ overwrite?: boolean | undefined;
37
50
  };
38
- export type OfferId = number | string;
51
+ export type InvokeEntryMessage = {
52
+ targetName: string;
53
+ method: string;
54
+ args: Passable[];
55
+ saveResult?: ResultPlan;
56
+ id?: number | string;
57
+ };
58
+ /**
59
+ * If `saveResult` is provided, the result of the invocation will be saved to
60
+ * the specified location. Otherwise it will be published directly to vstorage
61
+ * (or 'UNPUBLISHED' if it cannot be).
62
+ */
39
63
  export type OfferSpec = {
40
64
  id: OfferId;
41
- invitationSpec: import('./invitations').InvitationSpec;
65
+ invitationSpec: import("./invitations.js").InvitationSpec;
42
66
  proposal: Proposal;
43
- offerArgs?: unknown;
67
+ offerArgs?: any;
68
+ saveResult?: ResultPlan;
44
69
  };
45
- export type OfferStatus = import('./offers.js').OfferSpec & {
70
+ export type OfferStatus = OfferSpec & {
46
71
  error?: string;
47
72
  numWantsSatisfied?: number;
48
- result?: unknown | typeof UNPUBLISHED_RESULT;
73
+ result?: unknown | typeof UNPUBLISHED_RESULT | {
74
+ name: string;
75
+ passStyle: string;
76
+ };
49
77
  payouts?: AmountKeywordRecord;
50
78
  };
79
+ import type { Passable } from '@endo/pass-style';
80
+ import type { Proposal } from '@agoric/zoe';
51
81
  //# sourceMappingURL=offers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"offers.d.ts","sourceRoot":"","sources":["offers.js"],"names":[],"mappings":"AAIA;;GAEG;AAEH;;;;;;;GAOG;AAEH,kEAAkE;AAClE,+CAAgD;AAwBzC;SAVI,KAAK,UAAU,CAAC;;mCACW,QAAQ,MAAM,CAAC;;sBAC1C,KAAK,OAAO,KAAK,CAAC,CAAC;;gBAEnB,KAAK,OAAO,EAAE,MAAM,GAAE,OAAO,CAAC;mCACvB,OAAO,eAAe,EAAE,cAAc,KAAK,KAAK,UAAU,CAAC;+BAC1D,KAAK,KAAK,QAAQ,OAAO,SAAS,EAAE,WAAW,CAAC;;6BAC/C,WAAW,KAAK,IAAI;oCACnB,MAAM,oBAAoB,OAAO,KAAK,CAAC,oBAAoB,OAAO,SAAS,EAAE,sBAAsB,qBAAqB,OAAO,SAAS,EAAE,iBAAiB,GAAG,OAAO,iCAAiC,EAAE,YAAY,KAAM,QAAQ,IAAI,CAAC;;IAaxP;;;;;;;OAOG;4BAJQ,SAAS,2BACC,QAAQ,KAAK,IAAI,GACzB,QAAQ,IAAI,CAAC;EA0I7B;sBA/LY,MAAM,GAAG,MAAM;wBAIf;IACZ,EAAM,EAAE,OAAO,CAAC;IAChB,cAAkB,EAAE,OAAO,eAAe,EAAE,cAAc,CAAC;IAC3D,QAAY,EAAE,QAAQ,CAAC;IACvB,SAAa,CAAC,EAAE,OAAO,CAAA;CACpB;0BAOS,OAAO,aAAa,EAAE,SAAS,GAAG;IAC9C,KAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,MAAQ,CAAC,EAAE,OAAO,GAAG,yBAAyB,CAAC;IAC/C,OAAS,CAAC,EAAE,mBAAmB,CAAC;CAC7B"}
1
+ {"version":3,"file":"offers.d.ts","sourceRoot":"","sources":["offers.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH;;;;;;GAMG;AAEH;;;;;;;;GAQG;AAEH;;;;;;;;;;;GAWG;AAEH,kEAAkE;AAClE,iCAAkC,aAAa,CAAC;sBAnCnC,MAAM,GAAG,MAAM;;;;;UAKd,MAAM;;;;;;;;iCAOP;IACR,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB;;;;;;wBAIS;IACR,EAAE,EAAE,OAAO,CAAC;IACZ,cAAc,EAAE,OAAO,kBAAkB,EAAE,cAAc,CAAC;IAC1D,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,CAAC,EAAE,GAAG,CAAC;IAChB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;0BAUS,SAAS,GAAG;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EACH,OAAO,GACP,OAAO,kBAAkB,GACzB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B;8BAlDuB,kBAAkB;8BADlB,aAAa"}