@agoric/fast-usdc 0.2.0-upgrade-19-dev-0754752.0 → 0.2.0-upgrade-18a-dev-4ee0508.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.
@@ -1,15 +1,13 @@
1
- import { AmountMath, AmountShape, RatioShape } from '@agoric/ertp';
1
+ import { AmountMath } from '@agoric/ertp';
2
2
  import {
3
3
  makeRecorderTopic,
4
- RecorderKitShape,
5
4
  TopicsRecordShape,
6
- } from '@agoric/zoe/src/contractSupport/index.js';
5
+ } from '@agoric/zoe/src/contractSupport/topics.js';
7
6
  import { SeatShape } from '@agoric/zoe/src/typeGuards.js';
8
7
  import { M } from '@endo/patterns';
9
8
  import { Fail, q } from '@endo/errors';
10
9
  import {
11
10
  borrowCalc,
12
- checkPoolBalance,
13
11
  depositCalc,
14
12
  makeParity,
15
13
  repayCalc,
@@ -22,7 +20,6 @@ import {
22
20
  } from '../type-guards.js';
23
21
 
24
22
  /**
25
- * @import {Amount, Brand, Payment} from '@agoric/ertp';
26
23
  * @import {Zone} from '@agoric/zone';
27
24
  * @import {Remote} from '@agoric/internal'
28
25
  * @import {StorageNode} from '@agoric/internal/src/lib-chainStorage.js'
@@ -31,7 +28,32 @@ import {
31
28
  * @import {PoolStats} from '../types.js';
32
29
  */
33
30
 
34
- const { add, isGTE, makeEmpty } = AmountMath;
31
+ const { add, isEqual, isGTE, makeEmpty } = AmountMath;
32
+
33
+ /** @param {Brand} brand */
34
+ const makeDust = brand => AmountMath.make(brand, 1n);
35
+
36
+ /**
37
+ * Verifies that the total pool balance (unencumbered + encumbered) matches the
38
+ * shareWorth numerator. The total pool balance consists of:
39
+ * 1. unencumbered balance - USDC available in the pool for borrowing
40
+ * 2. encumbered balance - USDC currently lent out
41
+ *
42
+ * A negligible `dust` amount is used to initialize shareWorth with a non-zero
43
+ * denominator. It must remain in the pool at all times.
44
+ *
45
+ * @param {ZCFSeat} poolSeat
46
+ * @param {ShareWorth} shareWorth
47
+ * @param {Brand} USDC
48
+ * @param {Amount<'nat'>} encumberedBalance
49
+ */
50
+ const checkPoolBalance = (poolSeat, shareWorth, USDC, encumberedBalance) => {
51
+ const unencumberedBalance = poolSeat.getAmountAllocated('USDC', USDC);
52
+ const dust = makeDust(USDC);
53
+ const grossBalance = add(add(unencumberedBalance, dust), encumberedBalance);
54
+ isEqual(grossBalance, shareWorth.numerator) ||
55
+ Fail`🚨 pool balance ${q(unencumberedBalance)} and encumbered balance ${q(encumberedBalance)} inconsistent with shareWorth ${q(shareWorth)}`;
56
+ };
35
57
 
36
58
  /**
37
59
  * @typedef {{
@@ -49,22 +71,6 @@ const { add, isGTE, makeEmpty } = AmountMath;
49
71
  * }} RepayPaymentKWR
50
72
  */
51
73
 
52
- export const stateShape = harden({
53
- encumberedBalance: AmountShape,
54
- feeSeat: M.remotable(),
55
- poolStats: M.record(),
56
- poolMetricsRecorderKit: RecorderKitShape,
57
- poolSeat: M.remotable(),
58
- PoolShares: M.remotable(),
59
- proposalShapes: {
60
- deposit: M.pattern(),
61
- withdraw: M.pattern(),
62
- withdrawFees: M.pattern(),
63
- },
64
- shareMint: M.remotable(),
65
- shareWorth: RatioShape,
66
- });
67
-
68
74
  /**
69
75
  * @param {Zone} zone
70
76
  * @param {ZCF} zcf
@@ -100,18 +106,11 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
100
106
  withdrawHandler: M.interface('withdrawHandler', {
101
107
  handle: M.call(SeatShape, M.any()).returns(M.promise()),
102
108
  }),
103
- withdrawFeesHandler: M.interface('withdrawFeesHandler', {
104
- handle: M.call(SeatShape, M.any()).returns(M.promise()),
105
- }),
106
109
  public: M.interface('public', {
107
110
  makeDepositInvitation: M.call().returns(M.promise()),
108
111
  makeWithdrawInvitation: M.call().returns(M.promise()),
109
112
  getPublicTopics: M.call().returns(TopicsRecordShape),
110
113
  }),
111
- feeRecipient: M.interface('feeRecipient', {
112
- getContractFeeBalance: M.call().returns(AmountShape),
113
- makeWithdrawFeesInvitation: M.call().returns(M.promise()),
114
- }),
115
114
  },
116
115
  /**
117
116
  * @param {ZCFMint<'nat'>} shareMint
@@ -120,7 +119,7 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
120
119
  (shareMint, node) => {
121
120
  const { brand: PoolShares } = shareMint.getIssuerRecord();
122
121
  const proposalShapes = makeProposalShapes({ USDC, PoolShares });
123
- const shareWorth = makeParity(USDC, PoolShares);
122
+ const shareWorth = makeParity(makeDust(USDC), PoolShares);
124
123
  const { zcfSeat: poolSeat } = zcf.makeEmptySeatKit();
125
124
  const { zcfSeat: feeSeat } = zcf.makeEmptySeatKit();
126
125
  const poolMetricsRecorderKit = tools.makeRecorderKit(
@@ -192,9 +191,7 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
192
191
  zcf.atomicRearrange(
193
192
  harden([[borrowSeat, repaySeat, { USDC: amount }, returnAmounts]]),
194
193
  );
195
- this.facets.repayer.repay(repaySeat, returnAmounts);
196
- borrowSeat.exit();
197
- repaySeat.exit();
194
+ return this.facets.repayer.repay(repaySeat, returnAmounts);
198
195
  },
199
196
  },
200
197
  repayer: {
@@ -210,11 +207,7 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
210
207
  poolStats,
211
208
  shareWorth,
212
209
  } = this.state;
213
- checkPoolBalance(
214
- poolSeat.getCurrentAllocation(),
215
- shareWorth,
216
- encumberedBalance,
217
- );
210
+ checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance);
218
211
 
219
212
  const fromSeatAllocation = fromSeat.getCurrentAllocation();
220
213
  // Validate allocation equals amounts and Principal <= encumberedBalance
@@ -271,11 +264,7 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
271
264
  /** @type {USDCProposalShapes['deposit']} */
272
265
  // @ts-expect-error ensured by proposalShape
273
266
  const proposal = lp.getProposal();
274
- checkPoolBalance(
275
- poolSeat.getCurrentAllocation(),
276
- shareWorth,
277
- encumberedBalance,
278
- );
267
+ checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance);
279
268
  const post = depositCalc(shareWorth, proposal);
