@agoric/fast-usdc 0.1.1-other-dev-3eb1a1d.0 → 0.1.1-upgrade-19-dev-c605745.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 (40) hide show
  1. package/README.md +43 -0
  2. package/package.json +29 -25
  3. package/src/add-operators.core.js +63 -0
  4. package/src/cli/bridge-action.js +40 -0
  5. package/src/cli/cli.js +46 -153
  6. package/src/cli/config-commands.js +108 -0
  7. package/src/cli/config.js +15 -9
  8. package/src/cli/lp-commands.js +160 -0
  9. package/src/cli/operator-commands.js +146 -0
  10. package/src/cli/transfer.js +63 -13
  11. package/src/{util → cli/util}/agoric.js +1 -1
  12. package/src/cli/util/bank.js +12 -0
  13. package/src/{util → cli/util}/cctp.js +1 -1
  14. package/src/{util → cli/util}/file.js +1 -1
  15. package/src/clientSupport.js +98 -0
  16. package/src/constants.js +25 -2
  17. package/src/distribute-fees.core.js +93 -0
  18. package/src/exos/advancer.js +220 -106
  19. package/src/exos/liquidity-pool.js +130 -81
  20. package/src/exos/operator-kit.js +16 -12
  21. package/src/exos/settler.js +360 -64
  22. package/src/exos/status-manager.js +316 -65
  23. package/src/exos/transaction-feed.js +121 -42
  24. package/src/fast-usdc-policy.core.js +65 -0
  25. package/src/fast-usdc.contract.js +165 -84
  26. package/src/fast-usdc.flows.js +10 -0
  27. package/src/main.js +1 -0
  28. package/src/pool-share-math.js +55 -9
  29. package/src/{fast-usdc.start.js → start-fast-usdc.core.js} +48 -86
  30. package/src/type-guards.js +75 -24
  31. package/src/types.ts +89 -14
  32. package/src/utils/chain-policies.js +140 -0
  33. package/src/utils/core-eval.js +73 -0
  34. package/src/utils/deploy-config.js +127 -0
  35. package/src/utils/fees.js +3 -4
  36. package/tools/cli-tools.ts +9 -0
  37. package/tools/mock-io.ts +14 -0
  38. package/src/exos/README.md +0 -26
  39. package/src/utils/address.js +0 -71
  40. /package/src/{util → cli/util}/noble.js +0 -0
