Skip to content

Commit

Permalink
feat: chain-agnostic transfer API
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Feb 19, 2025
1 parent 0b35d96 commit c4096c3
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 37 deletions.
6 changes: 3 additions & 3 deletions packages/fast-usdc/src/exos/settler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
/**
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
* @import {Amount, Brand, NatValue, Payment} from '@agoric/ertp';
* @import {Denom, OrchestrationAccount, ChainHub, CosmosChainAddress} from '@agoric/orchestration';
* @import {AccountId, Denom, OrchestrationAccount, ChainHub, CosmosChainAddress} from '@agoric/orchestration';
* @import {WithdrawToSeat} from '@agoric/orchestration/src/utils/zoe-tools'
* @import {IBCChannelID, IBCPacket, VTransferIBCEvent} from '@agoric/vats';
* @import {Zone} from '@agoric/zone';
Expand Down Expand Up @@ -340,10 +340,10 @@ export const prepareSettler = (
forward(txHash, fullValue, EUD) {
const { settlementAccount, intermediateRecipient } = this.state;

/** @type {AccountId | null} */
const dest = (() => {
try {
// @ts-expect-error FIXME support any AccountId
return chainHub.makeChainAddress(EUD);
return chainHub.resolveAccountId(EUD);
} catch (e) {
log('⚠️ forward transfer failed!', e, txHash);
statusManager.forwarded(txHash, false);
Expand Down
22 changes: 5 additions & 17 deletions packages/fast-usdc/test/exos/settler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,7 @@ test('slow path: forward to EUD; remove pending tx', async t => {
t.deepEqual(accounts.settlement.callLog, [
[
'transfer',
{
chainId: 'osmosis-1',
value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
'cosmos:osmosis-1:osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
usdc.units(150),
{
forwardOpts: {
Expand Down Expand Up @@ -443,10 +440,7 @@ test('skip advance: forward to EUD; remove pending tx', async t => {
t.deepEqual(accounts.settlement.callLog, [
[
'transfer',
{
chainId: 'osmosis-1',
value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
'cosmos:osmosis-1:osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
usdc.units(150),
{
forwardOpts: {
Expand Down Expand Up @@ -532,9 +526,7 @@ test('Settlement for unknown transaction (minted early)', async t => {
t.like(accounts.settlement.callLog, [
[
'transfer',
{
value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
'cosmos:osmosis-1:osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
usdc.units(150),
{
forwardOpts: {
Expand Down Expand Up @@ -650,9 +642,7 @@ test('Settlement for Advancing transaction (advance fails)', async t => {
t.like(accounts.settlement.callLog, [
[
'transfer',
{
value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
'cosmos:osmosis-1:osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
usdc.units(150),
{
forwardOpts: {
Expand Down Expand Up @@ -713,9 +703,7 @@ test('slow path, and forward fails (terminal state)', async t => {
t.like(accounts.settlement.callLog, [
[
'transfer',
{
value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
'cosmos:osmosis-1:osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
usdc.units(150),
{
forwardOpts: {
Expand Down
41 changes: 28 additions & 13 deletions packages/orchestration/src/exos/chain-hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ForwardOptsShape,
IBCChannelIDShape,
IBCConnectionInfoShape,
AccountIdableShape,
} from '../typeGuards.js';
import { getBech32Prefix, parseAccountId } from '../utils/address.js';

Expand All @@ -23,7 +24,7 @@ import { getBech32Prefix, parseAccountId } from '../utils/address.js';
* @import {Zone} from '@agoric/zone';
* @import {CosmosAssetInfo, CosmosChainInfo, ForwardInfo, IBCConnectionInfo, IBCMsgTransferOptions, TransferRoute, GoDuration, CosmosAddress} from '../cosmos-api.js';
* @import {ChainInfo, KnownChains} from '../chain-info.js';
* @import {AccountId, CosmosChainAddress, ScopedChainId, Denom, DenomAmount} from '../orchestration-api.js';
* @import {AccountId, CosmosChainAddress, ScopedChainId, Denom, DenomAmount, AccountIdable} from '../orchestration-api.js';
* @import {Remote, TypedPattern} from '@agoric/internal';
*/

Expand Down Expand Up @@ -218,11 +219,7 @@ const ChainHubI = M.interface('ChainHub', {
getDenom: M.call(BrandShape).returns(M.or(M.string(), M.undefined())),
makeChainAddress: M.call(M.string()).returns(CosmosChainAddressShape),
resolveAccountId: M.call(M.string()).returns(M.string()),
makeTransferRoute: M.call(
CosmosChainAddressShape,
DenomAmountShape,
M.string(),
)
makeTransferRoute: M.call(AccountIdableShape, DenomAmountShape, M.string())
.optional(ForwardOptsShape)
.returns(M.or(M.undefined(), TransferRouteShape)),
});
Expand Down Expand Up @@ -510,8 +507,7 @@ export const makeChainHub = (zone, agoricNames, vowTools) => {
return undefined;
},
/**
* @param {AccountId | CosmosAddress} partialId CAIP-10 account ID or a
* Cosmos bech32 address
* @param {string} partialId CAIP-10 account ID or a Cosmos bech32 address
* @returns {AccountId}
* @throws {Error} if chain info not found for bech32Prefix
*/
Expand All @@ -525,15 +521,29 @@ export const makeChainHub = (zone, agoricNames, vowTools) => {
return `${fqChainId}:${parsed.accountAddress}`;
},
/**
* @param {CosmosAddress} address Cosmos bech32 address
* @param {AccountId | CosmosAddress} partialId CAIP-10 account ID or a
* Cosmos bech32 address
* @returns {CosmosChainAddress}
* @throws {Error} if chain info not found for bech32Prefix
*/
makeChainAddress(address) {
const chainId = resolveCosmosChainId(address);
makeChainAddress(partialId) {
const parsed = parseAccountId(partialId);
if (parsed.chainId) {
// that's the CAIP chainId and we want the Cosmos one
const [namespace, reference] = parsed.chainId.split(':');
assert.equal(namespace, 'cosmos');
return harden({
chainId: reference,
value: parsed.accountAddress,
});
}

const chainId = resolveCosmosChainId(
/** @type {CosmosAddress} */ (partialId),
);
return harden({
chainId,
value: address,
value: partialId,
});
},
// TODO document whether this is limited to IBC
Expand All @@ -547,7 +557,7 @@ export const makeChainHub = (zone, agoricNames, vowTools) => {
*
* XXX consider accepting AmountArg #10449
*
* @param {CosmosChainAddress} destination
* @param {AccountIdable} destination
* @param {DenomAmount} denomAmount
* @param {string} srcChainName
* @param {IBCMsgTransferOptions['forwardOpts']} [forwardOpts]
Expand Down Expand Up @@ -576,6 +586,11 @@ export const makeChainHub = (zone, agoricNames, vowTools) => {

const holdingChainId = chainInfos.get(srcChainName).chainId;

destination =
typeof destination === 'string'
? chainHub.makeChainAddress(destination)
: destination;

// asset is transferring to or from the issuing chain, return direct route
if (baseChainId === destination.chainId || baseName === srcChainName) {
// TODO use getConnectionInfo once its sync
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,11 @@ export const prepareCosmosOrchestrationAccountKit = (
transfer(destination, amount, opts) {
trace('transfer', destination, amount, opts);
return asVow(() => {
destination =
typeof destination === 'string'
? chainHub.makeChainAddress(destination)
: destination;

const { helper } = this.facets;
const token = helper.amountToCoin(amount);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { TransferRouteShape } from './chain-hub.js';
/**
* @import {HostOf} from '@agoric/async-flow';
* @import {LocalChain, LocalChainAccount} from '@agoric/vats/src/localchain.js';
* @import {AmountArg, CosmosChainAddress, DenomAmount, IBCMsgTransferOptions, IBCConnectionInfo, OrchestrationAccountCommon, LocalAccountMethods, TransferRoute} from '@agoric/orchestration';
* @import {AmountArg, CosmosChainAddress, DenomAmount, IBCMsgTransferOptions, IBCConnectionInfo, OrchestrationAccountCommon, LocalAccountMethods, TransferRoute, AccountId, AccountIdable} from '@agoric/orchestration';
* @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'.
* @import {Zone} from '@agoric/zone';
* @import {Remote} from '@agoric/internal';
Expand Down Expand Up @@ -671,7 +671,7 @@ export const prepareLocalOrchestrationAccountKit = (
});
},
/**
* @param {CosmosChainAddress} destination
* @param {AccountIdable} destination
* @param {AmountArg} amount an ERTP {@link Amount} or a
* {@link DenomAmount}
* @param {IBCMsgTransferOptions} [opts] if either timeoutHeight or
Expand All @@ -685,6 +685,7 @@ export const prepareLocalOrchestrationAccountKit = (
transfer(destination, amount, opts) {
return asVow(() => {
trace('Transferring funds over IBC');

const denomAmount = coerceDenomAmount(chainHub, amount);

const { forwardOpts, ...rest } = opts ?? {};
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/orchestration-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export interface OrchestrationAccountCommon {
* the transfer is rejected (insufficient funds, timeout)
*/
transfer: (
destination: CosmosChainAddress,
destination: AccountId | CosmosChainAddress,
amount: AmountArg,
opts?: IBCMsgTransferOptions,
) => Promise<void>;
Expand Down
3 changes: 2 additions & 1 deletion packages/orchestration/src/utils/orchestrationAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { VowShape } from '@agoric/vow';
import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/topics.js';
import { M } from '@endo/patterns';
import {
AccountIdableShape,
AmountArgShape,
CosmosChainAddressShape,
DenomAmountShape,
Expand All @@ -25,7 +26,7 @@ export const orchestrationAccountMethods = {
sendAll: M.call(CosmosChainAddressShape, M.arrayOf(AmountArgShape)).returns(
VowShape,
),
transfer: M.call(CosmosChainAddressShape, AmountArgShape)
transfer: M.call(AccountIdableShape, AmountArgShape)
.optional(IBCTransferOptionsShape)
.returns(VowShape),
transferSteps: M.call(AmountArgShape, M.any()).returns(VowShape),
Expand Down

0 comments on commit c4096c3

Please sign in to comment.