diff --git a/README.md b/README.md index 65089a7..aea52e7 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ GET /shouldRouteWormhole?originAddr=0x07865c6e87b9f70255377e024ace6630c1e00000&t # ---------- when error ---------- # GET /shouldRouteWormhole?originAddr=0x07865c6e87b9f70255377e024ace6630c1e00000&targetChainId=2&destAddr=0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6 -=> +=> { "error": ["fromParaId is a required field"], "msg": "invalid request params!" @@ -527,7 +527,7 @@ GET /shouldSwapAndRoute?recipient=0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6&poo ``` ### `/swapAndRoute` -swap token and airdrop targetAmount of targetToken to recipient, and route the quest, then returns the txhash +swap small amount of token and airdrop ACA to recipient, and route the quest, then returns the txhash ``` POST /swapAndRoute data: { @@ -556,6 +556,59 @@ data: { // similar to /routeXcm ``` +### `/shouldRouteDropAndBootstrap` +checks if the relayer can route this request, returns router address +``` +GET /shouldRouteDropAndBootstrap +params: { + recipient: string; // dest evm address + gasDrop: boolean; // whether to perform gas drop, only available when feeToken is 'jitosol' + feeToken: string; // token to pay for router fee, either 'jitosol' or 'ldot' +} +``` + +example +``` +GET /shouldRouteDropAndBootstrap?recipient=0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6&gasDrop=1&feeToken=jitosol +=> +{ + "data": { + "shouldRoute": true, + "routerAddr": "0xC3FaCa03c514C5e47cf267f971B50280E5ea780b" + } +} +``` + +### `/routeDropAndBootstrap` +- when calling for the first time: route and perform gas drop (if `gasDrop` is true) +- when calling for the second time: route only + +returns the txhash + +``` +POST /routeDropAndBootstrap +params: { + recipient: string; // dest evm address + gasDrop: boolean; // whether to perform gas drop, only available when feeToken is 'jitosol' + feeToken: string; // token to pay for router fee, either 'jitosol' or 'ldot' +} +``` + +example +``` +POST /routeDropAndBootstrap +{ + "recipient":"0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6", + "gasDrop": true, + "feeToken": "jitosol" +} + +=> tx hash +{ + data: '0xede191f4de90057d320c0d06388e7357edb7bcd6b437a5035dd63dfc8809ce7e' +} +``` + ## Routing Process A complete working flow can be found in [routing e2e tests](./src/__tests__/route.test.ts). diff --git a/codecov.yml b/codecov.yml index d8a6784..4fddbfe 100644 --- a/codecov.yml +++ b/codecov.yml @@ -13,7 +13,7 @@ coverage: comment: layout: "reach, diff, flags, files" - behavior: default + behavior: new require_changes: false require_base: false require_head: false diff --git a/docker-compose.yml b/docker-compose.yml index 494b15a..0cdb874 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: volumes: - ./src/__tests__/configs/acala.yml:/app/acala.yml command: - bunx @acala-network/chopsticks@latest -c /app/acala.yml + bunx @acala-network/chopsticks@0.15.0 -c /app/acala.yml ports: - 8000:8000 healthcheck: diff --git a/package.json b/package.json index 262f551..affec14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "acala-wormhole-relayer", - "version": "1.8.5", + "version": "1.9.0-5", "description": "", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -22,7 +22,7 @@ }, "dependencies": { "@acala-network/api": "~6.0.4", - "@acala-network/asset-router": "~1.0.18", + "@acala-network/asset-router": "~1.0.19-14", "@acala-network/bodhi": "~2.7.13", "@acala-network/contracts": "^4.5.0", "@acala-network/eth-providers": "~2.7.14", diff --git a/src/__tests__/__snapshots__/bootstrap.test.ts.snap b/src/__tests__/__snapshots__/bootstrap.test.ts.snap new file mode 100644 index 0000000..c1e01be --- /dev/null +++ b/src/__tests__/__snapshots__/bootstrap.test.ts.snap @@ -0,0 +1,55 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`/shouldRouteDropAndBootstrap > when should route > jitosol_gasDrop_false_lowercase 1`] = ` +{ + "data": { + "routerAddr": "0x7dD4A59B255e13F2eb5cF0297574e5fb9e89576b", + "shouldRoute": true, + }, +} +`; + +exports[`/shouldRouteDropAndBootstrap > when should route > jitosol_gasDrop_false_original 1`] = ` +{ + "data": { + "routerAddr": "0x7dD4A59B255e13F2eb5cF0297574e5fb9e89576b", + "shouldRoute": true, + }, +} +`; + +exports[`/shouldRouteDropAndBootstrap > when should route > jitosol_gasDrop_true_lowercase 1`] = ` +{ + "data": { + "routerAddr": "0x9A1100129688460930096d7E1204A2989321B113", + "shouldRoute": true, + }, +} +`; + +exports[`/shouldRouteDropAndBootstrap > when should route > jitosol_gasDrop_true_original 1`] = ` +{ + "data": { + "routerAddr": "0x9A1100129688460930096d7E1204A2989321B113", + "shouldRoute": true, + }, +} +`; + +exports[`/shouldRouteDropAndBootstrap > when should route > ldot_gasDrop_false_lowercase 1`] = ` +{ + "data": { + "routerAddr": "0x6046DD5F660818f9D64Af4536A061D8C3BCE01b9", + "shouldRoute": true, + }, +} +`; + +exports[`/shouldRouteDropAndBootstrap > when should route > ldot_gasDrop_false_original 1`] = ` +{ + "data": { + "routerAddr": "0x6046DD5F660818f9D64Af4536A061D8C3BCE01b9", + "shouldRoute": true, + }, +} +`; diff --git a/src/__tests__/bootstrap.test.ts b/src/__tests__/bootstrap.test.ts new file mode 100644 index 0000000..4d142fb --- /dev/null +++ b/src/__tests__/bootstrap.test.ts @@ -0,0 +1,484 @@ +import { ADDRESSES, ROUTER_TOKEN_INFO } from '@acala-network/asset-router/dist/consts'; +import { AcalaJsonRpcProvider } from '@acala-network/eth-providers'; +import { ApiPromise, WsProvider } from '@polkadot/api'; +import { ERC20__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts'; +import { FeeRegistry__factory } from '@acala-network/asset-router/dist/typechain-types'; +import { LDOT } from '@acala-network/contracts/utils/AcalaTokens'; +import { Wallet } from 'ethers'; +import { describe, expect, it } from 'vitest'; +import { parseEther, parseUnits } from 'ethers/lib/utils'; +import { toHuman } from '@acala-network/asset-router/dist/utils'; + +import { DropAndBootstrapParams } from '../utils'; +import { ETH_RPC, EUPHRATES_ADDR } from '../consts'; +import { + JITOSOL_LDOT_LP_PREDEPLOY_CODE, + NEW_DEX_CODE, + TEST_ADDR_RELAYER, + TEST_ADDR_USER, + TEST_KEY, +} from './testConsts'; +import { + api, + expectError, + sudoSendAndWait, + sudoTransferToken, + transferToken, +} from './testUtils'; + +const provider = new AcalaJsonRpcProvider(ETH_RPC.LOCAL); +const relayer = new Wallet(TEST_KEY.RELAYER, provider); +const user = Wallet.createRandom().connect(provider); + +const JITOSOL_DECIMALS = 9; +const LDOT_DECIMALS = 10; +const JITOSOL_ADDR = ROUTER_TOKEN_INFO.jitosol.acalaAddr; + +const recipient = user.address; +const boostrapAmountJitosol = '0.1'; +const boostrapAmountLdot = '10'; + +describe('prepare', () => { + it('open bootstrap', async () => { + const api = await ApiPromise.create({ + provider: new WsProvider('ws://localhost:8000'), + }); + + console.log('register jitosol ...'); + let tx = api.tx.assetRegistry.registerErc20Asset(JITOSOL_ADDR, 10000); + await sudoSendAndWait(api, tx); + + console.log('set new dex code ...'); + tx = api.tx.evm.setCode('0x0000000000000000000000000000000000000803', NEW_DEX_CODE); + await sudoSendAndWait(api, tx); + expect(await provider.getCode('0x0000000000000000000000000000000000000803')).to.not.eq('0x'); + + console.log('deploy LDOT-jitoSOL LP token predeploy ...'); + tx = api.tx.evm.createPredeployContract('0x00000000000000000002000000000301a7fb0045', JITOSOL_LDOT_LP_PREDEPLOY_CODE, 0, 1500000, 20000, []); + await sudoSendAndWait(api, tx); + expect(await provider.getCode('0x00000000000000000002000000000301a7fb0045')).to.not.eq('0x'); + + console.log('open euphrates pool ...'); + const evmTx = api.tx.evm.call(EUPHRATES_ADDR, '0xd914cd4b00000000000000000000000000000000000000000002000000000301a7fb0045', 0, 1000000, 100000, []); + tx = api.tx.utility.dispatchAs({ + system: { + Signed: '23j4ay2zBSgaSs18xstipmHBNi39W2Su9n8G89kWrz8eCe8F', + }, + }, evmTx); + await sudoSendAndWait(api, tx); + + console.log('list provisioning ...'); + tx = api.tx.dex.listProvisioning( + { Token: 'LDOT' }, + { Erc20: JITOSOL_ADDR }, + 1, + 1, + parseUnits('10', 10).toBigInt(), + parseUnits('10', 9).toBigInt(), + 0, + ); + await sudoSendAndWait(api, tx); + + console.log('minting some jitosol for relayer ...'); + await sudoTransferToken( + '0x335B26d05DacA31d6d921F07466e19eF8650D4Bf', + TEST_ADDR_RELAYER, + provider, + JITOSOL_ADDR, + 5, + ); + }); +}); + + +describe.concurrent('/shouldRouteDropAndBootstrap', () => { + const testShouldRouteDropAndBootstrap = async ( + params: DropAndBootstrapParams, + snapshotName: string, + ) => { + let res = await api.shouldRouteDropAndBootstrap(params); + expect(res).toMatchSnapshot(`${snapshotName}_original`); + + // should be case insensitive + res = await api.shouldRouteDropAndBootstrap(params); + expect(res).toMatchSnapshot(`${snapshotName}_lowercase`); + }; + + it('when should route', async () => { + await testShouldRouteDropAndBootstrap({ + recipient: TEST_ADDR_USER, + gasDrop: true, + feeToken: 'jitosol', + }, 'jitosol_gasDrop_true'); + + await testShouldRouteDropAndBootstrap({ + recipient: TEST_ADDR_USER, + gasDrop: false, + feeToken: 'jitosol', + }, 'jitosol_gasDrop_false'); + + await testShouldRouteDropAndBootstrap({ + recipient: TEST_ADDR_USER, + gasDrop: false, + feeToken: 'ldot', + }, 'ldot_gasDrop_false'); + }); + + describe('when should not route', () => { + it('when missing params', async () => { + try { + await api.shouldRouteDropAndBootstrap({ + recipient, + gasDrop: false, + }); + expect.fail('did not throw an err'); + } catch (err) { + expectError(err, ['feeToken is a required field'], 400); + } + + try { + await api.shouldRouteDropAndBootstrap({ + recipient, + feeToken: 'jitosol', + }); + expect.fail('did not throw an err'); + } catch (err) { + expectError(err, ['gasDrop is a required field'], 400); + } + }); + + it('when bad params', async () => { + try { + await api.shouldRouteDropAndBootstrap({ + recipient, + gasDrop: false, + feeToken: 'xxx', + }); + expect.fail('did not throw an err'); + } catch (err) { + expectError(err, ['feeToken must be one of { jitosol, ldot }'], 400); + } + + try { + await api.shouldRouteDropAndBootstrap({ + recipient, + gasDrop: true, + feeToken: 'ldot', + }); + expect.fail('did not throw an err'); + } catch (err) { + expectError(err, ['does not support gasDrop when feeToken is ldot'], 400); + } + }); + }); +}); + +describe('/routeDropAndBootstrap', () => { + let routerAddr: string; + const jitosol = ERC20__factory.connect(JITOSOL_ADDR, relayer); + const ldot = ERC20__factory.connect(LDOT, relayer); + + const fetchTokenBalances = async () => { + if (!routerAddr) throw new Error('routerAddr not set'); + + const [ + userBal, + userBalJitosol, + relayerBalJitosol, + routerBalJitosol, + userBalLdot, + relayerBalLdot, + routerBalLdot, + ] = await Promise.all([ + provider.getBalance(recipient), + jitosol.balanceOf(recipient), + jitosol.balanceOf(TEST_ADDR_RELAYER), + jitosol.balanceOf(routerAddr), + ldot.balanceOf(recipient), + ldot.balanceOf(TEST_ADDR_RELAYER), + ldot.balanceOf(routerAddr), + ]); + + console.log({ + userBal: toHuman(userBal, 18), + userBalJitosol: toHuman(userBalJitosol, JITOSOL_DECIMALS), + relayerBalJitosol: toHuman(relayerBalJitosol, JITOSOL_DECIMALS), + routerBalJitosol: toHuman(routerBalJitosol, JITOSOL_DECIMALS), + userBalLdot: toHuman(userBalLdot, LDOT_DECIMALS), + relayerBalLdot: toHuman(relayerBalLdot, LDOT_DECIMALS), + routerBalLdot: toHuman(routerBalLdot, LDOT_DECIMALS), + }); + + return { + userBal, + userBalJitosol, + relayerBalJitosol, + routerBalJitosol, + userBalLdot, + relayerBalLdot, + routerBalLdot, + }; + }; + + it('works with jitosol as fee token and gas drop', async () => { + const relayerBal = await relayer.getBalance(); + expect(relayerBal.gt(parseEther('10'))).to.be.true; + + const routeArgs = { + recipient: user.address, + gasDrop: true, + feeToken: 'jitosol', + }; + + const shouldRouteRes = await api.shouldRouteDropAndBootstrap(routeArgs); + ({ routerAddr } = shouldRouteRes.data); + console.log({ routerAddr }); + + // make sure user has enough token and ACA to transfer to router + console.log('refilling ACA for user ...'); + await (await relayer.sendTransaction({ + to: recipient, + value: parseEther('3'), + })).wait(); + + const bal = await fetchTokenBalances(); + const refillAmount = parseUnits(boostrapAmountJitosol, JITOSOL_DECIMALS); + if (bal.userBalJitosol.lt(refillAmount.mul(2))) { + if (bal.relayerBalJitosol.lt(refillAmount.mul(2))) { + throw new Error('both relayer and user do not have enough jitosol to transfer to router!'); + } + + console.log('refilling token for user ...'); + await (await jitosol.transfer(recipient, refillAmount.mul(2))).wait(); + } + + console.log('transferring token to router ...'); + await transferToken(routerAddr, user, JITOSOL_ADDR, Number(boostrapAmountJitosol)); + + const bal0 = await fetchTokenBalances(); + + console.log('routing ...'); + let routeRes = await api.routeDropAndBootstrap(routeArgs); + let txHash = routeRes.data; + console.log(`route finished! txHash: ${txHash}`); + + const bal1 = await fetchTokenBalances(); + + // router should NOT be destroyed + let routerCode = await provider.getCode(routerAddr); + expect(routerCode.length).to.be.greaterThan(100); + expect(bal1.routerBalJitosol.toNumber()).to.eq(0); + expect(bal1.routerBalLdot.toNumber()).to.eq(0); + + // relayer should receive jitosol fee + const routingFee = await FeeRegistry__factory.connect(ADDRESSES.ACALA.feeAddr, provider) + .getFee(JITOSOL_ADDR); + + // first route should perform swap + ACA drop + const swapFee = parseUnits('0.0035', 9); + expect(bal1.relayerBalJitosol.sub(bal0.relayerBalJitosol).toBigInt()) + .to.eq(routingFee.add(swapFee).toBigInt()); + expect(bal1.relayerBalLdot.sub(bal0.relayerBalLdot).toBigInt()).to.eq(0n); + + // user should receive 3 ACA drop + expect(bal1.userBal.sub(bal0.userBal).toBigInt()).to.eq(parseEther('3').toBigInt()); + + console.log('------------------------------------ route 2 ------------------------------------'); + console.log('transferring token to router ...'); + await transferToken(routerAddr, user, JITOSOL_ADDR, Number(boostrapAmountJitosol)); + + const bal2 = await fetchTokenBalances(); + + console.log('routing ...'); + routeRes = await api.routeDropAndBootstrap(routeArgs); + txHash = routeRes.data; + console.log(`route finished! txHash: ${txHash}`); + + const bal3 = await fetchTokenBalances(); + + // router should NOT be destroyed + routerCode = await provider.getCode(routerAddr); + expect(routerCode.length).to.be.greaterThan(100); + expect(bal3.routerBalJitosol.toNumber()).to.eq(0); + expect(bal3.routerBalLdot.toNumber()).to.eq(0); + + // second route should only route + expect(bal3.relayerBalJitosol.sub(bal2.relayerBalJitosol).toBigInt()).to.eq(routingFee.toBigInt()); + expect(bal3.relayerBalLdot.sub(bal2.relayerBalLdot).toBigInt()).to.eq(0n); + + // user should NOT receive 3 ACA drop + expect(bal3.userBal.sub(bal2.userBal).toBigInt()).to.eq(0n); + }); + + it('works with jitosol as fee token and no gas drop', async () => { + const relayerBal = await relayer.getBalance(); + expect(relayerBal.gt(parseEther('10'))).to.be.true; + + const routeArgs = { + recipient: user.address, + gasDrop: false, + feeToken: 'jitosol', + }; + + const shouldRouteRes = await api.shouldRouteDropAndBootstrap(routeArgs); + ({ routerAddr } = shouldRouteRes.data); + console.log({ routerAddr }); + + // make sure user has enough token and ACA to transfer to router + console.log('refilling ACA for user ...'); + await (await relayer.sendTransaction({ + to: recipient, + value: parseEther('3'), + })).wait(); + + const bal = await fetchTokenBalances(); + const refillAmount = parseUnits(boostrapAmountJitosol, JITOSOL_DECIMALS); + if (bal.userBalJitosol.lt(refillAmount.mul(2))) { + if (bal.relayerBalJitosol.lt(refillAmount.mul(2))) { + throw new Error('both relayer and user do not have enough jitosol to transfer to router!'); + } + + console.log('refilling token for user ...'); + await (await jitosol.transfer(recipient, refillAmount.mul(2))).wait(); + } + + console.log('transferring token to router ...'); + await transferToken(routerAddr, user, JITOSOL_ADDR, Number(boostrapAmountJitosol)); + + const bal0 = await fetchTokenBalances(); + + console.log('routing ...'); + let routeRes = await api.routeDropAndBootstrap(routeArgs); + let txHash = routeRes.data; + console.log(`route finished! txHash: ${txHash}`); + + const bal1 = await fetchTokenBalances(); + + // router should NOT be destroyed + let routerCode = await provider.getCode(routerAddr); + expect(routerCode.length).to.be.greaterThan(100); + expect(bal1.routerBalJitosol.toNumber()).to.eq(0); + expect(bal1.routerBalLdot.toNumber()).to.eq(0); + + // relayer should receive jitosol fee but no swap fee + const routingFee = await FeeRegistry__factory.connect(ADDRESSES.ACALA.feeAddr, provider) + .getFee(JITOSOL_ADDR); + expect(bal1.relayerBalJitosol.sub(bal0.relayerBalJitosol).toBigInt()).to.eq(routingFee.toBigInt()); + expect(bal1.relayerBalLdot.sub(bal0.relayerBalLdot).toBigInt()).to.eq(0n); + + // user should NOT receive 3 ACA drop + expect(bal1.userBal.sub(bal0.userBal).toBigInt()).to.eq(0n); + + console.log('------------------------------------ route 2 ------------------------------------'); + console.log('transferring token to router ...'); + await transferToken(routerAddr, user, JITOSOL_ADDR, Number(boostrapAmountJitosol)); + + const bal2 = await fetchTokenBalances(); + + console.log('routing ...'); + routeRes = await api.routeDropAndBootstrap(routeArgs); + txHash = routeRes.data; + console.log(`route finished! txHash: ${txHash}`); + + const bal3 = await fetchTokenBalances(); + + // router should NOT be destroyed + routerCode = await provider.getCode(routerAddr); + expect(routerCode.length).to.be.greaterThan(100); + expect(bal3.routerBalJitosol.toNumber()).to.eq(0); + expect(bal3.routerBalLdot.toNumber()).to.eq(0); + + // second route should be the same as the first one + expect(bal3.relayerBalJitosol.sub(bal2.relayerBalJitosol).toBigInt()).to.eq(routingFee.toBigInt()); + expect(bal3.relayerBalLdot.sub(bal2.relayerBalLdot).toBigInt()).to.eq(0n); + + // user should NOT receive 3 ACA drop + expect(bal3.userBal.sub(bal2.userBal).toBigInt()).to.eq(0n); + }); + + it('works with ldot as fee token and no gas drop', async () => { + const relayerBal = await relayer.getBalance(); + expect(relayerBal.gt(parseEther('10'))).to.be.true; + + const routeArgs = { + recipient: user.address, + gasDrop: false, + feeToken: 'ldot', + }; + + const shouldRouteRes = await api.shouldRouteDropAndBootstrap(routeArgs); + ({ routerAddr } = shouldRouteRes.data); + console.log({ routerAddr }); + + // make sure user has enough token and ACA to transfer to router + console.log('refilling ACA for user ...'); + await (await relayer.sendTransaction({ + to: recipient, + value: parseEther('3'), + })).wait(); + + const bal = await fetchTokenBalances(); + const refillAmount = parseUnits(boostrapAmountLdot, LDOT_DECIMALS).mul(2); + if (bal.userBalLdot.lt(refillAmount)) { + if (bal.relayerBalLdot.lt(refillAmount)) { + throw new Error('both relayer and user do not have enough ldot to transfer to router!'); + } + + console.log('refilling token for user ...'); + await (await ldot.transfer(recipient, refillAmount)).wait(); + } + + console.log('transferring token to router ...'); + await transferToken(routerAddr, user, LDOT, Number(boostrapAmountLdot)); + + const bal0 = await fetchTokenBalances(); + + console.log('routing ...'); + let routeRes = await api.routeDropAndBootstrap(routeArgs); + let txHash = routeRes.data; + console.log(`route finished! txHash: ${txHash}`); + + const bal1 = await fetchTokenBalances(); + + // router should NOT be destroyed + let routerCode = await provider.getCode(routerAddr); + expect(routerCode.length).to.be.greaterThan(100); + expect(bal1.routerBalJitosol.toNumber()).to.eq(0); + expect(bal1.routerBalLdot.toNumber()).to.eq(0); + + // relayer should receive ldot fee but no swap fee + const routingFee = await FeeRegistry__factory.connect(ADDRESSES.ACALA.feeAddr, provider) + .getFee(LDOT); + expect(bal1.relayerBalJitosol.sub(bal0.relayerBalJitosol).toBigInt()).to.eq(0n); + expect(bal1.relayerBalLdot.sub(bal0.relayerBalLdot).toBigInt()).to.eq(routingFee.toBigInt()); + + // user should NOT receive 3 ACA drop + expect(bal1.userBal.sub(bal0.userBal).toBigInt()).to.eq(0n); + + console.log('------------------------------------ route 2 ------------------------------------'); + console.log('transferring token to router ...'); + await transferToken(routerAddr, user, LDOT, Number(boostrapAmountLdot)); + + const bal2 = await fetchTokenBalances(); + + console.log('routing ...'); + routeRes = await api.routeDropAndBootstrap(routeArgs); + txHash = routeRes.data; + console.log(`route finished! txHash: ${txHash}`); + + const bal3 = await fetchTokenBalances(); + + // router should NOT be destroyed + routerCode = await provider.getCode(routerAddr); + expect(routerCode.length).to.be.greaterThan(100); + expect(bal3.routerBalJitosol.toNumber()).to.eq(0); + expect(bal3.routerBalLdot.toNumber()).to.eq(0); + + // second route should be the same as the first one + expect(bal3.relayerBalJitosol.sub(bal2.relayerBalJitosol).toBigInt()).to.eq(0n); + expect(bal3.relayerBalLdot.sub(bal2.relayerBalLdot).toBigInt()).to.eq(routingFee.toBigInt()); + + // user should NOT receive 3 ACA drop + expect(bal3.userBal.sub(bal2.userBal).toBigInt()).to.eq(0n); + }); +}); diff --git a/src/__tests__/configs/acala.yml b/src/__tests__/configs/acala.yml index 21e76ee..d60ace8 100644 --- a/src/__tests__/configs/acala.yml +++ b/src/__tests__/configs/acala.yml @@ -3,7 +3,6 @@ endpoint: - wss://acala-rpc.dwellir.com mock-signature-host: true # block: ${env.ACALA_BLOCK_NUMBER} -block: 6548800 db: ./db.sqlite runtime-log-level: 5 @@ -70,6 +69,11 @@ import-storage: - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY - token: LDOT - free: 1000000000000000 + - + - + - 246gNkjCexYRsCpdjtVhz35sHjcb21jpqipzT9u4uwKV8iEE + - token: LDOT + - free: 1000000000000000 - - - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY diff --git a/src/__tests__/euphrates.test.ts b/src/__tests__/euphrates.test.ts index 0ddf9f0..f285369 100644 --- a/src/__tests__/euphrates.test.ts +++ b/src/__tests__/euphrates.test.ts @@ -52,7 +52,7 @@ describe.concurrent('/shouldRouteEuphrates', () => { }; it('when should route', async () => { - for (const poolId of EUPHRATES_POOLS) { + for (const poolId of EUPHRATES_POOLS.slice(0, 5)) { await testShouldRouteEuphrates({ recipient, poolId, diff --git a/src/__tests__/testConsts.ts b/src/__tests__/testConsts.ts index b311289..fed4983 100644 --- a/src/__tests__/testConsts.ts +++ b/src/__tests__/testConsts.ts @@ -10,3 +10,7 @@ export const TEST_ADDR_RELAYER = new Wallet(TEST_KEY.RELAYER).address; // 0xe32 export const PROD_ADDR = '0xBbBBa9Ebe50f9456E106e6ef2992179182889999'; export const NOT_SUPPORTED_ADDRESS = ''; + +export const NEW_DEX_CODE = '0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c806392ea19911161008c578063e2dc85dc11610066578063e2dc85dc146101e5578063f1e908f8146101f8578063f4f31ede1461020b578063ffd73c4a1461021e57600080fd5b806392ea1991146101ac578063aa02e9d3146101bf578063dbcd19a2146101d257600080fd5b80635859df34116100c85780635859df34146101605780636fc4b4e5146101735780638ef239cf1461018657806391c98a2a1461019957600080fd5b8063165c7c9a146100ef5780633d8d96201461011c5780634d60beb11461013f575b600080fd5b6101026100fd366004611fd0565b610256565b604080519283526020830191909152015b60405180910390f35b61012f61012a3660046120da565b61042d565b6040519015158152602001610113565b61015261014d366004612128565b61068d565b604051908152602001610113565b61010261016e366004611fd0565b610893565b61012f6101813660046120da565b6109f2565b61010261019436600461216d565b610c3e565b61012f6101a73660046121b8565b610e19565b61012f6101ba366004612209565b6110ed565b61012f6101cd36600461216d565b611371565b6101526101e0366004612128565b61159a565b61012f6101f33660046121b8565b611731565b61012f61020636600461216d565b6119c4565b610102610219366004611fd0565b611c78565b61023161022c366004611fd0565b611dd7565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610113565b60008073ffffffffffffffffffffffffffffffffffffffff84166102c15760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166103245760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b60405173ffffffffffffffffffffffffffffffffffffffff85811660248301528416604482015260009081906104059060640160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f165c7c9a00000000000000000000000000000000000000000000000000000000179052516103ba919061224f565b600060405180830381855afa9150503d80600081146103f5576040519150601f19603f3d011682016040523d82523d6000602084013e6103fa565b606091505b5090925090508161040c573d60208201fd5b80806020019051810190610420919061227e565b9350935050509250929050565b6000805b84518110156104e157600073ffffffffffffffffffffffffffffffffffffffff16858281518110610464576104646122a2565b602002602001015173ffffffffffffffffffffffffffffffffffffffff16036104cf5760405162461bcd60e51b815260206004820152601a60248201527f4445583a20746f6b656e206973207a65726f206164647265737300000000000060448201526064016102b8565b806104d9816122d1565b915050610431565b50826000036105325760405162461bcd60e51b815260206004820152601960248201527f4445583a20746172676574416d6f756e74206973207a65726f0000000000000060448201526064016102b8565b60008061040573ffffffffffffffffffffffffffffffffffffffff16338787876040516024016105659493929190612381565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f9782ac8100000000000000000000000000000000000000000000000000000000179052516105c8919061224f565b6000604051808303816000865af19150503d8060008114610605576040519150601f19603f3d011682016040523d82523d6000602084013e61060a565b606091505b5090925090508161061c573d60208201fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7a696e54a294052ede35d3a5f468fc14323f76ad9aaa64051e8d4f40f9fcb24d878380602001905181019061066991906123c3565b88604051610679939291906123dc565b60405180910390a250600195945050505050565b6000805b835181101561074157600073ffffffffffffffffffffffffffffffffffffffff168482815181106106c4576106c46122a2565b602002602001015173ffffffffffffffffffffffffffffffffffffffff160361072f5760405162461bcd60e51b815260206004820152601a60248201527f4445583a20746f6b656e206973207a65726f206164647265737300000000000060448201526064016102b8565b80610739816122d1565b915050610691565b50816000036107925760405162461bcd60e51b815260206004820152601960248201527f4445583a20737570706c79416d6f756e74206973207a65726f0000000000000060448201526064016102b8565b60008061040573ffffffffffffffffffffffffffffffffffffffff1685856040516024016107c1929190612401565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f4d60beb10000000000000000000000000000000000000000000000000000000017905251610824919061224f565b600060405180830381855afa9150503d806000811461085f576040519150601f19603f3d011682016040523d82523d6000602084013e610864565b606091505b50909250905081610876573d60208201fd5b8080602001905181019061088a91906123c3565b95945050505050565b60008073ffffffffffffffffffffffffffffffffffffffff84166108f95760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff831661095c5760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b60405173ffffffffffffffffffffffffffffffffffffffff85811660248301528416604482015260009081906104059060640160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f5859df3400000000000000000000000000000000000000000000000000000000179052516103ba919061224f565b6000805b8451811015610aa657600073ffffffffffffffffffffffffffffffffffffffff16858281518110610a2957610a296122a2565b602002602001015173ffffffffffffffffffffffffffffffffffffffff1603610a945760405162461bcd60e51b815260206004820152601a60248201527f4445583a20746f6b656e206973207a65726f206164647265737300000000000060448201526064016102b8565b80610a9e816122d1565b9150506109f6565b5082600003610af75760405162461bcd60e51b815260206004820152601960248201527f4445583a20737570706c79416d6f756e74206973207a65726f0000000000000060448201526064016102b8565b60008061040573ffffffffffffffffffffffffffffffffffffffff1633878787604051602401610b2a9493929190612381565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f579baa180000000000000000000000000000000000000000000000000000000017905251610b8d919061224f565b6000604051808303816000865af19150503d8060008114610bca576040519150601f19603f3d011682016040523d82523d6000602084013e610bcf565b606091505b50909250905081610be1573d60208201fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7a696e54a294052ede35d3a5f468fc14323f76ad9aaa64051e8d4f40f9fcb24d878784806020019051810190610c2f91906123c3565b604051610679939291906123dc565b60008073ffffffffffffffffffffffffffffffffffffffff8416610ca45760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff8316610d075760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b60405173ffffffffffffffffffffffffffffffffffffffff868116602483015285811660448301528416606482015260009081906104059060840160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f8ef239cf0000000000000000000000000000000000000000000000000000000017905251610da5919061224f565b600060405180830381855afa9150503d8060008114610de0576040519150601f19603f3d011682016040523d82523d6000602084013e610de5565b606091505b50909250905081610df7573d60208201fd5b80806020019051810190610e0b919061227e565b935093505050935093915050565b600073ffffffffffffffffffffffffffffffffffffffff8616610e7e5760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff8516610ee15760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b83600003610f315760405162461bcd60e51b815260206004820152601760248201527f4445583a206d6178416d6f756e7441206973207a65726f00000000000000000060448201526064016102b8565b82600003610f815760405162461bcd60e51b815260206004820152601760248201527f4445583a206d6178416d6f756e7442206973207a65726f00000000000000000060448201526064016102b8565b60405133602482015273ffffffffffffffffffffffffffffffffffffffff8781166044830152861660648201526084810185905260a4810184905260c4810183905260009081906104059060e40160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f67088d590000000000000000000000000000000000000000000000000000000017905251611032919061224f565b6000604051808303816000865af19150503d806000811461106f576040519150601f19603f3d011682016040523d82523d6000602084013e611074565b606091505b50909250905081611086573d60208201fd5b604080518781526020810187905273ffffffffffffffffffffffffffffffffffffffff808a1692908b169133917f5b6f5f6550282279c4e72b95a8ba538bea92c64dec9e8c7c08a556d4457225c891015b60405180910390a4506001979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff85166111525760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff84166111b55760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b821515806111c257508115155b61120e5760405162461bcd60e51b815260206004820181905260248201527f4445583a20696e76616c696420636f6e747269627574696f6e20616d6f756e7460448201526064016102b8565b60405133602482015273ffffffffffffffffffffffffffffffffffffffff8681166044830152851660648201526084810184905260a4810183905260009081906104059060c40160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f97a2051600000000000000000000000000000000000000000000000000000000179052516112b8919061224f565b6000604051808303816000865af19150503d80600081146112f5576040519150601f19603f3d011682016040523d82523d6000602084013e6112fa565b606091505b5090925090508161130c573d60208201fd5b604080518681526020810186905273ffffffffffffffffffffffffffffffffffffffff80891692908a169133917f8d1357284615c1e124990863d94a78bee5ad0b610eda8b2e250d792224bfa520910160405180910390a45060019695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff84166113d65760405162461bcd60e51b815260206004820152601860248201527f4445583a2077686f206973207a65726f2061646472657373000000000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff83166114395760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff821661149c5760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b60405173ffffffffffffffffffffffffffffffffffffffff858116602483015284811660448301528316606482015260009081906104059060840160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167faa02e9d3000000000000000000000000000000000000000000000000000000001790525161153a919061224f565b6000604051808303816000865af19150503d8060008114611577576040519150601f19603f3d011682016040523d82523d6000602084013e61157c565b606091505b5090925090508161158e573d60208201fd5b50600195945050505050565b6000805b835181101561164e57600073ffffffffffffffffffffffffffffffffffffffff168482815181106115d1576115d16122a2565b602002602001015173ffffffffffffffffffffffffffffffffffffffff160361163c5760405162461bcd60e51b815260206004820152601a60248201527f4445583a20746f6b656e206973207a65726f206164647265737300000000000060448201526064016102b8565b80611646816122d1565b91505061159e565b508160000361169f5760405162461bcd60e51b815260206004820152601960248201527f4445583a20746172676574416d6f756e74206973207a65726f0000000000000060448201526064016102b8565b60008061040573ffffffffffffffffffffffffffffffffffffffff1685856040516024016116ce929190612401565b60408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fdbcd19a20000000000000000000000000000000000000000000000000000000017905251610824919061224f565b600073ffffffffffffffffffffffffffffffffffffffff86166117965760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff85166117f95760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b836000036118495760405162461bcd60e51b815260206004820152601860248201527f4445583a2072656d6f76655368617265206973207a65726f000000000000000060448201526064016102b8565b60405133602482015273ffffffffffffffffffffffffffffffffffffffff8781166044830152861660648201526084810185905260a4810184905260c4810183905260009081906104059060e40160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f3531533200000000000000000000000000000000000000000000000000000000179052516118fa919061224f565b6000604051808303816000865af19150503d8060008114611937576040519150601f19603f3d011682016040523d82523d6000602084013e61193c565b606091505b5090925090508161194e573d60208201fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f038116623990e7d0fed04a27e35b5dc88000ea942b37360c5898ae750bfa5df6896040516110d791815260200190565b600073ffffffffffffffffffffffffffffffffffffffff8416611a295760405162461bcd60e51b815260206004820152601860248201527f4445583a2077686f206973207a65726f2061646472657373000000000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff8316611a8c5760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff8216611aef5760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b60405173ffffffffffffffffffffffffffffffffffffffff858116602483015284811660448301528316606482015260009081906104059060840160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167ff1e908f80000000000000000000000000000000000000000000000000000000017905251611b8d919061224f565b6000604051808303816000865af19150503d8060008114611bca576040519150601f19603f3d011682016040523d82523d6000602084013e611bcf565b606091505b50909250905081611be1573d60208201fd5b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167f0a871e96c44907fad6faf1446ccd03932216813ea3a548e12e72a217d7e8c96784806020019051810190611c5b91906123c3565b60405190815260200160405180910390a450600195945050505050565b60008073ffffffffffffffffffffffffffffffffffffffff8416611cde5760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff8316611d415760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b60405173ffffffffffffffffffffffffffffffffffffffff85811660248301528416604482015260009081906104059060640160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167ff4f31ede00000000000000000000000000000000000000000000000000000000179052516103ba919061224f565b600073ffffffffffffffffffffffffffffffffffffffff8316611e3c5760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e41206973207a65726f2061646472657373000000000060448201526064016102b8565b73ffffffffffffffffffffffffffffffffffffffff8216611e9f5760405162461bcd60e51b815260206004820152601b60248201527f4445583a20746f6b656e42206973207a65726f2061646472657373000000000060448201526064016102b8565b60405173ffffffffffffffffffffffffffffffffffffffff84811660248301528316604482015260009081906104059060640160408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffd73c4a0000000000000000000000000000000000000000000000000000000017905251611f35919061224f565b600060405180830381855afa9150503d8060008114611f70576040519150601f19603f3d011682016040523d82523d6000602084013e611f75565b606091505b50909250905081611f87573d60208201fd5b8080602001905181019061088a9190612423565b73ffffffffffffffffffffffffffffffffffffffff81168114611fbd57600080fd5b50565b8035611fcb81611f9b565b919050565b60008060408385031215611fe357600080fd5b8235611fee81611f9b565b91506020830135611ffe81611f9b565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261204957600080fd5b8135602067ffffffffffffffff8083111561206657612066612009565b8260051b604051601f19603f8301168101818110848211171561208b5761208b612009565b6040529384528581018301938381019250878511156120a957600080fd5b83870191505b848210156120cf576120c082611fc0565b835291830191908301906120af565b979650505050505050565b6000806000606084860312156120ef57600080fd5b833567ffffffffffffffff81111561210657600080fd5b61211286828701612038565b9660208601359650604090950135949350505050565b6000806040838503121561213b57600080fd5b823567ffffffffffffffff81111561215257600080fd5b61215e85828601612038565b95602094909401359450505050565b60008060006060848603121561218257600080fd5b833561218d81611f9b565b9250602084013561219d81611f9b565b915060408401356121ad81611f9b565b809150509250925092565b600080600080600060a086880312156121d057600080fd5b85356121db81611f9b565b945060208601356121eb81611f9b565b94979496505050506040830135926060810135926080909101359150565b6000806000806080858703121561221f57600080fd5b843561222a81611f9b565b9350602085013561223a81611f9b565b93969395505050506040820135916060013590565b6000825160005b818110156122705760208186018101518583015201612256565b506000920191825250919050565b6000806040838503121561229157600080fd5b505080516020909101519092909150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612329577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b600081518084526020808501945080840160005b8381101561237657815173ffffffffffffffffffffffffffffffffffffffff1687529582019590820190600101612344565b509495945050505050565b73ffffffffffffffffffffffffffffffffffffffff851681526080602082015260006123b06080830186612330565b6040830194909452506060015292915050565b6000602082840312156123d557600080fd5b5051919050565b6060815260006123ef6060830186612330565b60208301949094525060400152919050565b6040815260006124146040830185612330565b90508260208301529392505050565b60006020828403121561243557600080fd5b815161244081611f9b565b939250505056fea264697066735822122076990ba5171aa1ecfd2a1e3821293b648e54b20054b02099f9de37647292744964736f6c63430008120033'; + +export const JITOSOL_LDOT_LP_PREDEPLOY_CODE = '0x608060405234801561001057600080fd5b506111f1806100206000396000f3fe608060405234801561001057600080fd5b50600436106100d45760003560e01c806370a0823111610081578063a457c2d71161005b578063a457c2d71461019e578063a9059cbb146101b1578063dd62ed3e146101c457600080fd5b806370a0823114610170578063871069c01461018357806395d89b411461019657600080fd5b806323b872dd116100b257806323b872dd14610130578063313ce56714610143578063395093511461015d57600080fd5b806306fdde03146100d9578063095ea7b3146100f757806318160ddd1461011a575b600080fd5b6100e1610208565b6040516100ee9190610ed8565b60405180910390f35b61010a610105366004610f52565b610217565b60405190151581526020016100ee565b610122610231565b6040519081526020016100ee565b61010a61013e366004610f7c565b61023b565b61014b61025f565b60405160ff90911681526020016100ee565b61010a61016b366004610f52565b610269565b61012261017e366004610fb8565b6102b3565b61010a610191366004610fda565b6102be565b6100e16103bb565b61010a6101ac366004610f52565b6103c5565b61010a6101bf366004610f52565b610494565b6101226101d2366004610ffc565b73ffffffffffffffffffffffffffffffffffffffff91821660009081526020818152604080832093909416825291909152205490565b60606102126104a2565b905090565b60003361022581858561057d565b60019150505b92915050565b600061021261072f565b600033610249858285610802565b6102548585856108d7565b506001949350505050565b6000610212610a87565b3360008181526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919061022590829086906102ae90879061102f565b61057d565b600061022b82610b5a565b600033838203610355576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f45524332303a207472616e7366657220746f20746865207a65726f204163636f60448201527f756e74496433320000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b610360818585610c76565b838173ffffffffffffffffffffffffffffffffffffffff167f1d17b5770b13229fb9a0fbb368edadb0cd26837679bf04920d26031ad8fd6bdc856040516103a991815260200190565b60405180910390a35060019392505050565b6060610212610d8b565b3360008181526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490919083811015610487576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f000000000000000000000000000000000000000000000000000000606482015260840161034c565b610254828686840361057d565b6000336102258185856108d7565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f06fdde030000000000000000000000000000000000000000000000000000000017905290516060916000918291610400916105109190611069565b600060405180830381855afa9150503d806000811461054b576040519150601f19603f3d011682016040523d82523d6000602084013e610550565b606091505b50909250905081610562573d60208201fd5b8080602001905181019061057691906110b4565b9250505090565b73ffffffffffffffffffffffffffffffffffffffff831661061f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f7265737300000000000000000000000000000000000000000000000000000000606482015260840161034c565b73ffffffffffffffffffffffffffffffffffffffff82166106c2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f7373000000000000000000000000000000000000000000000000000000000000606482015260840161034c565b73ffffffffffffffffffffffffffffffffffffffff8381166000818152602081815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f18160ddd000000000000000000000000000000000000000000000000000000001790529051600091829182916104009161079c9190611069565b600060405180830381855afa9150503d80600081146107d7576040519150601f19603f3d011682016040523d82523d6000602084013e6107dc565b606091505b509092509050816107ee573d60208201fd5b80806020019051810190610576919061117f565b73ffffffffffffffffffffffffffffffffffffffff838116600090815260208181526040808320938616835292905220547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146108d157818110156108c4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000604482015260640161034c565b6108d1848484840361057d565b50505050565b73ffffffffffffffffffffffffffffffffffffffff831661097a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f6472657373000000000000000000000000000000000000000000000000000000606482015260840161034c565b73ffffffffffffffffffffffffffffffffffffffff8216610a1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f6573730000000000000000000000000000000000000000000000000000000000606482015260840161034c565b610a28838383610df9565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161072291815260200190565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f313ce5670000000000000000000000000000000000000000000000000000000017905290516000918291829161040091610af49190611069565b600060405180830381855afa9150503d8060008114610b2f576040519150601f19603f3d011682016040523d82523d6000602084013e610b34565b606091505b50909250905081610b46573d60208201fd5b808060200190518101906105769190611198565b60405173ffffffffffffffffffffffffffffffffffffffff821660248201526000908190819061040090604401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f70a082310000000000000000000000000000000000000000000000000000000017905251610c089190611069565b600060405180830381855afa9150503d8060008114610c43576040519150601f19603f3d011682016040523d82523d6000602084013e610c48565b606091505b50909250905081610c5a573d60208201fd5b80806020019051810190610c6e919061117f565b949350505050565b60405173ffffffffffffffffffffffffffffffffffffffff841660248201526044810183905260648101829052600090819061040090608401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f895904980000000000000000000000000000000000000000000000000000000017905251610d309190611069565b6000604051808303816000865af19150503d8060008114610d6d576040519150601f19603f3d011682016040523d82523d6000602084013e610d72565b606091505b50909250905081610d84573d60208201fd5b5050505050565b60408051600481526024810182526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f95d89b410000000000000000000000000000000000000000000000000000000017905290516060916000918291610400916105109190611069565b60405173ffffffffffffffffffffffffffffffffffffffff84811660248301528316604482015260648101829052600090819061040090608401604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fbeabacc80000000000000000000000000000000000000000000000000000000017905251610d309190611069565b60005b83811015610ecf578181015183820152602001610eb7565b50506000910152565b6020815260008251806020840152610ef7816040850160208701610eb4565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610f4d57600080fd5b919050565b60008060408385031215610f6557600080fd5b610f6e83610f29565b946020939093013593505050565b600080600060608486031215610f9157600080fd5b610f9a84610f29565b9250610fa860208501610f29565b9150604084013590509250925092565b600060208284031215610fca57600080fd5b610fd382610f29565b9392505050565b60008060408385031215610fed57600080fd5b50508035926020909101359150565b6000806040838503121561100f57600080fd5b61101883610f29565b915061102660208401610f29565b90509250929050565b8082018082111561022b577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000825161107b818460208701610eb4565b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156110c657600080fd5b815167ffffffffffffffff808211156110de57600080fd5b818401915084601f8301126110f257600080fd5b81518181111561110457611104611085565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561114a5761114a611085565b8160405282815287602084870101111561116357600080fd5b611174836020830160208801610eb4565b979650505050505050565b60006020828403121561119157600080fd5b5051919050565b6000602082840312156111aa57600080fd5b815160ff81168114610fd357600080fdfea2646970667358221220054e18bc0b3f0a07464c0663a4015c3aa60d5e66cf648752fbf056e6eeefc5e864736f6c63430008120033'; diff --git a/src/__tests__/testUtils.ts b/src/__tests__/testUtils.ts index 8cee75a..d60739f 100644 --- a/src/__tests__/testUtils.ts +++ b/src/__tests__/testUtils.ts @@ -1,12 +1,13 @@ import { ApiPromise, Keyring, WsProvider } from '@polkadot/api'; import { ERC20__factory } from '@acala-network/asset-router/dist/typechain-types'; import { JsonRpcProvider } from '@ethersproject/providers'; +import { SubmittableExtrinsic } from '@polkadot/api/promise/types'; import { Wallet } from 'ethers'; import { expect } from 'vitest'; import { parseUnits } from 'ethers/lib/utils'; import axios from 'axios'; -import { RELAYER_URL } from '../consts'; +import { apiUrl } from '../consts'; const keyring = new Keyring({ type: 'sr25519' }); const alice = keyring.addFromUri('//Alice'); @@ -41,27 +42,50 @@ export const sudoTransferToken = async ( provider: new WsProvider('ws://localhost:8000'), }); - const tx = api.tx.evm.call(tokenAddr, data!, 0, 1000000, 64, []); + const tx = api.tx.evm.call(tokenAddr, data!, 0, 1000000, 100000, []); const extrinsic = api.tx.sudo.sudoAs(fromAddr, tx); const hash = await extrinsic.signAndSend(alice); - const receipt = await provider.waitForTransaction(hash.toHex()); + const receipt = await provider.waitForTransaction(hash.toHex(), 1, 30000); expect(receipt.status).to.eq(1); await api.disconnect(); } }; +export const sudoSendAndWait = ( + api: ApiPromise, + tx: SubmittableExtrinsic, +) => new Promise((resolve, reject) => { + api.tx.sudo.sudo(tx).signAndSend(alice, ({ status, events }) => { + if (status.isInBlock || status.isFinalized) { + events.forEach(({ event }) => { + const { data, method, section } = event; + if (section === 'system' && method === 'ExtrinsicFailed') { + reject(new Error(`Transaction failed: ${data.toString()}`)); + } + }); + + // FIXME: should not wait for 3s, chopsticks issue? + setTimeout(() => { + resolve(status.hash.toString()); + }, 3000); + } + }).catch(error => { + reject(error); + }); +}); + export const transferToken = async ( toAddr: string, signer: Wallet, tokenAddr: string, - amount: number, + humanAmount: number, ) => { const token = ERC20__factory.connect(tokenAddr, signer); const decimals = await token.decimals(); - const routeAmount = parseUnits(String(amount), decimals); + const routeAmount = parseUnits(String(humanAmount), decimals); const routerBal = await token.balanceOf(toAddr); if (routerBal.gt(0)) { @@ -80,7 +104,7 @@ export const expectError = (err: any, msg: any, code: number) => { expect(err.response?.status).to.equal(code); expect(err.response?.data.error).to.deep.equal(msg); } else { - throw new Error('not an axios error'); + throw err; } }; @@ -107,22 +131,33 @@ const _axiosPost = (url: string) => async (params: any) => { }; export const api = { - shouldRouteXcm: _axiosGet(RELAYER_URL.SHOULD_ROUTE_XCM), - shouldRouteWormhole: _axiosGet(RELAYER_URL.SHOULD_ROUTE_WORMHOLE), - shouldRelay: _axiosGet(RELAYER_URL.SHOULD_RELAY), - relay: _axiosPost(RELAYER_URL.RELAY), - routeXcm: _axiosPost(RELAYER_URL.ROUTE_XCM), - relayAndRoute: _axiosPost(RELAYER_URL.RELAY_AND_ROUTE), - relayAndRouteBatch: _axiosPost(RELAYER_URL.RELAY_AND_ROUTE_BATCH), - routeWormhole: _axiosPost(RELAYER_URL.ROUTE_WORMHOLE), - noRoute: _axiosPost(RELAYER_URL.NO_ROUTE), - version: _axiosGet(RELAYER_URL.VERSION), - testTimeout: _axiosPost(RELAYER_URL.TEST_TIMEOUT), - health: _axiosGet(RELAYER_URL.HEALTH), - shouldRouteHoma: _axiosGet(RELAYER_URL.SHOULD_ROUTER_HOMA), - routeHoma: _axiosPost(RELAYER_URL.ROUTE_HOMA), - routeHomaAuto: _axiosPost(RELAYER_URL.ROUTE_HOMA_AUTO), - routeStatus: _axiosGet(RELAYER_URL.ROUTE_STATUS), - shouldRouteEuphrates: _axiosGet(RELAYER_URL.SHOULD_ROUTER_EUPHRATES), - routeEuphrates: _axiosPost(RELAYER_URL.ROUTE_EUPHRATES), + shouldRelay: _axiosGet(apiUrl.shouldRelay), + relay: _axiosPost(apiUrl.relay), + + shouldRouteXcm: _axiosGet(apiUrl.shouldRouteXcm), + routeXcm: _axiosPost(apiUrl.routeXcm), + + shouldRouteWormhole: _axiosGet(apiUrl.shouldRouteWormhole ), + routeWormhole: _axiosPost(apiUrl.routeWormhole), + relayAndRoute: _axiosPost(apiUrl.relayAndRoute), + relayAndRouteBatch: _axiosPost(apiUrl.relayAndRouteBatch), + + shouldRouteHoma: _axiosGet(apiUrl.shouldRouteHoma), + routeHoma: _axiosPost(apiUrl.routeHoma), + routeHomaAuto: _axiosPost(apiUrl.routeHomaAuto), + routeStatus: _axiosGet(apiUrl.routeStatus), + + shouldRouteEuphrates: _axiosGet(apiUrl.shouldRouteEuphrates), + routeEuphrates: _axiosPost(apiUrl.routeEuphrates), + + shouldRouteDropAndBootstrap: _axiosGet(apiUrl.shouldRouteDropAndBootstrap), + routeDropAndBootstrap: _axiosPost(apiUrl.routeDropAndBootstrap), + + routerInfo: _axiosGet(apiUrl.routerInfo), + saveRouterInfo: _axiosPost(apiUrl.saveRouterInfo), + + noRoute: _axiosPost(apiUrl.noRoute), + version: _axiosGet(apiUrl.version), + testTimeout: _axiosPost(apiUrl.testTimeout), + health: _axiosGet(apiUrl.health), }; diff --git a/src/api/dropAndBootstrap.ts b/src/api/dropAndBootstrap.ts new file mode 100644 index 0000000..8e630ac --- /dev/null +++ b/src/api/dropAndBootstrap.ts @@ -0,0 +1,108 @@ +import { ACA, LDOT } from '@acala-network/contracts/utils/AcalaTokens'; +import { DEX } from '@acala-network/contracts/utils/Predeploy'; +import { DropAndBootstrapStakeFactory__factory } from '@acala-network/asset-router/dist/typechain-types'; +import { ERC20__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts'; +import { ROUTER_TOKEN_INFO } from '@acala-network/asset-router/dist/consts'; +import { constants } from 'ethers'; + +import { DROP_AMOUNT_ACA, DROP_SWAP_AMOUNT_JITOSOL, EUPHRATES_ADDR } from '../consts'; +import { + DropAndBootstrapParams, + Mainnet, + _populateRelayTx, + _populateRouteTx, + getChainConfig, + getMainnetChainId, +} from '../utils'; + +const JITOSOL_ADDR = ROUTER_TOKEN_INFO.jitosol.acalaAddr; +const DEFAULT_DROP_AND_BOOTSTRAP_PARAMS = { + euphrates: EUPHRATES_ADDR, + dex: DEX, + dropToken: ACA, + poolId: 7, +}; + +const prepareDropAndBoostrap = async () => { + const chainId = getMainnetChainId(Mainnet.Acala); + const chainConfig = await getChainConfig(chainId); + const { feeAddr, dropAndBootstrapStakeFactoryAddr, wallet } = chainConfig; + + const factory = DropAndBootstrapStakeFactory__factory + .connect(dropAndBootstrapStakeFactoryAddr!, wallet); + + return { factory, feeAddr, relayerAddr: wallet.address }; +}; + +export const shouldRouteDropAndBoostrap = async (params: DropAndBootstrapParams) => { + try { + const { factory, feeAddr, relayerAddr } = await prepareDropAndBoostrap(); + + const dropFee = params.gasDrop ? DROP_SWAP_AMOUNT_JITOSOL : 0; + const dropAmountAca = params.gasDrop ? DROP_AMOUNT_ACA : 0; + const otherContributionToken = params.feeToken === 'jitosol' ? LDOT : JITOSOL_ADDR; + + const insts = { + ...DEFAULT_DROP_AND_BOOTSTRAP_PARAMS, + recipient: params.recipient, + feeReceiver: relayerAddr, + dropFee, + otherContributionToken, + }; + + /* ---------- TODO: remove this check later after approved max ---------- */ + const aca = ERC20__factory.connect(ACA, factory.signer); + const allowance = await aca.allowance(relayerAddr, factory.address); + if (allowance.lt(dropAmountAca)) { + console.log('granting allowance'); + await (await aca.approve(factory.address, constants.MaxUint256)).wait(); + } else { + console.log('allowance ok'); + } + /* ----------------------------------------------------------------------- */ + + const routerAddr = await factory.callStatic.deployDropAndBootstrapStakeRouter( + feeAddr, + insts, + dropAmountAca, + ); + + return { + shouldRoute: true, + routerAddr, + }; + } catch (err) { + return { + shouldRoute: false, + msg: err.message, + }; + } +}; + +export const routeDropAndBoostrap = async (params: DropAndBootstrapParams) => { + const { factory, feeAddr, relayerAddr } = await prepareDropAndBoostrap(); + + const dropFee = params.gasDrop ? DROP_SWAP_AMOUNT_JITOSOL : 0; + const dropAmountAca = params.gasDrop ? DROP_AMOUNT_ACA : 0; + const [tokenAddr, otherContributionToken] = params.feeToken === 'jitosol' + ? [JITOSOL_ADDR, LDOT] + : [LDOT, JITOSOL_ADDR]; + + const insts = { + ...DEFAULT_DROP_AND_BOOTSTRAP_PARAMS, + recipient: params.recipient, + feeReceiver: relayerAddr, + dropFee, + otherContributionToken, + }; + + const tx = await factory.deployDropAndBootstrapStakeRouterAndRoute( + feeAddr, + insts, + tokenAddr, + dropAmountAca, + ); + const receipt = await tx.wait(); + + return receipt.transactionHash; +}; diff --git a/src/api/index.ts b/src/api/index.ts index ed74015..dd15763 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -6,3 +6,4 @@ export * from './xcm'; export * from './homa'; export * from './euphrates'; export * from './health'; +export * from './swapAndRoute'; diff --git a/src/api/swapAndRoute.ts b/src/api/swapAndRoute.ts index ec5b3ad..e0200f8 100644 --- a/src/api/swapAndRoute.ts +++ b/src/api/swapAndRoute.ts @@ -3,7 +3,7 @@ import { ERC20__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts import { SwapAndStakeEuphratesFactory__factory } from '@acala-network/asset-router/dist/typechain-types'; import { constants } from 'ethers'; -import { EUPHRATES_ADDR, EUPHRATES_POOLS, RELAYER_ADDR, SWAP_SUPPLY_TOKENS } from '../consts'; +import { DROP_SWAP_AMOUNT_JITOSOL, EUPHRATES_ADDR, EUPHRATES_POOLS, RELAYER_ADDR, SWAP_SUPPLY_TOKENS, DROP_AMOUNT_ACA } from '../consts'; import { Mainnet, RouteError, @@ -13,16 +13,13 @@ import { getChainConfig, getMainnetChainId, } from '../utils'; -import { parseUnits } from 'ethers/lib/utils'; -const TARGET_AMOUNT_ACA = parseUnits('3', 12); -const SUPPLY_AMOUNT_JITOSOL = parseUnits('0.0035', 9); const DEFAULT_SWAP_AND_ROUTE_PARAMS = { maker: RELAYER_ADDR, targetToken: ACA, euphrates: EUPHRATES_ADDR, - targetAmount: TARGET_AMOUNT_ACA, - supplyAmount: SUPPLY_AMOUNT_JITOSOL, + targetAmount: DROP_AMOUNT_ACA, + supplyAmount: DROP_SWAP_AMOUNT_JITOSOL, }; const prepareSwapAndRoute = async (chain: Mainnet) => { @@ -53,7 +50,7 @@ export const shouldSwapAndRoute = async (params: SwapAndRouteParams) => { /* ---------- TODO: remove this check later after approved max ---------- */ const aca = ERC20__factory.connect(ACA, factory.signer); const allowance = await aca.allowance(insts.maker, factory.address); - if (allowance.lt(TARGET_AMOUNT_ACA)) { + if (allowance.lt(DROP_AMOUNT_ACA)) { await (await aca.approve(factory.address, constants.MaxUint256)).wait(); } /* ----------------------------------------------------------------------- */ @@ -61,7 +58,7 @@ export const shouldSwapAndRoute = async (params: SwapAndRouteParams) => { const routerAddr = await factory.callStatic.deploySwapAndStakeEuphratesRouter( feeAddr, insts, - TARGET_AMOUNT_ACA + DROP_AMOUNT_ACA ); return { @@ -78,7 +75,7 @@ export const shouldSwapAndRoute = async (params: SwapAndRouteParams) => { export const swapAndRoute = async (params: SwapAndRouteParams) => { if (params.token === undefined) { - throw new RouteError(' param is required for swap and route', params); + throw new RouteError('[token] param is required for swap and route', params); } if (!SWAP_SUPPLY_TOKENS.includes(params.token as any)) { @@ -96,7 +93,7 @@ export const swapAndRoute = async (params: SwapAndRouteParams) => { feeAddr, insts, params.token, - TARGET_AMOUNT_ACA, + DROP_AMOUNT_ACA, ); const receipt = await tx.wait(); diff --git a/src/consts.ts b/src/consts.ts index 2814c4f..7b1c3ff 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -3,6 +3,7 @@ import { CHAIN_ID_KARURA, } from '@certusone/wormhole-sdk'; import { ROUTER_TOKEN_INFO } from '@acala-network/asset-router/dist/consts'; +import { parseUnits } from 'ethers/lib/utils'; import dotenv from 'dotenv'; dotenv.config({ path: '.env' }); @@ -41,58 +42,39 @@ const RELAYER_BASE_URL = 'http://localhost:3111'; // const RELAYER_BASE_URL = 'https://relayer.aca-dev.network'; // const RELAYER_BASE_URL = 'https://relayer.aca-api.network'; -export const RELAYER_API = { - SHOULD_RELAY: '/shouldRelay', - RELAY: '/relay', +export const getRelayerUrl = (path: string) => `${RELAYER_BASE_URL}${path}`; - SHOULD_ROUTE_XCM: '/shouldRouteXcm', - ROUTE_XCM: '/routeXcm', +export const apiUrl = { + shouldRelay: getRelayerUrl('/shouldRelay'), + relay: getRelayerUrl('/relay'), - SHOULD_ROUTE_WORMHOLE: '/shouldRouteWormhole', - ROUTE_WORMHOLE: '/routeWormhole', - RELAY_AND_ROUTE: '/relayAndRoute', - RELAY_AND_ROUTE_BATCH: '/relayAndRouteBatch', + shouldRouteXcm: getRelayerUrl('/shouldRouteXcm'), + routeXcm: getRelayerUrl('/routeXcm'), - SHOULD_ROUTER_HOMA: '/shouldRouteHoma', - ROUTE_HOMA: '/routeHoma', - ROUTE_HOMA_AUTO: '/routeHomaAuto', - ROUTE_STATUS: '/routeStatus', + shouldRouteWormhole: getRelayerUrl('/shouldRouteWormhole'), + routeWormhole: getRelayerUrl('/routeWormhole'), + relayAndRoute: getRelayerUrl('/relayAndRoute'), + relayAndRouteBatch: getRelayerUrl('/relayAndRouteBatch'), - SHOULD_ROUTER_EUPHRATES: '/shouldRouteEuphrates', - ROUTE_EUPHRATES: '/routeEuphrates', + shouldRouteHoma: getRelayerUrl('/shouldRouteHoma'), + routeHoma: getRelayerUrl('/routeHoma'), + routeHomaAuto: getRelayerUrl('/routeHomaAuto'), + routeStatus: getRelayerUrl('/routeStatus'), - NO_ROUTE: '/noRoute', - VERSION: '/version', - TEST_TIMEOUT: '/testTimeout', - HEALTH: '/health', -} as const; - -// TODO: make this getRelayerUrl() -export const RELAYER_URL = { - SHOULD_RELAY: `${RELAYER_BASE_URL}${RELAYER_API.SHOULD_RELAY}`, - RELAY: `${RELAYER_BASE_URL}${RELAYER_API.RELAY}`, - - SHOULD_ROUTE_XCM: `${RELAYER_BASE_URL}${RELAYER_API.SHOULD_ROUTE_XCM}`, - ROUTE_XCM: `${RELAYER_BASE_URL}${RELAYER_API.ROUTE_XCM}`, - - SHOULD_ROUTE_WORMHOLE: `${RELAYER_BASE_URL}${RELAYER_API.SHOULD_ROUTE_WORMHOLE}`, - ROUTE_WORMHOLE: `${RELAYER_BASE_URL}${RELAYER_API.ROUTE_WORMHOLE}`, - RELAY_AND_ROUTE: `${RELAYER_BASE_URL}${RELAYER_API.RELAY_AND_ROUTE}`, - RELAY_AND_ROUTE_BATCH: `${RELAYER_BASE_URL}${RELAYER_API.RELAY_AND_ROUTE_BATCH}`, + shouldRouteEuphrates: getRelayerUrl('/shouldRouteEuphrates'), + routeEuphrates: getRelayerUrl('/routeEuphrates'), - SHOULD_ROUTER_HOMA: `${RELAYER_BASE_URL}${RELAYER_API.SHOULD_ROUTER_HOMA}`, - ROUTE_HOMA: `${RELAYER_BASE_URL}${RELAYER_API.ROUTE_HOMA}`, - ROUTE_HOMA_AUTO: `${RELAYER_BASE_URL}${RELAYER_API.ROUTE_HOMA_AUTO}`, - ROUTE_STATUS: `${RELAYER_BASE_URL}${RELAYER_API.ROUTE_STATUS}`, + shouldRouteDropAndBootstrap: getRelayerUrl('/shouldRouteDropAndBootstrap'), + routeDropAndBootstrap: getRelayerUrl('/routeDropAndBootstrap'), - SHOULD_ROUTER_EUPHRATES: `${RELAYER_BASE_URL}${RELAYER_API.SHOULD_ROUTER_EUPHRATES}`, - ROUTE_EUPHRATES: `${RELAYER_BASE_URL}${RELAYER_API.ROUTE_EUPHRATES}`, + routerInfo: getRelayerUrl('/routerInfo'), + saveRouterInfo: getRelayerUrl('/saveRouterInfo'), - NO_ROUTE: `${RELAYER_BASE_URL}${RELAYER_API.NO_ROUTE}`, - VERSION: `${RELAYER_BASE_URL}${RELAYER_API.VERSION}`, - TEST_TIMEOUT: `${RELAYER_BASE_URL}${RELAYER_API.TEST_TIMEOUT}`, - HEALTH: `${RELAYER_BASE_URL}${RELAYER_API.HEALTH}`, -} as const; + noRoute: getRelayerUrl('/noRoute'), + version: getRelayerUrl('/version'), + testTimeout: getRelayerUrl('/testTimeout'), + health: getRelayerUrl('/health'), +}; /* --------------- thredhold amount is defined as "amount that will show on VAA" @@ -254,16 +236,20 @@ export const TESTNET_MODE_WARNING = ` `; export const EUPHRATES_ADDR = '0x7Fe92EC600F15cD25253b421bc151c51b0276b7D'; -export const EUPHRATES_POOLS = ['0', '1', '2', '3', '6']; +export const EUPHRATES_POOLS = ['0', '1', '2', '3', '6', '7']; export const SWAP_SUPPLY_TOKENS = [ ROUTER_TOKEN_INFO.jitosol.acalaAddr, ]; export const RELAYER_ADDR = '0x8B5C2F71eFa2d88A20E0e1c8EDFeA3767B2ab230'; +// swap 0.0035 jitosol to 3 ACA for gas drop +export const DROP_SWAP_AMOUNT_JITOSOL = parseUnits('0.0035', 9); +export const DROP_AMOUNT_ACA = parseUnits('3', 12); + export const SECOND = 1000; export const MINUTE = 60 * SECOND; export const HOUR = 60 * MINUTE; export const DAY = 24 * HOUR; -export const VERSION = '1.8.5'; +export const VERSION = '1.9.0-5'; diff --git a/src/middlewares/router.ts b/src/middlewares/router.ts index dae708f..3863e74 100644 --- a/src/middlewares/router.ts +++ b/src/middlewares/router.ts @@ -3,7 +3,9 @@ import { Schema } from 'yup'; import { NoRouteError, + dropAndBootstrapSchema, logger, + parseIp, relayAndRouteSchema, relaySchema, routeEuphratesSchema, @@ -31,9 +33,10 @@ import { shouldRouteHoma, shouldRouteWormhole, shouldRouteXcm, + shouldSwapAndRoute, + swapAndRoute, } from '../api'; -import { parseIp } from '../utils/formatter'; -import { shouldSwapAndRoute, swapAndRoute } from '../api/swapAndRoute'; +import { routeDropAndBoostrap, shouldRouteDropAndBoostrap } from '../api/dropAndBootstrap'; interface RouterConfig { schema?: Schema; @@ -70,6 +73,10 @@ const ROUTER_CONFIGS: { schema: swapAndRouteSchema, handler: shouldSwapAndRoute, }, + '/shouldRouteDropAndBootstrap': { + schema: dropAndBootstrapSchema, + handler: shouldRouteDropAndBoostrap, + }, '/health': { handler: healthCheck, }, @@ -119,6 +126,10 @@ const ROUTER_CONFIGS: { schema: swapAndRouteSchema, handler: swapAndRoute, }, + '/routeDropAndBootstrap': { + schema: dropAndBootstrapSchema, + handler: routeDropAndBoostrap, + }, }, }; @@ -137,8 +148,8 @@ const handleRoute = async (req: Request, res: Response) => { throw new NoRouteError(`${reqPath} not supported`); } - await config.schema?.validate(args, { abortEarly: false }); - const data = await config.handler(args); + const validatedArgs = await config.schema?.validate(args, { abortEarly: false }); + const data = await config.handler(validatedArgs); logger.info({ data }, `✨ ${reqPath}`); res.end(JSON.stringify({ data })); diff --git a/src/utils/configureEnv.ts b/src/utils/configureEnv.ts index fe744a0..d18aac1 100644 --- a/src/utils/configureEnv.ts +++ b/src/utils/configureEnv.ts @@ -21,6 +21,7 @@ export type ChainConfig = { accountHelperAddr?: string; euphratesFactoryAddr?: string; swapAndStakeFactoryAddr?: string; + dropAndBootstrapStakeFactoryAddr?: string; isTestnet: boolean; }; diff --git a/src/utils/index.ts b/src/utils/index.ts index 34bf4ba..c204da4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export * from './validate'; export * from './wormhole'; export * from './address'; export * from './error'; +export * from './formatter'; diff --git a/src/utils/validate.ts b/src/utils/validate.ts index c70f90c..fec5efd 100644 --- a/src/utils/validate.ts +++ b/src/utils/validate.ts @@ -1,5 +1,5 @@ import { CHAINS, CHAIN_ID_ACALA, CHAIN_ID_KARURA, ChainId } from '@certusone/wormhole-sdk'; -import { ObjectSchema, mixed, number, object, string } from 'yup'; +import { ObjectSchema, boolean, mixed, number, object, string } from 'yup'; export enum Mainnet { Acala = 'acala', @@ -53,6 +53,12 @@ export interface RouteParamsEuphrates { // does not support general swap yet export type SwapAndRouteParams = RouteParamsEuphrates; +export interface DropAndBootstrapParams { + recipient: string; // dest evm address + gasDrop: boolean; // swap jitosol for aca gas drop? + feeToken: string; // token to pay for router fee, either 'jitosol' or 'ldot' +} + export interface RelayAndRouteParams extends RouteParamsXcm { signedVAA: string; } @@ -113,6 +119,24 @@ export const routeEuphratesSchema: ObjectSchema = object({ export const swapAndRouteSchema = routeEuphratesSchema; +export const dropAndBootstrapSchema: ObjectSchema = object({ + recipient: string().required(), + gasDrop: boolean().required(), + feeToken: string().oneOf( + ['jitosol', 'ldot'], + 'feeToken must be one of { jitosol, ldot }' + ).required(), +}).test( + 'no-gas-drop-for-ldot', + 'does not support gasDrop when feeToken is ldot', + values => { + if (values.feeToken === 'ldot') { + return values.gasDrop === false; + } + return true; + } +); + export const routeStatusSchema: ObjectSchema = object({ id: string(), destAddr: string(), diff --git a/yarn.lock b/yarn.lock index 4ff7051..28a3069 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,10 +17,10 @@ "@acala-network/api-derive" "6.0.4" "@acala-network/types" "6.0.4" -"@acala-network/asset-router@~1.0.18": - version "1.0.18" - resolved "https://registry.yarnpkg.com/@acala-network/asset-router/-/asset-router-1.0.18.tgz#d03c48ae9396a97a11edffe87b0b33784bc5fdc5" - integrity sha512-Tg8Ct4lwIWgkhqPNFpCfo6WGxdSdPuSB7lKn9H7mLrV/qJN5nLyPECrxHOaBF2YGklCYtStGI61xglh15qgXEQ== +"@acala-network/asset-router@~1.0.19-14": + version "1.0.19-14" + resolved "https://registry.yarnpkg.com/@acala-network/asset-router/-/asset-router-1.0.19-14.tgz#60a75e738e5313284f98945d128cd692ebd3947b" + integrity sha512-WnQW0nE3S9LJmStKHt9MwEbGk1W7q9oZCqFzLo3P5fGC/Dvee0geTEzTryj3d3VgiIU/Td/g1DSeEl0qvEiiOQ== dependencies: "@acala-network/contracts" "^4.5.0" "@acala-network/eth-providers" "^2.7.13"