Skip to content

Commit 00dd441

Browse files
committed
chore: WIP swap anything
Refs: #8863
1 parent 40dc566 commit 00dd441

File tree

3 files changed

+328
-15
lines changed

3 files changed

+328
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
2+
import { E } from '@endo/far';
3+
import { M } from '@endo/patterns';
4+
import { prepareChainHubAdmin } from '../exos/chain-hub-admin.js';
5+
import { withOrchestration } from '../utils/start-helper.js';
6+
import * as sharedFlows from './shared.flows.js';
7+
import { swapIt } from './swap-anything.flows.js';
8+
import { AnyNatAmountShape } from '../typeGuards.js';
9+
import { registerChainsAndAssets } from '../utils/chain-hub-helper.js';
10+
11+
/**
12+
* @import {Remote, Vow} from '@agoric/vow';
13+
* @import {Zone} from '@agoric/zone';
14+
* @import {OrchestrationPowers, OrchestrationTools} from '../utils/start-helper.js';
15+
* @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration';
16+
*/
17+
18+
export const SingleNatAmountRecord = M.and(
19+
M.recordOf(M.string(), AnyNatAmountShape, { numPropertiesLimit: 1 }),
20+
M.not(harden({})),
21+
);
22+
harden(SingleNatAmountRecord);
23+
24+
/**
25+
* Send assets currently in an ERTP purse to an account on another chain. This
26+
* currently supports IBC and CCTP transfers. It could eventually support other
27+
* protocols, like Axelar GMP or IBC Eureka.
28+
*
29+
* Orchestration contract to be wrapped by withOrchestration for Zoe
30+
*
31+
* @param {ZCF} zcf
32+
* @param {OrchestrationPowers & {
33+
* assetInfo?: [Denom, DenomDetail & { brandKey?: string }][];
34+
* chainInfo?: Record<string, CosmosChainInfo>;
35+
* marshaller: Marshaller;
36+
* storageNode: Remote<StorageNode>;
37+
* }} privateArgs
38+
* @param {Zone} zone
39+
* @param {OrchestrationTools} tools
40+
*/
41+
export const contract = async (
42+
zcf,
43+
privateArgs,
44+
zone,
45+
{ chainHub, orchestrate, vowTools, zoeTools },
46+
) => {
47+
const creatorFacet = prepareChainHubAdmin(zone, chainHub);
48+
49+
// UNTIL https://github.com/Agoric/agoric-sdk/issues/9066
50+
const logNode = E(privateArgs.storageNode).makeChildNode('log');
51+
/** @type {(msg: string) => Vow<void>} */
52+
const log = msg => vowTools.watch(E(logNode).setValue(msg));
53+
54+
const makeLocalAccount = orchestrate(
55+
'makeLocalAccount',
56+
{},
57+
sharedFlows.makeLocalAccount,
58+
);
59+
60+
/**
61+
* @type {any} sharedLocalAccountP expects a Promise but this is a vow UNTIL
62+
* https://github.com/Agoric/agoric-sdk/issues/9822
63+
*/
64+
const sharedLocalAccountP = zone.makeOnce('localAccount', () =>
65+
makeLocalAccount(),
66+
);
67+
68+
const swapAnything = orchestrate(
69+
'swapAnything',
70+
{ chainHub, sharedLocalAccountP, log, zoeTools },
71+
swapIt,
72+
);
73+
74+
const publicFacet = zone.exo(
75+
'Send PF',
76+
M.interface('Send PF', {
77+
makeSendInvitation: M.callWhen().returns(InvitationShape),
78+
}),
79+
{
80+
makeSendInvitation() {
81+
return zcf.makeInvitation(
82+
swapAnything,
83+
'swap',
84+
undefined,
85+
M.splitRecord({ give: SingleNatAmountRecord }),
86+
);
87+
},
88+
},
89+
);
90+
91+
registerChainsAndAssets(
92+
chainHub,
93+
zcf.getTerms().brands,
94+
privateArgs.chainInfo,
95+
privateArgs.assetInfo,
96+
);
97+
98+
return { publicFacet, creatorFacet };
99+
};
100+
harden(contract);
101+
102+
export const start = withOrchestration(contract, { publishAccountInfo: true });
103+
harden(start);
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,50 @@
1+
import { NonNullish, makeTracer } from '@agoric/internal';
2+
import { Fail, makeError, q } from '@endo/errors';
3+
import { M, mustMatch } from '@endo/patterns';
4+
import { RatioShape } from '@agoric/ertp';
5+
6+
const trace = makeTracer('SwapAnything');
7+
8+
const { entries } = Object;
9+
110
/**
211
* @import {GuestInterface, GuestOf} from '@agoric/async-flow';
3-
* @import {Invitation, ZCF, ZCFSeat} from '@agoric/zoe';
12+
* @import {Denom, DenomDetail} from '@agoric/orchestration';
13+
* @import {ZCF, ZCFSeat} from '@agoric/zoe';
414
* @import {Brand} from '@agoric/ertp';
515
* @import {Vow} from '@agoric/vow';
616
* @import {LocalOrchestrationAccountKit} from '../exos/local-orchestration-account.js';
717
* @import {ZoeTools} from '../utils/zoe-tools.js';
8-
* @import {Orchestrator, OrchestrationFlow, LocalAccountMethods, ChainHub, ChainInfo} from '../types.js';
18+
* @import {Orchestrator, OrchestrationFlow, ChainHub, ChainInfo} from '../types.js';
919
* @import {AccountIdArg} from '../orchestration-api.ts';
1020
*/
1121

