Skip to content

Commit 129bb20

Browse files
authored
252 fu cctp (#11174)
refs: https://github.com/Agoric/agoric-private/issues/252 ## Description Give FastUSDC advancer the option to transfer using CCTP. This was in decent working shape before rebasing, but rebasing has upended things. ### Security Considerations Nothing special ### Scaling Considerations Fast USDC doesn't accumulate state long term. ### Documentation Considerations TODO: Probably worth an addition somewhere. Where do we have this level of detail written? ### Testing Considerations There are new tests using the mocks. Needs extensive testing on testnets and live chains. ### Upgrade Considerations upward compatible.
2 parents 2422c58 + b948247 commit 129bb20

File tree

5 files changed

+390
-31
lines changed

5 files changed

+390
-31
lines changed

packages/fast-usdc-contract/src/exos/advancer.ts

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,21 @@ import {
3636
CosmosChainAddressShape,
3737
} from '@agoric/orchestration';
3838
import type { AccountId } from '@agoric/orchestration/src/orchestration-api.js';
39-
import { parseAccountIdArg } from '@agoric/orchestration/src/utils/address.js';
39+
import {
40+
chainOfAccount,
41+
parseAccountId,
42+
parseAccountIdArg,
43+
} from '@agoric/orchestration/src/utils/address.js';
4044
import type { ZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
45+
import { M, mustMatch } from '@agoric/store';
4146
import { pickFacet } from '@agoric/vat-data';
4247
import type { VowTools } from '@agoric/vow';
4348
import { VowShape } from '@agoric/vow';
4449
import type { ZCF, ZCFSeat } from '@agoric/zoe/src/zoeService/zoe.js';
4550
import type { Zone } from '@agoric/zone';
4651
import { Fail, q } from '@endo/errors';
4752
import { E } from '@endo/far';
48-
import { M, mustMatch } from '@agoric/store';
53+
import { makeSupportsCctp } from '../utils/cctp.ts';
4954
import type { LiquidityPoolKit } from './liquidity-pool.js';
5055
import type { SettlerKit } from './settler.js';
5156
import type { StatusManager } from './status-manager.js';
@@ -134,7 +139,7 @@ export const prepareAdvancerKit = (
134139
log = makeTracer('Advancer', true),
135140
statusManager,
136141
usdc,
137-
vowTools: { watch, when },
142+
vowTools: { asVow, watch, when },
138143
zcf,
139144
zoeTools: { localTransfer, withdrawToSeat },
140145
}: AdvancerKitPowers,
@@ -149,6 +154,8 @@ export const prepareAdvancerKit = (
149154
const feeTools = makeFeeTools(feeConfig);
150155
const toAmount = (value: bigint) => AmountMath.make(usdc.brand, value);
151156

157+
const supportsCctp = makeSupportsCctp(chainHub);
158+
152159
return zone.exoClassKit(
153160
'Fast USDC Advancer',
154161
AdvancerKitI,
@@ -178,8 +185,7 @@ export const prepareAdvancerKit = (
178185
*
179186
* @param {EvidenceWithRisk} evidenceWithRisk
180187
*/
181-
async handleTransactionEvent({ evidence, risk }: EvidenceWithRisk) {
182-
await null;
188+
handleTransactionEvent({ evidence, risk }: EvidenceWithRisk) {
183189
try {
184190
if (statusManager.hasBeenObserved(evidence)) {
185191
log('txHash already seen:', evidence.txHash);
@@ -200,8 +206,20 @@ export const prepareAdvancerKit = (
200206
}
201207
const { EUD } = decoded.query;
202208
log(`decoded EUD: ${EUD}`);
203-
// throws if the bech32 prefix is not found
209+
210+
// throws if it's neither CAIP-10 nor bare bech32.
204211
const destination = chainHub.resolveAccountId(EUD);
212+
const accountId = parseAccountId(destination);
213+
214+
// Dest must be a Cosmos account or support CCTP
215+
if (
216+
!(accountId.namespace === 'cosmos' || supportsCctp(destination))
217+
) {
218+
const destChain = chainOfAccount(destination);
219+
statusManager.skipAdvance(evidence, [
220+
`Transfer to ${destChain} not supported.`,
221+
]);
222+
}
205223

206224
const fullAmount = toAmount(evidence.tx.amount);
207225
const { borrower, notifier, poolAccount } = this.state;
@@ -258,28 +276,56 @@ export const prepareAdvancerKit = (
258276
result: undefined,
259277
ctx: AdvancerVowCtx & { tmpSeat: ZCFSeat },
260278
) {
261-
const { poolAccount, settlementAddress } = this.state;
262-
const { destination, advanceAmount, tmpSeat, ...detail } = ctx;
263-
tmpSeat.exit();
264-
const amount = harden({
265-
denom: usdc.denom,
266-
value: advanceAmount.value,
267-
});
268-
const intermediateRecipient = getNobleICA().getAddress();
269-
const accountId = parseAccountIdArg(destination);
279+
return asVow(async () => {
280+
const { poolAccount, settlementAddress } = this.state;
281+
const { tmpSeat, ...vowContext } = ctx;
282+
const { destination, advanceAmount, ...detail } = vowContext;
283+
tmpSeat.exit();
284+
const amount = harden({
285+
denom: usdc.denom,
286+
value: advanceAmount.value,
287+
});
288+
const accountId = parseAccountIdArg(destination);
289+
await null;
270290

271-
assert.equal(accountId.namespace, 'cosmos');
291+
const intermediaryAccount = getNobleICA();
292+
const intermediaryAddress = intermediaryAccount.getAddress();
272293

273-
const transferOrSendV =
274-
accountId.reference === settlementAddress.chainId
275-
? E(poolAccount).send(destination, amount)
276-
: E(poolAccount).transfer(destination, amount, {
277-
forwardOpts: { intermediateRecipient },
278-
});
279-
return watch(transferOrSendV, this.facets.transferHandler, {
280-
destination,
281-
advanceAmount,
282-
...detail,
294+
let transferOrSendV;
295+
if (
296+
accountId.namespace === 'cosmos' &&
297+
accountId.reference === settlementAddress.chainId
298+
) {
299+
// send to recipient on Agoric
300+
transferOrSendV = E(poolAccount).send(destination, amount);
301+
} else if (accountId.namespace === 'cosmos') {
302+
// send via IBC
303+
304+
transferOrSendV = E(poolAccount).transfer(destination, amount, {
305+
forwardOpts: {
306+
intermediateRecipient: intermediaryAddress,
307+
},
308+
});
309+
} else if (supportsCctp(destination)) {
310+
// send USDC via CCTP
311+
312+
await E(poolAccount).transfer(intermediaryAddress, amount);
313+
// assets are on noble, transfer to dest.
314+
315+
transferOrSendV = intermediaryAccount.depositForBurn(
316+
destination,
317+
amount,
318+
);
319+
} else {
320+
// This is supposed to be caught in handleTransactionEvent()
321+
Fail`🚨 can only transfer to Agoric addresses, via IBC, or via CCTP`;
322+
}
323+
324+
return watch(
325+
transferOrSendV,
326+
this.facets.transferHandler,
327+
vowContext,
328+
);
283329
});
284330
},
285331
/**

0 commit comments

Comments
 (0)