@@ -0,0 +1,93 @@
1
+ /** @file core eval module to collect fees. */
2
+ import { AmountMath } from '@agoric/ertp';
3
+ import { floorMultiplyBy } from '@agoric/zoe/src/contractSupport/index.js';
4
+ import { E } from '@endo/far';
5
+ import { makeTracer } from '@agoric/internal';
6
+ import { fromExternalConfig } from './utils/config-marshal.js';
7
+
8
+ /**
9
+ * @import {DepositFacet} from '@agoric/ertp';
10
+ * @import {FastUSDCCorePowers} from '@agoric/fast-usdc/src/start-fast-usdc.core.js';
11
+ * @import {CopyRecord} from '@endo/pass-style'
12
+ * @import {BootstrapManifestPermit} from '@agoric/vats/src/core/lib-boot.js'
13
+ * @import {LegibleCapData} from './utils/config-marshal.js'
14
+ */
15
+
16
+ /**
17
+ * @typedef {{ destinationAddress: string } &
18
+ * ({ feePortion: Ratio} | {fixedFees: Amount<'nat'>}) &
19
+ * CopyRecord
20
+ * } FeeDistributionTerms
21
+ */
22
+
23
+ const kwUSDC = 'USDC'; // keyword in AmountKeywordRecord
24
+ const issUSDC = 'USDC'; // issuer name
25
+
26
+ const trace = makeTracer('FUCF', true);
27
+
28
+ /**
29
+ * @param {BootstrapPowers & FastUSDCCorePowers } permittedPowers
30
+ * @param {{ options: LegibleCapData<{ feeTerms: FeeDistributionTerms}> }} config
31
+ */
32
+ export const distributeFees = async (permittedPowers, config) => {
33
+ trace('distributeFees...', config.options);
34
+
35
+ const { agoricNames, namesByAddress, zoe } = permittedPowers.consume;
36
+ /** @type {Brand<'nat'>} */
37
+ const usdcBrand = await E(agoricNames).lookup('brand', issUSDC);
38
+ /** @type {{ feeTerms: FeeDistributionTerms}} */
39
+ const { feeTerms: terms } = fromExternalConfig(config.options, {
40
+ USDC: usdcBrand,
41
+ });
42
+
43
+ const { creatorFacet } = await permittedPowers.consume.fastUsdcKit;
44
+ const want = {
45
+ [kwUSDC]: await ('fixedFees' in terms
46
+ ? terms.fixedFees
47
+ : E(creatorFacet)
48
+ .getContractFeeBalance()
49
+ .then(balance => floorMultiplyBy(balance, terms.feePortion))),
50
+ };
51
+ const proposal = harden({ want });
52
+
53
+ /** @type {DepositFacet} */
54
+ const depositFacet = await E(namesByAddress).lookup(
55
+ terms.destinationAddress,
56
+ 'depositFacet',
57
+ );
58
+ trace('to:', terms.destinationAddress, depositFacet);
59
+
60
+ const toWithdraw = await E(creatorFacet).makeWithdrawFeesInvitation();
61
+ trace('invitation:', toWithdraw, 'proposal:', proposal);
62
+ const seat = E(zoe).offer(toWithdraw, proposal);
63
+ const result = await E(seat).getOfferResult();
64
+ trace('offer result', result);
65
+ const payout = await E(seat).getPayout(kwUSDC);
66
+ /** @type {Amount<'nat'>} */
67
+ // @ts-expect-error USDC is a nat brand
68
+ const rxd = await E(depositFacet).receive(payout);
69
+ trace('received', rxd);
70
+ if (!AmountMath.isGTE(rxd, proposal.want[kwUSDC])) {
71
+ trace('🚨 expected', proposal.want[kwUSDC], 'got', rxd);
72
+ }
73
+ trace('done');
74
+ };
75
+ harden(distributeFees);
76
+
77
+ /** @satisfies {BootstrapManifestPermit} */
78
+ const permit = {
79
+ consume: {
80
+ fastUsdcKit: true,
81
+ agoricNames: true,
82
+ namesByAddress: true,
83
+ zoe: true,
84
+ },
85
+ };
86
+
87
+ /**
88
+ * @param {unknown} _utils
89
+ * @param {Parameters<typeof distributeFees>[1]} config
90
+ */
91
+ export const getManifestForDistributeFees = (_utils, { options }) => {
92
+ return { manifest: { [distributeFees.name]: permit }, options };
93
+ };
@@ -1,25 +1,29 @@
1
- import { AmountMath, AmountShape } from '@agoric/ertp';
1
+ import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
2
+ import { AmountMath } from '@agoric/ertp';
2
3
  import { assertAllDefined, makeTracer } from '@agoric/internal';
3
- import { ChainAddressShape } from '@agoric/orchestration';
4
+ import { AnyNatAmountShape, ChainAddressShape } from '@agoric/orchestration';
4
5
  import { pickFacet } from '@agoric/vat-data';
5
6
  import { VowShape } from '@agoric/vow';
6
- import { q } from '@endo/errors';
7
7
  import { E } from '@endo/far';
8
- import { M } from '@endo/patterns';
9
- import { CctpTxEvidenceShape, EudParamShape } from '../type-guards.js';
10
- import { addressTools } from '../utils/address.js';
8
+ import { M, mustMatch } from '@endo/patterns';
9
+ import { Fail, q } from '@endo/errors';
10
+ import {
11
+ AddressHookShape,
12
+ EvmHashShape,
13
+ EvidenceWithRiskShape,
14
+ } from '../type-guards.js';
11
15
  import { makeFeeTools } from '../utils/fees.js';
12
16
 
13
- const { isGTE } = AmountMath;
14
-
15
17
  /**
16
18
  * @import {HostInterface} from '@agoric/async-flow';
19
+ * @import {Amount, Brand} from '@agoric/ertp';
20
+ * @import {TypedPattern} from '@agoric/internal'
17
21
  * @import {NatAmount} from '@agoric/ertp';
18
22
  * @import {ChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration';
19
23
  * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
20
24
  * @import {VowTools} from '@agoric/vow';
21
25
  * @import {Zone} from '@agoric/zone';
22
- * @import {CctpTxEvidence, FeeConfig, LogFn} from '../types.js';
26
+ * @import {AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js';
23
27
  * @import {StatusManager} from './status-manager.js';
24
28
  * @import {LiquidityPoolKit} from './liquidity-pool.js';
25
29
  */