22+
const denomForBrand = async (orch, brand) => {
23+
const agoric = await orch.getChain('agoric');
24+
const assets = await agoric.getVBankAssetInfo();
25+
const { denom } = NonNullish(
26+
assets.find(a => a.brand === brand),
27+
`${brand} not registered in ChainHub`,
28+
);
29+
return denom;
30+
};
31+
1232
/**
1333
* @satisfies {OrchestrationFlow}
1434
* @param {Orchestrator} orch
1535
* @param {object} ctx
1636
* @param {GuestInterface<ChainHub>} ctx.chainHub
1737
* @param {Promise<GuestInterface<LocalOrchestrationAccountKit['holder']>>} ctx.sharedLocalAccountP
18-
* @param {Promise<
19-
* GuestInterface<
20-
* import('../exos/cosmos-orchestration-account.js').CosmosOrchestrationAccountKit['holder']
21-
* >
22-
* >} ctx.nobleAccountP
2338
* @param {GuestInterface<ZoeTools>} ctx.zoeTools
2439
* @param {GuestOf<(msg: string) => Vow<void>>} ctx.log
25-
* @param {Brand} ctx.USDC
2640
* @param {ZCFSeat} seat
2741
* @param {{
28-
* destAddr: AccountIdArg;
29-
* destDenom: Denom;
30-
* slippage: { slippageRatio: Ratio; windowSeconds: Nat };
42+
* destAddr: string;
43+
* receiverAddr: string;
44+
* outDenom: Denom;
45+
* slippage: { slippageRatio: Ratio; windowSeconds: bigint };
3146
* onFailedDelivery: string;
32-
* nextMemo: string;
47+
* nextMemo?: string;
3348
* }} offerArgs
3449
*/
3550