280
269
 
281
270
  // COMMIT POINT
@@ -311,12 +300,8 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
311
300
  // @ts-expect-error ensured by proposalShape
312
301
  const proposal = lp.getProposal();
313
302
  const { zcfSeat: burn } = zcf.makeEmptySeatKit();
314
- const post = withdrawCalc(
315
- shareWorth,
316
- proposal,
317
- poolSeat.getCurrentAllocation(),
318
- encumberedBalance,
319
- );
303
+ checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance);
304
+ const post = withdrawCalc(shareWorth, proposal);
320
305
 
321
306
  // COMMIT POINT
322
307
  try {
@@ -340,21 +325,6 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
340
325
  external.publishPoolMetrics();
341
326
  },
342
327
  },
343
- withdrawFeesHandler: {
344
- /** @param {ZCFSeat} seat */
345
- async handle(seat) {
346
- const { feeSeat } = this.state;
347
-
348
- const { want } = seat.getProposal();
349
- const available = feeSeat.getAmountAllocated('USDC', want.USDC.brand);
350
- isGTE(available, want.USDC) ||
351
- Fail`cannot withdraw ${want.USDC}; only ${available} available`;
352
-
353
- // COMMIT POINT
354
- zcf.atomicRearrange(harden([[feeSeat, seat, want]]));
355
- seat.exit();
356
- },
357
- },
358
328
  public: {
359
329
  makeDepositInvitation() {
360
330
  return zcf.makeInvitation(
@@ -382,28 +352,11 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
382
352
  };
383
353
  },
384
354
  },
