@agoric/fast-usdc 0.1.1-other-dev-3eb1a1d.0 → 0.2.0-u18.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,25 +1,28 @@
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 {TypedPattern} from '@agoric/internal'
17
20
  * @import {NatAmount} from '@agoric/ertp';
18
21
  * @import {ChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration';
19
22
  * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
20
23
  * @import {VowTools} from '@agoric/vow';
21
24
  * @import {Zone} from '@agoric/zone';
22
- * @import {CctpTxEvidence, FeeConfig, LogFn} from '../types.js';
25
+ * @import {CctpTxEvidence, AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js';
23
26
  * @import {StatusManager} from './status-manager.js';
24
27
  * @import {LiquidityPoolKit} from './liquidity-pool.js';
25
28
  */
@@ -37,36 +40,47 @@ const { isGTE } = AmountMath;
37
40
  * }} AdvancerKitPowers
38
41
  */
39
42
 
43
+ /** @type {TypedPattern<AdvancerVowCtx>} */
44
+ const AdvancerVowCtxShape = M.splitRecord(
45
+ {
46
+ fullAmount: AnyNatAmountShape,
47
+ advanceAmount: AnyNatAmountShape,
48
+ destination: ChainAddressShape,
49
+ forwardingAddress: M.string(),
50
+ txHash: EvmHashShape,
51
+ },
52
+ { tmpSeat: M.remotable() },
53
+ );
54
+
40
55
  /** type guards internal to the AdvancerKit */
41
56
  const AdvancerKitI = harden({
42
57
  advancer: M.interface('AdvancerI', {
43
- handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(),
58
+ handleTransactionEvent: M.callWhen(EvidenceWithRiskShape).returns(),
59
+ setIntermediateRecipient: M.call(ChainAddressShape).returns(),
44
60
  }),
45
61
  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(),
62
+ onFulfilled: M.call(M.undefined(), AdvancerVowCtxShape).returns(VowShape),
63
+ onRejected: M.call(M.error(), AdvancerVowCtxShape).returns(),
56
64
  }),
57
65
  transferHandler: M.interface('TransferHandlerI', {
58
66
  // TODO confirm undefined, and not bigint (sequence)
59
- onFulfilled: M.call(M.undefined(), {
60
- amount: AmountShape,
61
- destination: ChainAddressShape,
62
- }).returns(M.undefined()),
63
- onRejected: M.call(M.error(), {
64
- amount: AmountShape,
65
- destination: ChainAddressShape,
66
- }).returns(M.undefined()),
67
+ onFulfilled: M.call(M.undefined(), AdvancerVowCtxShape).returns(
68
+ M.undefined(),
69
+ ),
70
+ onRejected: M.call(M.error(), AdvancerVowCtxShape).returns(M.undefined()),
67
71
  }),
68
72
  });
69
73
 