@@ -41,11 +56,92 @@ export const swapIt = async (
4156
chainHub,
4257
sharedLocalAccountP,
4358
log,
44-
nobleAccountP,
45-
USDC,
4659
zoeTools: { localTransfer, withdrawToSeat },
4760
},
4861
seat,
4962
offerArgs,
50-
) => {};
63+
) => {
64+
// offerArgs,
65+
// harden({
66+
// destAddr: M.string(),
67+
// receiverAddr: M.string(),
68+
// outDenom: M.string(),
69+
// onFailedDelivery: M.string(),
70+
// slippage: { slippageRatio: RatioShape, windowSeconds: M.bigint() },
71+
// }),
72+
// );
73+
mustMatch(
74+
offerArgs,
75+
M.splitRecord(
76+
{
77+
destAddr: M.string(),
78+
receiverAddr: M.string(),
79+
outDenom: M.string(),
80+
onFailedDelivery: M.string(),
81+
slippage: { slippageRatio: RatioShape, windowSeconds: M.bigint() },
82+
},
83+
{ nextMemo: M.or(undefined, M.string()) },
84+
),
85+
);
86+
87+
trace('HELLLOOOOO');
88+
89+
const { destAddr } = offerArgs;
90+
// NOTE the proposal shape ensures that the `give` is a single asset
91+
const { give } = seat.getProposal();
92+
const [[_kw, amt]] = entries(give);
93+
void log(`sending {${amt.value}} from osmosis to ${destAddr}`);
94+
const denom = await denomForBrand(orch, amt.brand);
95+
96+
/** @type {ChainInfo} */
97+
const info = await chainHub.getChainInfo('osmosis');
98+
99+
/**
100+
* @type {any} XXX methods returning vows
101+
* https://github.com/Agoric/agoric-sdk/issues/9822
102+
*/
103+
const sharedLocalAccount = await sharedLocalAccountP;
104+
105+
/**
106+
* Helper function to recover if IBC Transfer fails
107+
*
108+
* @param {Error} e
109+
*/
110+
const recoverFailedTransfer = async e => {
111+
await withdrawToSeat(sharedLocalAccount, seat, give);
112+
const errorMsg = `IBC Transfer failed ${q(e)}`;
113+
void log(`ERROR: ${errorMsg}`);
114+
seat.fail(errorMsg);
115+
throw makeError(errorMsg);
116+
};
117+
118+
const { chainId } = info;
119+
assert(typeof chainId === 'string', 'bad chainId');
120+
121+
const [_a, _o, connection] = await chainHub.getChainsAndConnection(
122+
'agoric',
123+
'osmosis',
124+
);
125+
126+
connection.counterparty || Fail`No IBC connection to Osmosis`;
127+
128+
void log(`got info for chain: osmosis ${chainId}`);
129+
130+
await localTransfer(seat, sharedLocalAccount, give);
131+
void log(`completed transfer to localAccount`);
132+
133+
try {
134+
await sharedLocalAccount.transfer(
135+
{ value: destAddr, encoding: 'bech32', chainId },
136+
{ denom, value: amt.value },
137+
// memo here
138+
);
139+
void log(`completed transfer to ${destAddr}`);
140+
} catch (e) {
141+
return recoverFailedTransfer(e);
142+
}
143+
144+
seat.exit();
145+
void log(`transfer complete, seat exited`);
146+
};
51147
harden(swapIt);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
2+
import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js';
3+
import { E } from '@endo/far';
4+
import { makeRatio } from '@agoric/ertp/src/ratio.js';
5+
import * as contractExports from '../../src/examples/swap-anything.contract.js';
6+
import { commonSetup } from '../supports.js';
7+
8+
const contractName = 'swap-anything';
9+
type StartFn = typeof contractExports.start;
10+
11+
const bootstrapOrchestration = async t => {
12+
t.log('bootstrap, orchestration core-eval');
13+
const {
14+
bootstrap,
15+
commonPrivateArgs,
16+
brands: { bld },
17+
utils: {
18+
inspectLocalBridge,
19+
pourPayment,
20+
transmitTransferAck,
21+
populateChainHub,
22+
},
23+
} = await commonSetup(t);
24+
const vt = bootstrap.vowTools;
25+
26+
const { zoe, bundleAndInstall } = await setUpZoeForTest();
27+
28+
t.log('contract coreEval', contractName);
29+
30+
const installation: Installation<StartFn> =
31+
await bundleAndInstall(contractExports);
32+
33+
const storageNode = await E(bootstrap.storage.rootNode).makeChildNode(
34+
contractName,
35+
);
36+
37+
populateChainHub();
38+
39+
const swapKit = await E(zoe).startInstance(
40+
installation,
41+
{ BLD: bld.issuer },
42+
{},
43+
{ ...commonPrivateArgs, storageNode },
44+
);
45+
46+
return {
47+
bootstrap,
48+
commonPrivateArgs,
49+
bld,
50+
inspectLocalBridge,
51+
pourPayment,
52+
transmitTransferAck,
53+
vt,
54+
zoe,
55+
installation,
56+
storageNode,
57+
swapKit,
58+
};
59+
};
60+
61+
test('swap BLD for Osmo, receiver on Agoric', async t => {
62+
const {
63+
vt,
64+
swapKit,
65+
zoe,
66+
pourPayment,
67+
bld,
68+
transmitTransferAck,
69+
inspectLocalBridge,
70+
} = await bootstrapOrchestration(t);
71+
72+
const publicFacet = await E(zoe).getPublicFacet(swapKit.instance);
73+
const inv = E(publicFacet).makeSendInvitation();
74+
const amt = await E(zoe).getInvitationDetails(inv);
75+
t.is(amt.description, 'swap');
76+
77+
const anAmt = bld.units(3.5);
78+
const Send = await pourPayment(anAmt);
79+
const userSeat = await E(zoe).offer(
80+
inv,
81+
{ give: { Send: anAmt } },
82+
{ Send },
83+
{
84+
destAddr: 'hot1destAddr',
85+
receiverAddr: 'osmosis1xcsaddr',
86+
outDenom: 'uosmo',
87+
onFailedDelivery: 'doNothing',
88+
slippage: {
89+
slippageRatio: makeRatio(10n, bld.brand),
90+
windowSeconds: 10n,
91+
},
92+
},
93+
);
94+
await transmitTransferAck();
95+
await vt.when(E(userSeat).getOfferResult());
96+
97+
const history = inspectLocalBridge();
98+
t.log(history);
99+
t.like(history, [
100+
{ type: 'VLOCALCHAIN_ALLOCATE_ADDRESS' },
101+
{ type: 'VLOCALCHAIN_EXECUTE_TX' },
102+
]);
103+
104+
t.like(
105+
history.find(x => x.type === 'VLOCALCHAIN_EXECUTE_TX')?.messages?.[0],
106+
{
107+
'@type': '/ibc.applications.transfer.v1.MsgTransfer',
108+
receiver: 'hot1destAddr',
109+
sender: 'agoric1fakeLCAAddress',
110+
memo: '',
111+
},
112+
'crosschain swap sent',
113+
);
114+
});

0 commit comments

Comments
 (0)