@@ -28,45 +32,73 @@ const { isGTE } = AmountMath;
28
32
  * @typedef {{
29
33
  * chainHub: ChainHub;
30
34
  * feeConfig: FeeConfig;
31
- * localTransfer: ZoeTools['localTransfer'];
32
35
  * log?: LogFn;
33
36
  * statusManager: StatusManager;
34
37
  * usdc: { brand: Brand<'nat'>; denom: Denom; };
35
38
  * vowTools: VowTools;
36
39
  * zcf: ZCF;
40
+ * zoeTools: ZoeTools;
37
41
  * }} AdvancerKitPowers
38
42
  */
39
43
 
44
+ /** @type {TypedPattern<AdvancerVowCtx>} */
45
+ const AdvancerVowCtxShape = M.splitRecord(
46
+ {
47
+ fullAmount: AnyNatAmountShape,
48
+ advanceAmount: AnyNatAmountShape,
49
+ destination: ChainAddressShape,
50
+ forwardingAddress: M.string(),
51
+ txHash: EvmHashShape,
52
+ },
53
+ { tmpSeat: M.remotable() },
54
+ );
55
+
40
56
  /** type guards internal to the AdvancerKit */
41
57
  const AdvancerKitI = harden({
42
58
  advancer: M.interface('AdvancerI', {
43
- handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(),
59
+ handleTransactionEvent: M.callWhen(EvidenceWithRiskShape).returns(),
60
+ setIntermediateRecipient: M.call(ChainAddressShape).returns(),
44
61
  }),
45
62
  depositHandler: M.interface('DepositHandlerI', {
46
- onFulfilled: M.call(M.undefined(), {
47
- amount: AmountShape,
48
- destination: ChainAddressShape,
49
- tmpSeat: M.remotable(),
50
- }).returns(VowShape),
51
- onRejected: M.call(M.error(), {
52
- amount: AmountShape,
53
- destination: ChainAddressShape,
54
- tmpSeat: M.remotable(),
55
- }).returns(),
63
+ onFulfilled: M.call(M.undefined(), AdvancerVowCtxShape).returns(VowShape),
64
+ onRejected: M.call(M.error(), AdvancerVowCtxShape).returns(),
56
65
  }),
57
66
  transferHandler: M.interface('TransferHandlerI', {
58
- // TODO confirm undefined, and not bigint (sequence)
67
+ onFulfilled: M.call(M.undefined(), AdvancerVowCtxShape).returns(
68
+ M.undefined(),
69
+ ),
70
+ onRejected: M.call(M.error(), AdvancerVowCtxShape).returns(M.undefined()),
71
+ }),
72
+ withdrawHandler: M.interface('WithdrawHandlerI', {
59
73
  onFulfilled: M.call(M.undefined(), {
60
- amount: AmountShape,
61
- destination: ChainAddressShape,
74
+ advanceAmount: AnyNatAmountShape,
75
+ tmpReturnSeat: M.remotable(),
62
76
  }).returns(M.undefined()),
63
77
  onRejected: M.call(M.error(), {
64
- amount: AmountShape,
65
- destination: ChainAddressShape,
78
+ advanceAmount: AnyNatAmountShape,
79
+ tmpReturnSeat: M.remotable(),
66
80
  }).returns(M.undefined()),
67
81
  }),
68
82
  });
69
83
 