74
+ /**
75
+ * @typedef {{
76
+ * fullAmount: NatAmount;
77
+ * advanceAmount: NatAmount;
78
+ * destination: ChainAddress;
79
+ * forwardingAddress: NobleAddress;
80
+ * txHash: EvmHash;
81
+ * }} AdvancerVowCtx
82
+ */
83
+
70
84
  /**
71
85
  * @param {Zone} zone
72
86
  * @param {AdvancerKitPowers} caps
@@ -82,7 +96,7 @@ export const prepareAdvancerKit = (
82
96
  usdc,
83
97
  vowTools: { watch, when },
84
98
  zcf,
85
- } = /** @type {AdvancerKitPowers} */ ({}),
99
+ },
86
100
  ) => {
87
101
  assertAllDefined({
88
102
  chainHub,
@@ -100,11 +114,19 @@ export const prepareAdvancerKit = (
100
114
  AdvancerKitI,
101
115
  /**
102
116
  * @param {{
117
+ * notifyFacet: import('./settler.js').SettlerKit['notify'];
103
118
  * borrowerFacet: LiquidityPoolKit['borrower'];
104
119
  * poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
120
+ * settlementAddress: ChainAddress;
121
+ * intermediateRecipient?: ChainAddress;
105
122
  * }} config
106
123
  */
107
- config => harden(config),
124
+ config =>
125
+ harden({
126
+ ...config,
127
+ // make sure the state record has this property, perhaps with an undefined value
128
+ intermediateRecipient: config.intermediateRecipient,
129
+ }),
108
130
  {
109
131
  advancer: {
110
132
  /**
@@ -115,129 +137,153 @@ export const prepareAdvancerKit = (
115
137
  * `StatusManager` - so we don't need to concern ourselves with
116
138
  * preserving the vow chain for callers.
117
139
  *
118
- * @param {CctpTxEvidence} evidence
140
+ * @param {EvidenceWithRisk} evidenceWithRisk
119
141
  */
120
- async handleTransactionEvent(evidence) {
142
+ async handleTransactionEvent({ evidence, risk }) {
121
143
  await null;
122
144
  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);
145
+ if (statusManager.hasBeenObserved(evidence)) {
146
+ log('txHash already seen:', evidence.txHash);
143
147
  return;
144
148
  }
145
149
 
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());
150
+ if (risk.risksIdentified?.length) {
151
+ log('risks identified, skipping advance');
152
+ statusManager.skipAdvance(evidence, risk.risksIdentified);
154
153
  return;
155
154
  }
156
155
 
157
- 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());
156
+ const { borrowerFacet, poolAccount, settlementAddress } =
157
+ this.state;
158
+ const { recipientAddress } = evidence.aux;
159
+ const decoded = decodeAddressHook(recipientAddress);
160
+ mustMatch(decoded, AddressHookShape);
161
+ if (decoded.baseAddress !== settlementAddress.value) {
162
+ throw Fail`⚠️ baseAddress of address hook ${q(decoded.baseAddress)} does not match the expected address ${q(settlementAddress.value)}`;
167
163
  }
164
+ const { EUD } = /** @type {AddressHook['query']} */ (decoded.query);
165
+ log(`decoded EUD: ${EUD}`);
166
+ // throws if the bech32 prefix is not found
167
+ const destination = chainHub.makeChainAddress(EUD);
168
+
169
+ const fullAmount = toAmount(evidence.tx.amount);
170
+ // throws if requested does not exceed fees
171
+ const advanceAmount = feeTools.calculateAdvance(fullAmount);
172
+
173
+ const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit();
174
+ // throws if the pool has insufficient funds
175
+ borrowerFacet.borrow(tmpSeat, advanceAmount);
176
+
177
+ // this cannot throw since `.isSeen()` is called in the same turn
178
+ statusManager.advance(evidence);
168
179
 
169
180
  const depositV = localTransfer(
170
181
  tmpSeat,
171
182
  // @ts-expect-error LocalAccountMethods vs OrchestrationAccount
172
183
  poolAccount,
173
- amountKWR,
184
+ harden({ USDC: advanceAmount }),
174
185
  );
175
186
  void watch(depositV, this.facets.depositHandler, {
176
- amount: advanceAmount,
187
+ advanceAmount,
177
188
  destination,
189
+ forwardingAddress: evidence.tx.forwardingAddress,
190
+ fullAmount,
178
191
  tmpSeat,
192
+ txHash: evidence.txHash,
179
193
  });
180
- } catch (e) {
181
- log('Advancer error:', q(e).toString());
194
+ } catch (error) {
195
+ log('Advancer error:', error);
182
196
  statusManager.observe(evidence);
183
197
  }
184
198
  },
199
+ /** @param {ChainAddress} intermediateRecipient */
200
+ setIntermediateRecipient(intermediateRecipient) {
201
+ this.state.intermediateRecipient = intermediateRecipient;
202
+ },
185
203
  },
186
204
  depositHandler: {
187
205
  /**
188
206
  * @param {undefined} result
189
- * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx
207
+ * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
190
208
  */
191
- onFulfilled(result, { amount, destination }) {
192
- const { poolAccount } = this.state;
193
- const transferV = E(poolAccount).transfer(destination, {
194
- denom: usdc.denom,
195
- value: amount.value,
196
- });
209
+ onFulfilled(result, ctx) {
210
+ const { poolAccount, intermediateRecipient } = this.state;
211
+ const { destination, advanceAmount, ...detail } = ctx;
212
+ const transferV = E(poolAccount).transfer(
213
+ destination,
214
+ { denom: usdc.denom, value: advanceAmount.value },
215
+ { forwardOpts: { intermediateRecipient } },
216
+ );
197
217
  return watch(transferV, this.facets.transferHandler, {
198
218
  destination,
199
- amount,
219
+ advanceAmount,
220
+ ...detail,
200
221
  });
201
222
  },
202
223
  /**
224
+ * We do not expect this to be a common failure. it should only occur
225
+ * if USDC is not registered in vbank or the tmpSeat has less than
226
+ * `advanceAmount`.
227
+ *
228
+ * If we do hit this path, we return funds to the Liquidity Pool and
229
+ * notify of Advancing failure.
230
+ *
203
231
  * @param {Error} error
204
- * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx
232
+ * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
205
233
  */
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
234
+ onRejected(error, { tmpSeat, advanceAmount, ...restCtx }) {
211
235
  log(
212
- 'TODO live payment on seat to return to LP',
213
- q(tmpSeat).toString(),
236
+ '⚠️ deposit to localOrchAccount failed, attempting to return payment to LP',
237
+ error,
214
238
  );
239
+ try {
240
+ const { borrowerFacet, notifyFacet } = this.state;
241
+ notifyFacet.notifyAdvancingResult(restCtx, false);
242
+ borrowerFacet.returnToPool(tmpSeat, advanceAmount);
243
+ } catch (e) {
244
+ log('🚨 deposit to localOrchAccount failure recovery failed', e);
245
+ }
215
246
  },
216
247
  },
217
248
  transferHandler: {
218
249
  /**
219
- * @param {undefined} result TODO confirm this is not a bigint (sequence)
220
- * @param {{ destination: ChainAddress; amount: NatAmount; }} ctx
250
+ * @param {unknown} result TODO confirm this is not a bigint (sequence)
251
+ * @param {AdvancerVowCtx} ctx
221
252
  */
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(),
228
- );
253
+ onFulfilled(result, ctx) {
254
+ const { notifyFacet } = this.state;
255
+ const { advanceAmount, destination, ...detail } = ctx;
256
+ log('Advance transfer fulfilled', {
257
+ advanceAmount,
258
+ destination,
259
+ result,
260
+ });
261
+ // During development, due to a bug, this call threw.
262
+ // The failure was silent (no diagnostics) due to:
263
+ // - #10576 Vows do not report unhandled rejections
264
+ // For now, the advancer kit relies on consistency between
265
+ // notifyFacet, statusManager, and callers of handleTransactionEvent().
266
+ // TODO: revisit #10576 during #10510
267
+ notifyFacet.notifyAdvancingResult({ destination, ...detail }, true);
229
268
  },
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());
269
+ /**
270
+ * @param {Error} error
271
+ * @param {AdvancerVowCtx} ctx
272
+ */
273
+ onRejected(error, ctx) {
274
+ const { notifyFacet } = this.state;
275
+ log('Advance transfer rejected', error);
276
+ notifyFacet.notifyAdvancingResult(ctx, false);
234
277
  },
235
278
  },
236
279
  },
