@agoric/fast-usdc 0.2.0-u19.2 → 0.2.0-u20.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,369 +0,0 @@
1
- import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
2
- import { AmountMath } from '@agoric/ertp';
3
- import { assertAllDefined, makeTracer } from '@agoric/internal';
4
- import { AnyNatAmountShape, ChainAddressShape } from '@agoric/orchestration';
5
- import { pickFacet } from '@agoric/vat-data';
6
- import { VowShape } from '@agoric/vow';
7
- import { E } from '@endo/far';
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';
15
- import { makeFeeTools } from '../utils/fees.js';
16
-
17
- /**
18
- * @import {HostInterface} from '@agoric/async-flow';
19
- * @import {Amount, Brand} from '@agoric/ertp';
20
- * @import {TypedPattern} from '@agoric/internal'
21
- * @import {NatAmount} from '@agoric/ertp';
22
- * @import {ChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration';
23
- * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
24
- * @import {VowTools} from '@agoric/vow';
25
- * @import {Zone} from '@agoric/zone';
26
- * @import {AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js';
27
- * @import {StatusManager} from './status-manager.js';
28
- * @import {LiquidityPoolKit} from './liquidity-pool.js';
29
- */
30
-
31
- /**
32
- * @typedef {{
33
- * chainHub: ChainHub;
34
- * feeConfig: FeeConfig;
35
- * log?: LogFn;
36
- * statusManager: StatusManager;
37
- * usdc: { brand: Brand<'nat'>; denom: Denom; };
38
- * vowTools: VowTools;
39
- * zcf: ZCF;
40
- * zoeTools: ZoeTools;
41
- * }} AdvancerKitPowers
42
- */
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
-
56
- /** type guards internal to the AdvancerKit */
57
- const AdvancerKitI = harden({
58
- advancer: M.interface('AdvancerI', {
59
- handleTransactionEvent: M.callWhen(EvidenceWithRiskShape).returns(),
60
- setIntermediateRecipient: M.call(ChainAddressShape).returns(),
61
- }),
62
- depositHandler: M.interface('DepositHandlerI', {
63
- onFulfilled: M.call(M.undefined(), AdvancerVowCtxShape).returns(VowShape),
64
- onRejected: M.call(M.error(), AdvancerVowCtxShape).returns(),
65
- }),
66
- transferHandler: M.interface('TransferHandlerI', {
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', {
73
- onFulfilled: M.call(M.undefined(), {
74
- advanceAmount: AnyNatAmountShape,
75
- tmpReturnSeat: M.remotable(),
76
- }).returns(M.undefined()),
77
- onRejected: M.call(M.error(), {
78
- advanceAmount: AnyNatAmountShape,
79
- tmpReturnSeat: M.remotable(),
80
- }).returns(M.undefined()),
81
- }),
82
- });
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
-
102
- /**
103
- * @param {Zone} zone
104
- * @param {AdvancerKitPowers} caps
105
- */
106
- export const prepareAdvancerKit = (
107
- zone,
108
- {
109
- chainHub,
110
- feeConfig,
111
- log = makeTracer('Advancer', true),
112
- statusManager,
113
- usdc,
114
- vowTools: { watch, when },
115
- zcf,
116
- zoeTools: { localTransfer, withdrawToSeat },
117
- },
118
- ) => {
119
- assertAllDefined({
120
- chainHub,
121
- feeConfig,
122
- statusManager,
123
- watch,
124
- when,
125
- });
126
- const feeTools = makeFeeTools(feeConfig);
127
- /** @param {bigint} value */
128
- const toAmount = value => AmountMath.make(usdc.brand, value);
129
-
130
- return zone.exoClassKit(
131
- 'Fast USDC Advancer',
132
- AdvancerKitI,
133
- /**
134
- * @param {{
135
- * notifier: import('./settler.js').SettlerKit['notifier'];
136
- * borrower: LiquidityPoolKit['borrower'];
137
- * poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
138
- * settlementAddress: ChainAddress;
139
- * intermediateRecipient?: ChainAddress;
140
- * }} config
141
- */
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
- }),
148
- {
149
- advancer: {
150
- /**
151
- * Must perform a status update for every observed transaction.
152
- *
153
- * We do not expect any callers to depend on the settlement of
154
- * `handleTransactionEvent` - errors caught are communicated to the
155
- * `StatusManager` - so we don't need to concern ourselves with
156
- * preserving the vow chain for callers.
157
- *
158
- * @param {EvidenceWithRisk} evidenceWithRisk
159
- */
160
- async handleTransactionEvent({ evidence, risk }) {
161
- await null;
162
- try {
163
- if (statusManager.hasBeenObserved(evidence)) {
164
- log('txHash already seen:', evidence.txHash);
165
- return;
166
- }
167
-
168
- if (risk.risksIdentified?.length) {
169
- log('risks identified, skipping advance');
170
- statusManager.skipAdvance(evidence, risk.risksIdentified);
171
- return;
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);
196
-
197
- const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit();
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);
203
-
204
- const depositV = localTransfer(
205
- tmpSeat,
206
- // @ts-expect-error LocalAccountMethods vs OrchestrationAccount
207
- poolAccount,
208
- harden({ USDC: advanceAmount }),
209
- );
210
- void watch(depositV, this.facets.depositHandler, {
211
- advanceAmount,
212
- destination,
213
- forwardingAddress: evidence.tx.forwardingAddress,
214
- fullAmount,
215
- tmpSeat,
216
- txHash: evidence.txHash,
217
- });
218
- } catch (error) {
219
- log('Advancer error:', error);
220
- statusManager.skipAdvance(evidence, [error.message]);
221
- }
222
- },
223
- /** @param {ChainAddress} intermediateRecipient */
224
- setIntermediateRecipient(intermediateRecipient) {
225
- this.state.intermediateRecipient = intermediateRecipient;
226
- },
227
- },
228
- depositHandler: {
229
- /**
230
- * @param {undefined} result
231
- * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
232
- */
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({
239
- denom: usdc.denom,
240
- value: advanceAmount.value,
241
- });
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, {
249
- destination,
250
- advanceAmount,
251
- ...detail,
252
- });
253
- },
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
- *
262
- * @param {Error} error
263
- * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
264
- */
265
- onRejected(error, { tmpSeat, advanceAmount, ...restCtx }) {
266
- log(
267
- '⚠️ deposit to localOrchAccount failed, attempting to return payment to LP',
268
- error,
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
- }
278
- },
279
- },
280
- transferHandler: {
281
- /**
282
- * @param {undefined} result
283
- * @param {AdvancerVowCtx} ctx
284
- */
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 }),
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();
338
- },
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();
351
- },
352
- },
353
- },
354
- {
355
- stateShape,
356
- },
357
- );
358
- };
359
- harden(prepareAdvancerKit);
360
-
361
- /**
362
- * @param {Zone} zone
363
- * @param {AdvancerKitPowers} caps
364
- */
365
- export const prepareAdvancer = (zone, caps) => {
366
- const makeAdvancerKit = prepareAdvancerKit(zone, caps);
367
- return pickFacet(makeAdvancerKit, 'advancer');
368
- };
369
- harden(prepareAdvancer);