385
- feeRecipient: {
386
- getContractFeeBalance() {
387
- const { feeSeat } = this.state;
388
- /** @type {Amount<'nat'>} */
389
- const balance = feeSeat.getCurrentAllocation().USDC;
390
- return balance;
391
- },
392
- makeWithdrawFeesInvitation() {
393
- return zcf.makeInvitation(
394
- this.facets.withdrawFeesHandler,
395
- 'Withdraw Fees',
396
- undefined,
397
- this.state.proposalShapes.withdrawFees,
398
- );
399
- },
400
- },
401
355
  },
402
356
  {
403
357
  finish: ({ facets: { external } }) => {
404
358
  void external.publishPoolMetrics();
405
359
  },
406
- stateShape,
407
360
  },
408
361
  );
409
362
  };
@@ -90,6 +90,8 @@ export const prepareOperatorKit = (zone, staticPowers) =>
90
90
  */
91
91
  async SubmitEvidence(evidence, riskAssessment) {
92
92
  const { operator } = this.facets;
93
+ // TODO(bootstrap integration): cause this call to throw and confirm that it
94
+ // shows up in the the smart-wallet UpdateRecord `error` property
93
95
  operator.submitEvidence(evidence, riskAssessment);
94
96
  return staticPowers.makeInertInvitation(
95
97
  'evidence was pushed in the invitation maker call',
@@ -8,72 +8,20 @@ import { M } from '@endo/patterns';
8
8
  import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
9
9
  import { PendingTxStatus } from '../constants.js';
10
10
  import { makeFeeTools } from '../utils/fees.js';
11
- import {
12
- CctpTxEvidenceShape,
13
- EvmHashShape,
14
- makeNatAmountShape,
15
- } from '../type-guards.js';
11
+ import { EvmHashShape } from '../type-guards.js';
16
12
 
17
13
  /**
18
14
  * @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
19
- * @import {Amount, Brand, NatValue, Payment} from '@agoric/ertp';
20
15
  * @import {Denom, OrchestrationAccount, ChainHub, ChainAddress} from '@agoric/orchestration';
21
16
  * @import {WithdrawToSeat} from '@agoric/orchestration/src/utils/zoe-tools'
22
- * @import {IBCChannelID, IBCPacket, VTransferIBCEvent} from '@agoric/vats';
17
+ * @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats';
23
18
  * @import {Zone} from '@agoric/zone';
24
19
  * @import {HostOf, HostInterface} from '@agoric/async-flow';
25
20
  * @import {TargetRegistration} from '@agoric/vats/src/bridge-target.js';
26
- * @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash, LogFn, CctpTxEvidence} from '../types.js';
21
+ * @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash, LogFn} from '../types.js';
27
22
  * @import {StatusManager} from './status-manager.js';
28
23
  */
29
24
 
30
- /**
31
- * @param {IBCPacket} data
32
- * @param {string} remoteDenom
33
- * @returns {{ nfa: NobleAddress, amount: bigint, EUD: string } | {error: object[]}}
34
- */
35
- const decodeEventPacket = ({ data }, remoteDenom) => {
36
- // NB: may not be a FungibleTokenPacketData or even JSON
37
- /** @type {FungibleTokenPacketData} */
38
- let tx;
39
- try {
40
- tx = JSON.parse(atob(data));
41
- } catch (e) {
42
- return { error: ['could not parse packet data', data] };
43
- }
44
-
45
- // given the sourceChannel check, we can be certain of this cast
46
- const nfa = /** @type {NobleAddress} */ (tx.sender);
47
-
48
- if (tx.denom !== remoteDenom) {
49
- const { denom: actual } = tx;
50
- return { error: ['unexpected denom', { actual, expected: remoteDenom }] };
51
- }
52
-
53
- let EUD;
54
- try {
55
- ({ EUD } = decodeAddressHook(tx.receiver).query);
56
- if (!EUD) {
57
- return { error: ['no EUD parameter', tx.receiver] };
58
- }
59
- if (typeof EUD !== 'string') {
60
- return { error: ['EUD is not a string', EUD] };
61
- }
62
- } catch (e) {
63
- return { error: ['no query params', tx.receiver] };
64
- }
65
-
66
- let amount;
67
- try {
68
- amount = BigInt(tx.amount);
69
- } catch (e) {
70
- return { error: ['invalid amount', tx.amount] };
71
- }
72
-
73
- return { nfa, amount, EUD };
74
- };
75
- harden(decodeEventPacket);
76
-
77
25
  /**
78
26
  * NOTE: not meant to be parsable.
79
27
  *
@@ -83,25 +31,6 @@ harden(decodeEventPacket);
83
31
  const makeMintedEarlyKey = (addr, amount) =>
84
32
  `pendingTx:${JSON.stringify([addr, String(amount)])}`;
85
33
 
86
- /** @param {Brand<'nat'>} USDC */
87
- export const makeAdvanceDetailsShape = USDC =>
88
- harden({
89
- destination: ChainAddressShape,
90
- forwardingAddress: M.string(),
91
- fullAmount: makeNatAmountShape(USDC),
92
- txHash: EvmHashShape,
93
- });
94
-
95
- export const stateShape = harden({
96
- repayer: M.remotable('Repayer'),
97
- settlementAccount: M.remotable('Account'),
98
- registration: M.or(M.undefined(), M.remotable('Registration')),
99
- sourceChannel: M.string(),
100
- remoteDenom: M.string(),
101
- mintedEarly: M.remotable('mintedEarly'),
102
- intermediateRecipient: M.opt(ChainAddressShape),
103
- });
104
-
105
34
  /**
106
35
  * @param {Zone} zone
107
36
  * @param {object} caps
@@ -138,23 +67,26 @@ export const prepareSettler = (
138
67
  tap: M.interface('SettlerTapI', {
139
68
  receiveUpcall: M.call(M.record()).returns(M.promise()),
140
69
  }),
141
- notifier: M.interface('SettlerNotifyI', {
70
+ notify: M.interface('SettlerNotifyI', {
142
71
  notifyAdvancingResult: M.call(
143
- makeAdvanceDetailsShape(USDC),
72
+ M.record(), // XXX fill in details TODO
144
73
  M.boolean(),
145
74
  ).returns(),
146
- checkMintedEarly: M.call(
147
- CctpTxEvidenceShape,
148
- ChainAddressShape,
149
- ).returns(M.boolean()),
150
75
  }),
151
76
  self: M.interface('SettlerSelfI', {
152
- disburse: M.call(EvmHashShape, M.nat()).returns(M.promise()),
153
- forward: M.call(EvmHashShape, M.nat(), M.string()).returns(),
77
+ disburse: M.call(EvmHashShape, M.string(), M.nat()).returns(
78
+ M.promise(),
79
+ ),
80
+ forward: M.call(
81
+ M.opt(EvmHashShape),
82
+ M.string(),
83
+ M.nat(),
84
+ M.string(),
85
+ ).returns(),
154
86
  }),
155
87
  transferHandler: M.interface('SettlerTransferI', {
156
- onFulfilled: M.call(M.undefined(), M.string()).returns(),
157
- onRejected: M.call(M.error(), M.string()).returns(),
88
+ onFulfilled: M.call(M.any(), M.record()).returns(),
89
+ onRejected: M.call(M.any(), M.record()).returns(),
158
90
  }),
159
91
  },
160
92
  /**
@@ -205,40 +137,61 @@ export const prepareSettler = (
205
137
  return;
206
138
  }
207
139
 
208
- const decoded = decodeEventPacket(event.packet, remoteDenom);
209
- if ('error' in decoded) {
210
- log('invalid event packet', decoded.error);
140
+ // TODO: why is it safe to cast this without a runtime check?
141
+ const tx = /** @type {FungibleTokenPacketData} */ (
142
+ JSON.parse(atob(packet.data))
143
+ );
144
+
145
+ // given the sourceChannel check, we can be certain of this cast
146
+ const nfa = /** @type {NobleAddress} */ (tx.sender);
147
+
148
+ if (tx.denom !== remoteDenom) {
149
+ const { denom: actual } = tx;
150
+ log('unexpected denom', { actual, expected: remoteDenom });
151
+ return;
152
+ }
153
+
154
+ let EUD;
155
+ try {
156
+ ({ EUD } = decodeAddressHook(tx.receiver).query);
157
+ if (!EUD) {
158
+ log('no EUD parameter', tx.receiver);
159
+ return;
160
+ }
161
+ if (typeof EUD !== 'string') {
162
+ log('EUD is not a string', EUD);
163
+ return;
164
+ }
165
+ } catch (e) {
166
+ log('no query params', tx.receiver);
211
167
  return;
212
168
  }
213
169
 
214
- const { nfa, amount, EUD } = decoded;
170
+ const amount = BigInt(tx.amount); // TODO: what if this throws?
171
+
215
172
  const { self } = this.facets;
216
173
  const found = statusManager.dequeueStatus(nfa, amount);
217
174
  log('dequeued', found, 'for', nfa, amount);
218
175
  switch (found?.status) {
219
176
  case PendingTxStatus.Advanced:
220
- return self.disburse(found.txHash, amount);
177
+ return self.disburse(found.txHash, nfa, amount);
221
178
 
222
179
  case PendingTxStatus.Advancing:
223
- log('⚠️ tap: minted while advancing', nfa, amount);
224
180
  this.state.mintedEarly.add(makeMintedEarlyKey(nfa, amount));
225
181
  return;
226
182
 
227
183
  case PendingTxStatus.Observed:
228
184
  case PendingTxStatus.AdvanceSkipped:
229
185
  case PendingTxStatus.AdvanceFailed:
230
- return self.forward(found.txHash, amount, EUD);
186
+ return self.forward(found.txHash, nfa, amount, EUD);
231
187
 
232
188
  case undefined:
233
189
  default:
234
- log('⚠️ tap: minted before observed', nfa, amount);
235
- // XXX consider capturing in vstorage
236
- // we would need a new key, as this does not have a txHash
237
- this.state.mintedEarly.add(makeMintedEarlyKey(nfa, amount));
190
+ log('⚠️ tap: no status for ', nfa, amount);
238
191
  }
239
192
  },
240
193
  },
