Skip to content

Commit

Permalink
feat(local-orchestration-account): support multi-hop pfm transfers
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Dec 3, 2024
1 parent 50b1717 commit c35fac7
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 56 deletions.
1 change: 1 addition & 0 deletions packages/orchestration/src/cosmos-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,5 +401,6 @@ export type TransferRoute = {
}
| {
receiver: string;
forwardInfo?: never;
}
);
6 changes: 3 additions & 3 deletions packages/orchestration/src/exos/chain-hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ export const makeChainHub = (zone, agoricNames, vowTools) => {
// TODO use getConnectionInfo once its sync
const connKey = connectionKey(holdingChainId, destination.chainId);
connectionInfos.has(connKey) ||
Fail`no connection info found for ${q(connKey)}`;
Fail`no connection info found for ${holdingChainId}<->${destination.chainId}`;

const { transferChannel } = denormalizeConnectionInfo(
holdingChainId, // from chain (primary)
Expand All @@ -568,11 +568,11 @@ export const makeChainHub = (zone, agoricNames, vowTools) => {
// TODO use getConnectionInfo once its sync
const currToIssuerKey = connectionKey(holdingChainId, baseChainId);
connectionInfos.has(currToIssuerKey) ||
Fail`no connection info found for ${q(currToIssuerKey)}`;
Fail`no connection info found for ${holdingChainId}<->${baseChainId}`;

const issuerToDestKey = connectionKey(baseChainId, destination.chainId);
connectionInfos.has(issuerToDestKey) ||
Fail`no connection info found for ${q(issuerToDestKey)}`;
Fail`no connection info found for ${baseChainId}<->${destination.chainId}`;

const currToIssuer = denormalizeConnectionInfo(
holdingChainId,
Expand Down
70 changes: 36 additions & 34 deletions packages/orchestration/src/exos/local-orchestration-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Fail, q } from '@endo/errors';
import {
AmountArgShape,
AnyNatAmountsRecord,
ChainAddressShape,
DenomAmountShape,
DenomShape,
IBCTransferOptionsShape,
Expand All @@ -24,11 +23,12 @@ import { makeTimestampHelper } from '../utils/time.js';
import { preparePacketTools } from './packet-tools.js';
import { prepareIBCTools } from './ibc-packet.js';
import { coerceCoin, coerceDenomAmount } from '../utils/amounts.js';
import { TransferRouteShape } from './chain-hub.js';

/**
* @import {HostOf} from '@agoric/async-flow';
* @import {LocalChain, LocalChainAccount} from '@agoric/vats/src/localchain.js';
* @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, IBCConnectionInfo, OrchestrationAccountI, LocalAccountMethods} from '@agoric/orchestration';
* @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, IBCConnectionInfo, OrchestrationAccountI, LocalAccountMethods, TransferRoute} 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 @@ -107,7 +107,7 @@ export const prepareLocalOrchestrationAccountKit = (
zoeTools,
},
) => {
const { watch, allVows, asVow, when } = vowTools;
const { watch, asVow, when } = vowTools;
const { makeIBCTransferSender } = prepareIBCTools(
zone.subZone('ibcTools'),
vowTools,
Expand All @@ -134,11 +134,10 @@ export const prepareLocalOrchestrationAccountKit = (
.returns(VowShape),
}),
transferWatcher: M.interface('transferWatcher', {
onFulfilled: M.call([M.record(), M.nat()])
onFulfilled: M.call(M.nat())
.optional({
destination: ChainAddressShape,
opts: M.or(M.undefined(), IBCTransferOptionsShape),
amount: DenomAmountShape,
route: TransferRouteShape,
})
.returns(Vow$(M.record())),
}),
Expand Down Expand Up @@ -345,37 +344,34 @@ export const prepareLocalOrchestrationAccountKit = (
},
transferWatcher: {
/**
* @param {[
* { transferChannel: IBCConnectionInfo['transferChannel'] },
* bigint,
* ]} results
* @param {bigint} timeoutTimestamp
* @param {{
* destination: ChainAddress;
* opts?: IBCMsgTransferOptions;
* amount: DenomAmount;
* opts?: Omit<IBCMsgTransferOptions, 'forwardOpts'>;
* route: TransferRoute;
* }} ctx
*/
onFulfilled(
[{ transferChannel }, timeoutTimestamp],
{ opts, amount, destination },
) {
onFulfilled(timeoutTimestamp, { opts, route }) {
const { forwardInfo, ...transferDetails } = route;
/** @type {string | undefined} */
let memo;
if (opts && 'memo' in opts) {
memo = opts.memo;
}
if (forwardInfo) {
// forward memo takes precedence
memo = JSON.stringify(forwardInfo);
}
const transferMsg = typedJson(
'/ibc.applications.transfer.v1.MsgTransfer',
{
sourcePort: transferChannel.portId,
sourceChannel: transferChannel.channelId,
token: {
amount: String(amount.value),
denom: amount.denom,
},
...transferDetails,
sender: this.state.address.value,
receiver: destination.value,
timeoutHeight: opts?.timeoutHeight ?? {
revisionHeight: 0n,
revisionNumber: 0n,
},
timeoutTimestamp,
memo: opts?.memo ?? '',
memo: memo ?? '',
},
);

Expand Down Expand Up @@ -682,16 +678,23 @@ export const prepareLocalOrchestrationAccountKit = (
* timeoutTimestamp are not supplied, a default timeoutTimestamp will
* be set for 5 minutes in the future
* @returns {Vow<any>}
* @throws {Error} if route is not determinable, asset is not
* recognized, or the transfer is rejected (insufficient funds,
* timeout)
*/
transfer(destination, amount, opts) {
return asVow(() => {
trace('Transferring funds from LCA over IBC');
const denomAmount = coerceDenomAmount(chainHub, amount);

const connectionInfoV = watch(
chainHub.getConnectionInfo(
this.state.address.chainId,
destination.chainId,
),
const { forwardOpts, ...rest } = opts ?? {};

// throws if route is not determinable
const route = chainHub.makeTransferRoute(
destination,
denomAmount,
'agoric',
forwardOpts,
);

// set a `timeoutTimestamp` if caller does not supply either `timeoutHeight` or `timeoutTimestamp`
Expand All @@ -705,12 +708,11 @@ export const prepareLocalOrchestrationAccountKit = (
// don't resolve the vow until the transfer is confirmed on remote
// and reject vow if the transfer fails for any reason
const resultV = watch(
allVows([connectionInfoV, timeoutTimestampVowOrValue]),
timeoutTimestampVowOrValue,
this.facets.transferWatcher,
{
opts,
amount: coerceDenomAmount(chainHub, amount),
destination,
opts: rest,
route,
},
);
return resultV;
Expand Down
4 changes: 2 additions & 2 deletions packages/orchestration/src/orchestration-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ export interface OrchestrationAccountI {
* @param destination - the account to transfer the amount to.
* @param [opts] - an optional memo to include with the transfer, which could drive custom PFM behavior, and timeout parameters
* @returns void
*
* TODO document the mapping from the address to the destination chain.
* @throws {Error} if route is not determinable, asset is not recognized, or
* the transfer is rejected (insufficient funds, timeout)
*/
transfer: (
destination: ChainAddress,
Expand Down
6 changes: 3 additions & 3 deletions packages/orchestration/test/exos/chain-hub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ test('makeTransferRoute - no connection info single hop', t => {
harden({ denom: uusdcOnAgoric, value: 100n }),
'agoric',
),
{ message: 'no connection info found for "agoric-3_noble-1"' },
{ message: 'no connection info found for "agoric-3"<->"noble-1"' },
);
});

Expand Down Expand Up @@ -517,7 +517,7 @@ test('makeTransferRoute - no connection info multi hop', t => {
harden({ denom: uusdcOnAgoric, value: 100n }),
'agoric',
),
{ message: 'no connection info found for "noble-1_osmosis-1"' },
{ message: 'no connection info found for "noble-1"<->"osmosis-1"' },
);

// transfer USDC on osmosis to agoric
Expand All @@ -528,7 +528,7 @@ test('makeTransferRoute - no connection info multi hop', t => {
harden({ denom: uusdcOnOsmosis, value: 100n }),
'osmosis',
),
{ message: 'no connection info found for "noble-1_osmosis-1"' },
{ message: 'no connection info found for "osmosis-1"<->"noble-1"' },
);
});

Expand Down
Loading

0 comments on commit c35fac7

Please sign in to comment.