84
+ /**
85
+ * @typedef {{
86
+ * fullAmount: NatAmount;
87
+ * advanceAmount: NatAmount;
88
+ * destination: ChainAddress;
89
+ * forwardingAddress: NobleAddress;
90
+ * txHash: EvmHash;
91
+ * }} AdvancerVowCtx
92
+ */
93
+
94
+ export const stateShape = harden({
95
+ notifier: M.remotable(),
96
+ borrower: M.remotable(),
97
+ poolAccount: M.remotable(),
98
+ intermediateRecipient: M.opt(ChainAddressShape),
99
+ settlementAddress: M.opt(ChainAddressShape),
100
+ });
101
+
70
102
  /**
71
103
  * @param {Zone} zone
72
104
  * @param {AdvancerKitPowers} caps
@@ -76,13 +108,13 @@ export const prepareAdvancerKit = (
76
108
  {
77
109
  chainHub,
78
110
  feeConfig,
79
- localTransfer,
80
111
  log = makeTracer('Advancer', true),
81
112
  statusManager,
82
113
  usdc,
83
114
  vowTools: { watch, when },
84
115
  zcf,
85
- } = /** @type {AdvancerKitPowers} */ ({}),
116
+ zoeTools: { localTransfer, withdrawToSeat },
117
+ },
86
118
  ) => {
87
119
  assertAllDefined({
88
120
  chainHub,
@@ -100,11 +132,19 @@ export const prepareAdvancerKit = (
100
132
  AdvancerKitI,
101
133
  /**
102
134
  * @param {{
103
- * borrowerFacet: LiquidityPoolKit['borrower'];
135
+ * notifier: import('./settler.js').SettlerKit['notifier'];
136
+ * borrower: LiquidityPoolKit['borrower'];
104
137
  * poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
138
+ * settlementAddress: ChainAddress;
139
+ * intermediateRecipient?: ChainAddress;
105
140
  * }} config
106
141
  */
107
- config => harden(config),
142
+ config =>
143
+ harden({
144
+ ...config,
145
+ // make sure the state record has this property, perhaps with an undefined value
146
+ intermediateRecipient: config.intermediateRecipient,
147
+ }),
108
148
  {
109
149
  advancer: {
110
150
  /**
@@ -115,130 +155,204 @@ export const prepareAdvancerKit = (
115
155
  * `StatusManager` - so we don't need to concern ourselves with
116
156
  * preserving the vow chain for callers.
117
157
  *
118
- * @param {CctpTxEvidence} evidence
158
+ * @param {EvidenceWithRisk} evidenceWithRisk
119
159
  */
120
- async handleTransactionEvent(evidence) {
160
+ async handleTransactionEvent({ evidence, risk }) {
121
161
  await null;
122
162
  try {
123
- const { borrowerFacet, poolAccount } = this.state;
124
- const { recipientAddress } = evidence.aux;
125
- const { EUD } = addressTools.getQueryParams(
126
- recipientAddress,
127
- EudParamShape,
128
- );
129
-
130
- // this will throw if the bech32 prefix is not found, but is handled by the catch
131
- const destination = chainHub.makeChainAddress(EUD);
132
- const requestedAmount = toAmount(evidence.tx.amount);
133
- const advanceAmount = feeTools.calculateAdvance(requestedAmount);
134
-
135
- // TODO: consider skipping and using `borrow()`s internal balance check
136
- const poolBalance = borrowerFacet.getBalance();
137
- if (!isGTE(poolBalance, requestedAmount)) {
138
- log(
139
- `Insufficient pool funds`,
140
- `Requested ${q(advanceAmount)} but only have ${q(poolBalance)}`,
141
- );
142
- statusManager.observe(evidence);
163
+ if (statusManager.hasBeenObserved(evidence)) {
164
+ log('txHash already seen:', evidence.txHash);
143
165
  return;
144
166
  }
145
167
 
146
- try {
147
- // Mark as Advanced since `transferV` initiates the advance.
148
- // Will throw if we've already .skipped or .advanced this evidence.
149
- statusManager.advance(evidence);
150
- } catch (e) {
151
- // Only anticipated error is `assertNotSeen`, so intercept the
152
- // catch so we don't call .skip which also performs this check
153
- log('Advancer error:', q(e).toString());
168
+ if (risk.risksIdentified?.length) {
169
+ log('risks identified, skipping advance');
170
+ statusManager.skipAdvance(evidence, risk.risksIdentified);
154
171
  return;
155
172
  }
173
+ const { settlementAddress } = this.state;
174
+ const { recipientAddress } = evidence.aux;
175
+ const decoded = decodeAddressHook(recipientAddress);
176
+ mustMatch(decoded, AddressHookShape);
177
+ if (decoded.baseAddress !== settlementAddress.value) {
178
+ throw Fail`⚠️ baseAddress of address hook ${q(decoded.baseAddress)} does not match the expected address ${q(settlementAddress.value)}`;
179
+ }
180
+ const { EUD } = /** @type {AddressHook['query']} */ (decoded.query);
181
+ log(`decoded EUD: ${EUD}`);
182
+ // throws if the bech32 prefix is not found
183
+ const destination = chainHub.makeChainAddress(EUD);
184
+
185
+ const fullAmount = toAmount(evidence.tx.amount);
186
+ const { borrower, notifier, poolAccount } = this.state;
187
+ // do not advance if we've already received a mint/settlement
188
+ const mintedEarly = notifier.checkMintedEarly(
189
+ evidence,
190
+ destination,
191
+ );
192
+ if (mintedEarly) return;
193
+
194
+ // throws if requested does not exceed fees
195
+ const advanceAmount = feeTools.calculateAdvance(fullAmount);
156
196
 
157
197
  const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit();
158
- const amountKWR = harden({ USDC: advanceAmount });
159
- try {
160
- borrowerFacet.borrow(tmpSeat, amountKWR);
161
- } catch (e) {
162
- // We do not expect this to fail since there are no turn boundaries
163
- // between .getBalance() and .borrow().
164
- // We catch to report outside of the normal error flow since this is
165
- // not expected.
166
- log('🚨 advance borrow failed', q(e).toString());
167
- }
198
+ // throws if the pool has insufficient funds
199
+ borrower.borrow(tmpSeat, advanceAmount);
200
+
201
+ // this cannot throw since `.isSeen()` is called in the same turn
202
+ statusManager.advance(evidence);
168
203
 
169
204
  const depositV = localTransfer(
170
205
  tmpSeat,
171
206
  // @ts-expect-error LocalAccountMethods vs OrchestrationAccount
172
207
  poolAccount,
173
- amountKWR,
208
+ harden({ USDC: advanceAmount }),
174
209
  );
175
210
  void watch(depositV, this.facets.depositHandler, {
176
- amount: advanceAmount,
211
+ advanceAmount,
177
212
  destination,
213
+ forwardingAddress: evidence.tx.forwardingAddress,
214
+ fullAmount,
178
215
  tmpSeat,
216
+ txHash: evidence.txHash,
179
217
  });
180
- } catch (e) {
181
- log('Advancer error:', q(e).toString());
182
- statusManager.observe(evidence);
218
+ } catch (error) {
219
+ log('Advancer error:', error);
220
+ statusManager.skipAdvance(evidence, [error.message]);
183
221
  }
184
222
  },
223
+ /** @param {ChainAddress} intermediateRecipient */
224
+ setIntermediateRecipient(intermediateRecipient) {
225
+ this.state.intermediateRecipient = intermediateRecipient;
226
+ },
185
227
  },
186
228
  depositHandler: {
187
229
  /**
188
230
  * @param {undefined} result
189
- * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx
231
+ * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
190
232
  */
191
- onFulfilled(result, { amount, destination }) {
192
- const { poolAccount } = this.state;
193
- const transferV = E(poolAccount).transfer(destination, {
233
+ onFulfilled(result, ctx) {
234
+ const { poolAccount, intermediateRecipient, settlementAddress } =
235
+ this.state;
236
+ const { destination, advanceAmount, tmpSeat, ...detail } = ctx;
237
+ tmpSeat.exit();
238
+ const amount = harden({
194
239
  denom: usdc.denom,
195
- value: amount.value,
240
+ value: advanceAmount.value,
196
241
  });
197
- return watch(transferV, this.facets.transferHandler, {
242
+ const transferOrSendV =
243
+ destination.chainId === settlementAddress.chainId
244
+ ? E(poolAccount).send(destination, amount)
245
+ : E(poolAccount).transfer(destination, amount, {
246
+ forwardOpts: { intermediateRecipient },
247
+ });
248
+ return watch(transferOrSendV, this.facets.transferHandler, {
198
249
  destination,
199
- amount,
250
+ advanceAmount,
251
+ ...detail,
200
252
  });
201
253
  },
202
254
  /**
255
+ * We do not expect this to be a common failure. it should only occur
256
+ * if USDC is not registered in vbank or the tmpSeat has less than
257
+ * `advanceAmount`.
258
+ *
259
+ * If we do hit this path, we return funds to the Liquidity Pool and
260
+ * notify of Advancing failure.
261
+ *
203
262
  * @param {Error} error
204
- * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx
263
+ * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
205
264
  */
206
- onRejected(error, { tmpSeat }) {
207
- // TODO return seat allocation from ctx to LP?
208
- log('🚨 advance deposit failed', q(error).toString());
209
- // TODO #10510 (comprehensive error testing) determine
210
- // course of action here
265
+ onRejected(error, { tmpSeat, advanceAmount, ...restCtx }) {
211
266
  log(
212
- 'TODO live payment on seat to return to LP',
213
- q(tmpSeat).toString(),
267
+ '⚠️ deposit to localOrchAccount failed, attempting to return payment to LP',
268
+ error,
214
269
  );
270
+ try {
271
+ const { borrower, notifier } = this.state;
272
+ notifier.notifyAdvancingResult(restCtx, false);
273
+ borrower.returnToPool(tmpSeat, advanceAmount);
274
+ tmpSeat.exit();
275
+ } catch (e) {
276
+ log('🚨 deposit to localOrchAccount failure recovery failed', e);
277
+ }
215
278
  },
216
279
  },
217
280
  transferHandler: {
218
281
  /**
219
- * @param {undefined} result TODO confirm this is not a bigint (sequence)
220
- * @param {{ destination: ChainAddress; amount: NatAmount; }} ctx
282
+ * @param {undefined} result
283
+ * @param {AdvancerVowCtx} ctx
221
284
  */
222
- onFulfilled(result, { destination, amount }) {
223
- // TODO vstorage update? We don't currently have a status for
224
- // Advanced + transferV settled
225
- log(
226
- 'Advance transfer fulfilled',
227
- q({ amount, destination, result }).toString(),
285
+ onFulfilled(result, ctx) {
286
+ const { notifier } = this.state;
287
+ const { advanceAmount, destination, ...detail } = ctx;
288
+ log('Advance succeeded', { advanceAmount, destination });
289
+ // During development, due to a bug, this call threw.
290
+ // The failure was silent (no diagnostics) due to:
291
+ // - #10576 Vows do not report unhandled rejections
292
+ // For now, the advancer kit relies on consistency between
293
+ // notify, statusManager, and callers of handleTransactionEvent().
294
+ // TODO: revisit #10576 during #10510
295
+ notifier.notifyAdvancingResult({ destination, ...detail }, true);
296
+ },
297
+ /**
298
+ * @param {Error} error
299
+ * @param {AdvancerVowCtx} ctx
300
+ */
301
+ onRejected(error, ctx) {
302
+ const { notifier, poolAccount } = this.state;
303
+ log('Advance failed', error);
304
+ const { advanceAmount, ...restCtx } = ctx;
305
+ notifier.notifyAdvancingResult(restCtx, false);
306
+ const { zcfSeat: tmpReturnSeat } = zcf.makeEmptySeatKit();
307
+ const withdrawV = withdrawToSeat(
308
+ // @ts-expect-error LocalAccountMethods vs OrchestrationAccount
309
+ poolAccount,
310
+ tmpReturnSeat,
311
+ harden({ USDC: advanceAmount }),
228
312
  );
313
+ void watch(withdrawV, this.facets.withdrawHandler, {
314
+ advanceAmount,
315
+ tmpReturnSeat,
316
+ });
317
+ },
318
+ },
319
+ withdrawHandler: {
320
+ /**
321
+ *
322
+ * @param {undefined} result
323
+ * @param {{ advanceAmount: Amount<'nat'>; tmpReturnSeat: ZCFSeat; }} ctx
324
+ */
325
+ onFulfilled(result, { advanceAmount, tmpReturnSeat }) {
326
+ const { borrower } = this.state;
327
+ try {
328
+ borrower.returnToPool(tmpReturnSeat, advanceAmount);
329
+ } catch (e) {
330
+ // If we reach here, the unused advance funds will remain in `tmpReturnSeat`
331
+ // and must be retrieved from recovery sets.
332
+ log(
333
+ `🚨 return ${q(advanceAmount)} to pool failed. funds remain on "tmpReturnSeat"`,
334
+ e,
335
+ );
336
+ }
337
+ tmpReturnSeat.exit();
229
338
  },
230
- onRejected(error) {
231
- // TODO #10510 (comprehensive error testing) determine
232
- // course of action here. This might fail due to timeout.
233
- log('Advance transfer rejected', q(error).toString());
339
+ /**
340
+ * @param {Error} error
341
+ * @param {{ advanceAmount: Amount<'nat'>; tmpReturnSeat: ZCFSeat; }} ctx
342
+ */
343
+ onRejected(error, { advanceAmount, tmpReturnSeat }) {
344
+ log(
345
+ `🚨 withdraw ${q(advanceAmount)} from "poolAccount" to return to pool failed`,
346
+ error,
347
+ );
348
+ // If we reach here, the unused advance funds will remain in the `poolAccount`.
349
+ // A contract update will be required to return them to the LiquidityPool.
350
+ tmpReturnSeat.exit();
234
351
  },
235
352
  },
236
353
  },
237
354
  {
238
- stateShape: harden({
239
- borrowerFacet: M.remotable(),
240
- poolAccount: M.remotable(),
241
- }),
355
+ stateShape,
242
356
  },
243
357
  );
244
358
  };