241
- notifier: {
194
+ notify: {
242
195
  /**
243
196
  * @param {object} ctx
244
197
  * @param {EvmHash} ctx.txHash
@@ -257,12 +210,16 @@ export const prepareSettler = (
257
210
  const key = makeMintedEarlyKey(forwardingAddress, fullValue);
258
211
  if (mintedEarly.has(key)) {
259
212
  mintedEarly.delete(key);
260
- statusManager.advanceOutcomeForMintedEarly(txHash, success);
261
213
  if (success) {
262
- void this.facets.self.disburse(txHash, fullValue);
214
+ void this.facets.self.disburse(
215
+ txHash,
216
+ forwardingAddress,
217
+ fullValue,
218
+ );
263
219
  } else {
264
220
  void this.facets.self.forward(
265
221
  txHash,
222
+ forwardingAddress,
266
223
  fullValue,
267
224
  destination.value,
268
225
  );
@@ -271,39 +228,14 @@ export const prepareSettler = (
271
228
  statusManager.advanceOutcome(forwardingAddress, fullValue, success);
272
229
  }
273
230
  },
274
- /**
275
- * @param {CctpTxEvidence} evidence
276
- * @param {ChainAddress} destination
277
- * @returns {boolean}
278
- * @throws {Error} if minted early, so advancer doesn't advance
279
- */
280
- checkMintedEarly(evidence, destination) {
281
- const {
282
- tx: { forwardingAddress, amount },
283
- txHash,
284
- } = evidence;
285
- const key = makeMintedEarlyKey(forwardingAddress, amount);
286
- const { mintedEarly } = this.state;
287
- if (mintedEarly.has(key)) {
288
- log(
289
- 'matched minted early key, initiating forward',
290
- forwardingAddress,
291
- amount,
292
- );
293
- mintedEarly.delete(key);
294
- statusManager.advanceOutcomeForUnknownMint(evidence);
295
- void this.facets.self.forward(txHash, amount, destination.value);
296
- return true;
297
- }
298
- return false;
299
- },
300
231
  },
301
232
  self: {
302
233
  /**
303
234
  * @param {EvmHash} txHash
235
+ * @param {NobleAddress} nfa
304
236
  * @param {NatValue} fullValue
305
237
  */
306
- async disburse(txHash, fullValue) {
238
+ async disburse(txHash, nfa, fullValue) {
307
239
  const { repayer, settlementAccount } = this.state;
308
240
  const received = AmountMath.make(USDC, fullValue);
309
241
  const { zcfSeat: settlingSeat } = zcf.makeEmptySeatKit();
@@ -327,67 +259,76 @@ export const prepareSettler = (
327
259
  harden([[settlingSeat, settlingSeat, { In: received }, split]]),
328
260
  );
329
261
  repayer.repay(settlingSeat, split);
330
- settlingSeat.exit();
331
262
 
332
- // update status manager, marking tx `DISBURSED`
263
+ // update status manager, marking tx `SETTLED`
333
264
  statusManager.disbursed(txHash, split);
334
265
  },
335
266
  /**
336
267
  * @param {EvmHash} txHash
268
+ * @param {NobleAddress} nfa
337
269
  * @param {NatValue} fullValue
338
270
  * @param {string} EUD
339
271
  */
340
- forward(txHash, fullValue, EUD) {
272
+ forward(txHash, nfa, fullValue, EUD) {
341
273
  const { settlementAccount, intermediateRecipient } = this.state;
342
274
 
343
- const dest = (() => {
344
- try {
345
- return chainHub.makeChainAddress(EUD);
346
- } catch (e) {
347
- log('⚠️ forward transfer failed!', e, txHash);
348
- statusManager.forwarded(txHash, false);
349
- return null;
350
- }
351
- })();
352
- if (!dest) return;
275
+ const dest = chainHub.makeChainAddress(EUD);
353
276
 
277
+ // TODO? statusManager.forwarding(txHash, sender, amount);
354
278
  const txfrV = E(settlementAccount).transfer(
355
279
  dest,
356
280
  AmountMath.make(USDC, fullValue),
357
281
  { forwardOpts: { intermediateRecipient } },
358
282
  );
359
- void vowTools.watch(txfrV, this.facets.transferHandler, txHash);
283
+ void vowTools.watch(txfrV, this.facets.transferHandler, {
284
+ txHash,
285
+ nfa,
286
+ fullValue,
287
+ });
360
288
  },
361
289
  },
362
290
  transferHandler: {
363
291
  /**
364
292
  * @param {unknown} _result
365
- * @param {EvmHash} txHash
293
+ * @param {SettlerTransferCtx} ctx
294
+ *
295
+ * @typedef {{
296
+ * txHash: EvmHash;
297
+ * nfa: NobleAddress;
298
+ * fullValue: NatValue;
299
+ * }} SettlerTransferCtx
366
300
  */
367
- onFulfilled(_result, txHash) {
368
- // update status manager, marking tx `FORWARDED` without fee split
369
- statusManager.forwarded(txHash, true);
301
+ onFulfilled(_result, ctx) {
302
+ const { txHash, nfa, fullValue } = ctx;
303
+ statusManager.forwarded(txHash, nfa, fullValue);
370
304
  },
371
305
  /**
372
306
  * @param {unknown} reason
373
- * @param {EvmHash} txHash
307
+ * @param {SettlerTransferCtx} ctx
374
308
  */
375
- onRejected(reason, txHash) {
376
- // funds remain in `settlementAccount` and must be recovered via a
377
- // contract upgrade
378
- log('🚨 forward transfer rejected!', reason, txHash);
379
- // update status manager, flagging a terminal state that needs to be
380
- // manual intervention or a code update to remediate
381
- statusManager.forwarded(txHash, false);
309
+ onRejected(reason, ctx) {
310
+ log('⚠️ transfer rejected!', reason, ctx);
311
+ // const { txHash, nfa, amount } = ctx;
312
+ // TODO(#10510): statusManager.forwardFailed(txHash, nfa, amount);
382
313
  },
383
314
  },
384
315
  },
385
316
  {
386
- stateShape,
317
+ stateShape: harden({
318
+ repayer: M.remotable('Repayer'),
319
+ settlementAccount: M.remotable('Account'),
320
+ registration: M.or(M.undefined(), M.remotable('Registration')),
321
+ sourceChannel: M.string(),
322
+ remoteDenom: M.string(),
323
+ mintedEarly: M.remotable('mintedEarly'),
324
+ intermediateRecipient: M.opt(ChainAddressShape),
325
+ }),
387
326
  },
388
327
  );
389
328
  };
390
329
  harden(prepareSettler);
391
330
 
392
- // Expose the whole kit because the contract needs `creatorFacet` and the Advancer needs `notifier`
393
- /** @typedef {ReturnType<ReturnType<typeof prepareSettler>>} SettlerKit */
331
+ /**
332
+ * XXX consider using pickFacet (do we have pickFacets?)
333
+ * @typedef {ReturnType<ReturnType<typeof prepareSettler>>} SettlerKit
334
+ */