237
280
  {
238
281
  stateShape: harden({
282
+ notifyFacet: M.remotable(),
239
283
  borrowerFacet: M.remotable(),
240
284
  poolAccount: M.remotable(),
285
+ intermediateRecipient: M.opt(ChainAddressShape),
286
+ settlementAddress: M.opt(ChainAddressShape),
241
287
  }),
242
288
  },
243
289
  );
@@ -1,4 +1,4 @@
1
- import { AmountMath, AmountShape } from '@agoric/ertp';
1
+ import { AmountMath } from '@agoric/ertp';
2
2
  import {
3
3
  makeRecorderTopic,
4
4
  TopicsRecordShape,
@@ -28,7 +28,7 @@ import {
28
28
  * @import {PoolStats} from '../types.js';
29
29
  */
30
30
 
31
- const { add, isEqual, makeEmpty } = AmountMath;
31
+ const { add, isEqual, isGTE, makeEmpty } = AmountMath;
32
32
 
33
33
  /** @param {Brand} brand */
34
34
  const makeDust = brand => AmountMath.make(brand, 1n);
@@ -84,11 +84,8 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
84
84
  'Liquidity Pool',
85
85
  {
86
86
  borrower: M.interface('borrower', {
87
- getBalance: M.call().returns(AmountShape),
88
- borrow: M.call(
89
- SeatShape,
90
- harden({ USDC: makeNatAmountShape(USDC, 1n) }),
91
- ).returns(),
87
+ borrow: M.call(SeatShape, makeNatAmountShape(USDC, 1n)).returns(),
88
+ returnToPool: M.call(SeatShape, makeNatAmountShape(USDC, 1n)).returns(),
92
89
  }),
93
90
  repayer: M.interface('repayer', {
94
91
  repay: M.call(
@@ -152,38 +149,50 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
152
149
  },
153
150
  {
154
151
  borrower: {
155
- getBalance() {
156
- const { poolSeat } = this.state;
157
- return poolSeat.getAmountAllocated('USDC', USDC);
158
- },
159
152
  /**
160
153
  * @param {ZCFSeat} toSeat
161
- * @param {{ USDC: Amount<'nat'>}} amountKWR
154
+ * @param {Amount<'nat'>} amount
162
155
  */
163
- borrow(toSeat, amountKWR) {
156
+ borrow(toSeat, amount) {
164
157
  const { encumberedBalance, poolSeat, poolStats } = this.state;
165
158
 
166
159
  // Validate amount is available in pool
167
160
  const post = borrowCalc(
168
- amountKWR.USDC,
161
+ amount,
169
162
  poolSeat.getAmountAllocated('USDC', USDC),
170
163
  encumberedBalance,
171
164
  poolStats,
172
165
  );
173
166
 
174
167
  // COMMIT POINT
175
- try {
176
- zcf.atomicRearrange(harden([[poolSeat, toSeat, amountKWR]]));
177
- } catch (cause) {
178
- const reason = Error('🚨 cannot commit borrow', { cause });
179
- console.error(reason.message, cause);
180
- zcf.shutdownWithFailure(reason);
181
- }
168
+ // UNTIL #10684: ability to terminate an incarnation w/o terminating the contract
169
+ zcf.atomicRearrange(harden([[poolSeat, toSeat, { USDC: amount }]]));
182
170
 
183
171
  Object.assign(this.state, post);
184
172
  this.facets.external.publishPoolMetrics();
185
173
  },
186
- // TODO method to repay failed `LOA.deposit()`
174
+ /**
175
+ * If something fails during advance, return funds to the pool.
176
+ *
177
+ * @param {ZCFSeat} borrowSeat
178
+ * @param {Amount<'nat'>} amount
179
+ */
180
+ returnToPool(borrowSeat, amount) {
181
+ const { zcfSeat: repaySeat } = zcf.makeEmptySeatKit();
182
+ const returnAmounts = harden({
183
+ Principal: amount,
184
+ PoolFee: makeEmpty(USDC),
185
+ ContractFee: makeEmpty(USDC),
186
+ });
187
+ const borrowSeatAllocation = borrowSeat.getCurrentAllocation();
188
+ isGTE(borrowSeatAllocation.USDC, amount) ||
189
+ Fail`⚠️ borrowSeatAllocation ${q(borrowSeatAllocation)} less than amountKWR ${q(amount)}`;
190
+ // arrange payments in a format repay is expecting
191
+ zcf.atomicRearrange(
192
+ harden([[borrowSeat, repaySeat, { USDC: amount }, returnAmounts]]),
193
+ );
194
+ return this.facets.repayer.repay(repaySeat, returnAmounts);
195
+ },
187
196
  },
188
197
  repayer: {
189
198
  /**
@@ -213,23 +222,18 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
213
222
  const { ContractFee, ...rest } = amounts;
214
223
 
215
224
  // COMMIT POINT
216
- try {
217
- zcf.atomicRearrange(
218
- harden([
219
- [
220
- fromSeat,
221
- poolSeat,
222
- rest,
223
- { USDC: add(amounts.PoolFee, amounts.Principal) },
224
- ],
225
- [fromSeat, feeSeat, { ContractFee }, { USDC: ContractFee }],
226
- ]),
227
- );
228
- } catch (cause) {
229
- const reason = Error('🚨 cannot commit repay', { cause });
230
- console.error(reason.message, cause);
231
- zcf.shutdownWithFailure(reason);
232
- }
225
+ // UNTIL #10684: ability to terminate an incarnation w/o terminating the contract
226
+ zcf.atomicRearrange(
227
+ harden([
228
+ [
229
+ fromSeat,
230
+ poolSeat,
231
+ rest,
232
+ { USDC: add(amounts.PoolFee, amounts.Principal) },
233
+ ],
234
+ [fromSeat, feeSeat, { ContractFee }, { USDC: ContractFee }],
235
+ ]),
236
+ );
233
237
 
234
238
  Object.assign(this.state, post);
235
239
  this.facets.external.publishPoolMetrics();
@@ -264,9 +268,8 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
264
268
  const post = depositCalc(shareWorth, proposal);
265
269
 
266
270
  // COMMIT POINT
267
-
271
+ const mint = shareMint.mintGains(post.payouts);
268
272
  try {
269
- const mint = shareMint.mintGains(post.payouts);
270
273
  this.state.shareWorth = post.shareWorth;
271
274
  zcf.atomicRearrange(
272
275
  harden([
@@ -276,12 +279,12 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
276
279
  [mint, lp, post.payouts],
277
280
  ]),
278
281
  );
282
+ } catch (cause) {
283
+ // UNTIL #10684: ability to terminate an incarnation w/o terminating the contract
284
+ throw new Error('🚨 cannot commit deposit', { cause });
285
+ } finally {
279
286
  lp.exit();
280
287
  mint.exit();
281
- } catch (cause) {
282
- const reason = Error('🚨 cannot commit deposit', { cause });
283
- console.error(reason.message, cause);
284
- zcf.shutdownWithFailure(reason);
285
288
  }
286
289
  external.publishPoolMetrics();
287
290
  },
@@ -301,7 +304,6 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
301
304
  const post = withdrawCalc(shareWorth, proposal);
302
305
 
303
306
  // COMMIT POINT
304
-
305
307
  try {
306
308
  this.state.shareWorth = post.shareWorth;
307
309
  zcf.atomicRearrange(
@@ -313,12 +315,12 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
313
315
  ]),
314
316
  );
315
317
  shareMint.burnLosses(proposal.give, burn);
318
+ } catch (cause) {
319
+ // UNTIL #10684: ability to terminate an incarnation w/o terminating the contract
320
+ throw new Error('🚨 cannot commit withdraw', { cause });
321
+ } finally {
316
322
  lp.exit();
317
323
  burn.exit();
318
- } catch (cause) {
319
- const reason = Error('🚨 cannot commit withdraw', { cause });
320
- console.error(reason.message, cause);
321
- zcf.shutdownWithFailure(reason);
322
324
  }
323
325
  external.publishPoolMetrics();
324
326
  },
@@ -1,18 +1,18 @@
1
1
  import { makeTracer } from '@agoric/internal';
2
2
  import { Fail } from '@endo/errors';
3
3
  import { M } from '@endo/patterns';
4
- import { CctpTxEvidenceShape } from '../type-guards.js';
4
+ import { CctpTxEvidenceShape, RiskAssessmentShape } from '../type-guards.js';
5
5
 
6
6
  const trace = makeTracer('TxOperator');
7
7
 
8
8
  /**
9
9
  * @import {Zone} from '@agoric/zone';
10
- * @import {CctpTxEvidence} from '../types.js';
10
+ * @import {CctpTxEvidence, RiskAssessment} from '../types.js';
11
11
  */
12
12
 
13
13
  /**
14
14
  * @typedef {object} OperatorPowers
15
- * @property {(evidence: CctpTxEvidence, operatorKit: OperatorKit) => void} submitEvidence
15
+ * @property {(evidence: CctpTxEvidence, riskAssessment: RiskAssessment, operatorId: string) => void} attest
16
16
  */
17
17
 
18
18
  /**
@@ -31,11 +31,15 @@ const OperatorKitI = {
31
31
  }),
32
32
 
33
33
  invitationMakers: M.interface('InvitationMakers', {
34
- SubmitEvidence: M.call(CctpTxEvidenceShape).returns(M.promise()),
34
+ SubmitEvidence: M.call(CctpTxEvidenceShape)
35
+ .optional(RiskAssessmentShape)
36
+ .returns(M.promise()),
35
37
  }),
36
38
 
37
39
  operator: M.interface('Operator', {
38
- submitEvidence: M.call(CctpTxEvidenceShape).returns(M.promise()),
40
+ submitEvidence: M.call(CctpTxEvidenceShape)
41
+ .optional(RiskAssessmentShape)
42
+ .returns(),
39
43
  getStatus: M.call().returns(M.record()),
40
44
  }),
41
45
  };
@@ -81,13 +85,14 @@ export const prepareOperatorKit = (zone, staticPowers) =>
81
85
  * fluxAggregator contract used for price oracles.
82
86
  *
83
87
  * @param {CctpTxEvidence} evidence
88
+ * @param {RiskAssessment} [riskAssessment]
84
89
  * @returns {Promise<Invitation>}
85
90
  */
86
- async SubmitEvidence(evidence) {
91
+ async SubmitEvidence(evidence, riskAssessment) {
87
92
  const { operator } = this.facets;
88
93
  // TODO(bootstrap integration): cause this call to throw and confirm that it
89
94
  // shows up in the the smart-wallet UpdateRecord `error` property
90
- await operator.submitEvidence(evidence);
95
+ operator.submitEvidence(evidence, riskAssessment);
91
96
  return staticPowers.makeInertInvitation(
92
97
  'evidence was pushed in the invitation maker call',
93
98
  );
@@ -98,12 +103,13 @@ export const prepareOperatorKit = (zone, staticPowers) =>
98
103
  * submit evidence from this operator
99
104
  *
100
105
  * @param {CctpTxEvidence} evidence
106
+ * @param {RiskAssessment} [riskAssessment]
107
+ * @returns {void}
101
108
  */
102
- async submitEvidence(evidence) {
109
+ submitEvidence(evidence, riskAssessment = {}) {
103
110
  const { state } = this;
104
111
  !state.disabled || Fail`submitEvidence for disabled operator`;
105
- const result = state.powers.submitEvidence(evidence, this.facets);
106
- return result;
112
+ state.powers.attest(evidence, riskAssessment, state.operatorId);
107
113
  },
108
114
  /** @returns {OperatorStatus} */
109
115
  getStatus() {