@agoric/fast-usdc 0.1.1-dev-3cbd11f.0 → 0.1.1-dev-4b5dff2.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,375 +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 {
5
- AnyNatAmountShape,
6
- CosmosChainAddressShape,
7
- } from '@agoric/orchestration';
8
- import { pickFacet } from '@agoric/vat-data';
9
- import { VowShape } from '@agoric/vow';
10
- import { E } from '@endo/far';
11
- import { M, mustMatch } from '@endo/patterns';
12
- import { Fail, q } from '@endo/errors';
13
- import {
14
- AddressHookShape,
15
- EvmHashShape,
16
- EvidenceWithRiskShape,
17
- } from '../type-guards.js';
18
- import { makeFeeTools } from '../utils/fees.js';
19
-
20
- /**
21
- * @import {HostInterface} from '@agoric/async-flow';
22
- * @import {Amount, Brand} from '@agoric/ertp';
23
- * @import {TypedPattern} from '@agoric/internal'
24
- * @import {NatAmount} from '@agoric/ertp';
25
- * @import {CosmosChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration';
26
- * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
27
- * @import {VowTools} from '@agoric/vow';
28
- * @import {Zone} from '@agoric/zone';
29
- * @import {AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js';
30
- * @import {StatusManager} from './status-manager.js';
31
- * @import {LiquidityPoolKit} from './liquidity-pool.js';
32
- */
33
-
34
- /**
35
- * @typedef {{
36
- * chainHub: ChainHub;
37
- * feeConfig: FeeConfig;
38
- * log?: LogFn;
39
- * statusManager: StatusManager;
40
- * usdc: { brand: Brand<'nat'>; denom: Denom; };
41
- * vowTools: VowTools;
42
- * zcf: ZCF;
43
- * zoeTools: ZoeTools;
44
- * }} AdvancerKitPowers
45
- */
46
-
47
- /** @type {TypedPattern<AdvancerVowCtx>} */
48
- const AdvancerVowCtxShape = M.splitRecord(
49
- {
50
- fullAmount: AnyNatAmountShape,
51
- advanceAmount: AnyNatAmountShape,
52
- destination: CosmosChainAddressShape,
53
- forwardingAddress: M.string(),
54
- txHash: EvmHashShape,
55
- },
56
- { tmpSeat: M.remotable() },
57
- );
58
-
59
- /** type guards internal to the AdvancerKit */
60
- const AdvancerKitI = harden({
61
- advancer: M.interface('AdvancerI', {
62
- handleTransactionEvent: M.callWhen(EvidenceWithRiskShape).returns(),
63
- setIntermediateRecipient: M.call(CosmosChainAddressShape).returns(),
64
- }),
65
- depositHandler: M.interface('DepositHandlerI', {
66
- onFulfilled: M.call(M.undefined(), AdvancerVowCtxShape).returns(VowShape),
67
- onRejected: M.call(M.error(), AdvancerVowCtxShape).returns(),
68
- }),
69
- transferHandler: M.interface('TransferHandlerI', {
70
- onFulfilled: M.call(M.undefined(), AdvancerVowCtxShape).returns(
71
- M.undefined(),
72
- ),
73
- onRejected: M.call(M.error(), AdvancerVowCtxShape).returns(M.undefined()),
74
- }),
75
- withdrawHandler: M.interface('WithdrawHandlerI', {
76
- onFulfilled: M.call(M.undefined(), {
77
- advanceAmount: AnyNatAmountShape,
78
- tmpReturnSeat: M.remotable(),
79
- }).returns(M.undefined()),
80
- onRejected: M.call(M.error(), {
81
- advanceAmount: AnyNatAmountShape,
82
- tmpReturnSeat: M.remotable(),
83
- }).returns(M.undefined()),
84
- }),
85
- });
86
-
87
- /**
88
- * @typedef {{
89
- * fullAmount: NatAmount;
90
- * advanceAmount: NatAmount;
91
- * destination: CosmosChainAddress;
92
- * forwardingAddress: NobleAddress;
93
- * txHash: EvmHash;
94
- * }} AdvancerVowCtx
95
- */
96
-
97
- export const stateShape = harden({
98
- notifier: M.remotable(),
99
- borrower: M.remotable(),
100
- poolAccount: M.remotable(),
101
- intermediateRecipient: M.opt(CosmosChainAddressShape),
102
- settlementAddress: M.opt(CosmosChainAddressShape),
103
- });
104
-
105
- /**
106
- * @param {Zone} zone
107
- * @param {AdvancerKitPowers} caps
108
- */
109
- export const prepareAdvancerKit = (
110
- zone,
111
- {
112
- chainHub,
113
- feeConfig,
114
- log = makeTracer('Advancer', true),
115
- statusManager,
116
- usdc,
117
- vowTools: { watch, when },
118
- zcf,
119
- zoeTools: { localTransfer, withdrawToSeat },
120
- },
121
- ) => {
122
- assertAllDefined({
123
- chainHub,
124
- feeConfig,
125
- statusManager,
126
- watch,
127
- when,
128
- });
129
- const feeTools = makeFeeTools(feeConfig);
130
- /** @param {bigint} value */
131
- const toAmount = value => AmountMath.make(usdc.brand, value);
132
-
133
- return zone.exoClassKit(
134
- 'Fast USDC Advancer',
135
- AdvancerKitI,
136
- /**
137
- * @param {{
138
- * notifier: import('./settler.js').SettlerKit['notifier'];
139
- * borrower: LiquidityPoolKit['borrower'];
140
- * poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
141
- * settlementAddress: CosmosChainAddress;
142
- * intermediateRecipient?: CosmosChainAddress;
143
- * }} config
144
- */
145
- config =>
146
- harden({
147
- ...config,
148
- // make sure the state record has this property, perhaps with an undefined value
149
- intermediateRecipient: config.intermediateRecipient,
150
- }),
151
- {
152
- advancer: {
153
- /**
154
- * Must perform a status update for every observed transaction.
155
- *
156
- * We do not expect any callers to depend on the settlement of
157
- * `handleTransactionEvent` - errors caught are communicated to the
158
- * `StatusManager` - so we don't need to concern ourselves with
159
- * preserving the vow chain for callers.
160
- *
161
- * @param {EvidenceWithRisk} evidenceWithRisk
162
- */
163
- async handleTransactionEvent({ evidence, risk }) {
164
- await null;
165
- try {
166
- if (statusManager.hasBeenObserved(evidence)) {
167
- log('txHash already seen:', evidence.txHash);
168
- return;
169
- }
170
-
171
- if (risk.risksIdentified?.length) {
172
- log('risks identified, skipping advance');
173
- statusManager.skipAdvance(evidence, risk.risksIdentified);
174
- return;
175
- }
176
- const { settlementAddress } = this.state;
177
- const { recipientAddress } = evidence.aux;
178
- const decoded = decodeAddressHook(recipientAddress);
179
- mustMatch(decoded, AddressHookShape);
180
- if (decoded.baseAddress !== settlementAddress.value) {
181
- throw Fail`⚠️ baseAddress of address hook ${q(decoded.baseAddress)} does not match the expected address ${q(settlementAddress.value)}`;
182
- }
183
- const { EUD } = decoded.query;
184
- log(`decoded EUD: ${EUD}`);
185
- assert.typeof(EUD, 'string');
186
- // throws if the bech32 prefix is not found
187
- const destination = chainHub.makeChainAddress(EUD);
188
-
189
- const fullAmount = toAmount(evidence.tx.amount);
190
- const { borrower, notifier, poolAccount } = this.state;
191
- // do not advance if we've already received a mint/settlement
192
- const mintedEarly = notifier.checkMintedEarly(
193
- evidence,
194
- destination,
195
- );
196
- if (mintedEarly) return;
197
-
198
- // throws if requested does not exceed fees
199
- const advanceAmount = feeTools.calculateAdvance(fullAmount);
200
-
201
- const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit();
202
- // throws if the pool has insufficient funds
203
- borrower.borrow(tmpSeat, advanceAmount);
204
-
205
- // this cannot throw since `.isSeen()` is called in the same turn
206
- statusManager.advance(evidence);
207
-
208
- const depositV = localTransfer(
209
- tmpSeat,
210
- // @ts-expect-error LocalAccountMethods vs OrchestrationAccount
211
- poolAccount,
212
- harden({ USDC: advanceAmount }),
213
- );
214
- // WARNING: this must never reject, see handler @throws {never} below
215
- // void not enforced by linter until #10627 no-floating-vows
216
- void watch(depositV, this.facets.depositHandler, {
217
- advanceAmount,
218
- destination,
219
- forwardingAddress: evidence.tx.forwardingAddress,
220
- fullAmount,
221
- tmpSeat,
222
- txHash: evidence.txHash,
223
- });
224
- } catch (error) {
225
- log('Advancer error:', error);
226
- statusManager.skipAdvance(evidence, [error.message]);
227
- }
228
- },
229
- /** @param {CosmosChainAddress} intermediateRecipient */
230
- setIntermediateRecipient(intermediateRecipient) {
231
- this.state.intermediateRecipient = intermediateRecipient;
232
- },
233
- },
234
- depositHandler: {
235
- /**
236
- * @param {undefined} result
237
- * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
238
- * @throws {never} WARNING: this function must not throw, because user funds are at risk
239
- */
240
- onFulfilled(result, ctx) {
241
- const { poolAccount, intermediateRecipient, settlementAddress } =
242
- this.state;
243
- const { destination, advanceAmount, tmpSeat, ...detail } = ctx;
244
- tmpSeat.exit();
245
- const amount = harden({
246
- denom: usdc.denom,
247
- value: advanceAmount.value,
248
- });
249
- const transferOrSendV =
250
- destination.chainId === settlementAddress.chainId
251
- ? E(poolAccount).send(destination, amount)
252
- : E(poolAccount).transfer(destination, amount, {
253
- forwardOpts: { intermediateRecipient },
254
- });
255
- return watch(transferOrSendV, this.facets.transferHandler, {
256
- destination,
257
- advanceAmount,
258
- ...detail,
259
- });
260
- },
261
- /**
262
- * We do not expect this to be a common failure. it should only occur
263
- * if USDC is not registered in vbank or the tmpSeat has less than
264
- * `advanceAmount`.
265
- *
266
- * If we do hit this path, we return funds to the Liquidity Pool and
267
- * notify of Advancing failure.
268
- *
269
- * @param {Error} error
270
- * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
271
- * @throws {never} WARNING: this function must not throw, because user funds are at risk
272
- */
273
- onRejected(error, { tmpSeat, advanceAmount, ...restCtx }) {
274
- log(
275
- '⚠️ deposit to localOrchAccount failed, attempting to return payment to LP',
276
- error,
277
- );
278
- try {
279
- const { borrower, notifier } = this.state;
280
- notifier.notifyAdvancingResult(restCtx, false);
281
- borrower.returnToPool(tmpSeat, advanceAmount);
282
- tmpSeat.exit();
283
- } catch (e) {
284
- log('🚨 deposit to localOrchAccount failure recovery failed', e);
285
- }
286
- },
287
- },
288
- transferHandler: {
289
- /**
290
- * @param {undefined} result
291
- * @param {AdvancerVowCtx} ctx
292
- * @throws {never} WARNING: this function must not throw, because user funds are at risk
293
- */
294
- onFulfilled(result, ctx) {
295
- const { notifier } = this.state;
296
- const { advanceAmount, destination, ...detail } = ctx;
297
- log('Advance succeeded', { advanceAmount, destination });
298
- notifier.notifyAdvancingResult({ destination, ...detail }, true);
299
- },
300
- /**
301
- * @param {Error} error
302
- * @param {AdvancerVowCtx} ctx
303
- */
304
- onRejected(error, ctx) {
305
- const { notifier, poolAccount } = this.state;
306
- log('Advance failed', error);
307
- const { advanceAmount, ...restCtx } = ctx;
308
- notifier.notifyAdvancingResult(restCtx, false);
309
- const { zcfSeat: tmpReturnSeat } = zcf.makeEmptySeatKit();
310
- const withdrawV = withdrawToSeat(
311
- // @ts-expect-error LocalAccountMethods vs OrchestrationAccount
312
- poolAccount,
313
- tmpReturnSeat,
314
- harden({ USDC: advanceAmount }),
315
- );
316
- // WARNING: this must never reject, see handler @throws {never} below
317
- // void not enforced by linter until #10627 no-floating-vows
318
- void watch(withdrawV, this.facets.withdrawHandler, {
319
- advanceAmount,
320
- tmpReturnSeat,
321
- });
322
- },
323
- },
324
- withdrawHandler: {
325
- /**
326
- *
327
- * @param {undefined} result
328
- * @param {{ advanceAmount: Amount<'nat'>; tmpReturnSeat: ZCFSeat; }} ctx
329
- * @throws {never} WARNING: this function must not throw, because user funds are at risk
330
- */
331
- onFulfilled(result, { advanceAmount, tmpReturnSeat }) {
332
- const { borrower } = this.state;
333
- try {
334
- borrower.returnToPool(tmpReturnSeat, advanceAmount);
335
- } catch (e) {
336
- // If we reach here, the unused advance funds will remain in `tmpReturnSeat`
337
- // and must be retrieved from recovery sets.
338
- log(
339
- `🚨 return ${q(advanceAmount)} to pool failed. funds remain on "tmpReturnSeat"`,
340
- e,
341
- );
342
- }
343
- },
344
- /**
345
- * @param {Error} error
346
- * @param {{ advanceAmount: Amount<'nat'>; tmpReturnSeat: ZCFSeat; }} ctx
347
- * @throws {never} WARNING: this function must not throw, because user funds are at risk
348
- */
349
- onRejected(error, { advanceAmount, tmpReturnSeat }) {
350
- log(
351
- `🚨 withdraw ${q(advanceAmount)} from "poolAccount" to return to pool failed`,
352
- error,
353
- );
354
- // If we reach here, the unused advance funds will remain in the `poolAccount`.
355
- // A contract update will be required to return them to the LiquidityPool.
356
- tmpReturnSeat.exit();
357
- },
358
- },
359
- },
360
- {
361
- stateShape,
362
- },
363
- );
364
- };
365
- harden(prepareAdvancerKit);
366
-
367
- /**
368
- * @param {Zone} zone
369
- * @param {AdvancerKitPowers} caps
370
- */
371
- export const prepareAdvancer = (zone, caps) => {
372
- const makeAdvancerKit = prepareAdvancerKit(zone, caps);
373
- return pickFacet(makeAdvancerKit, 'advancer');
374
- };
375
- harden(prepareAdvancer);