From b0adda07191337b21c13b86b3fe2ed699a7adc95 Mon Sep 17 00:00:00 2001 From: Shunji Zhan Date: Wed, 23 Oct 2024 15:06:29 +0800 Subject: [PATCH] swap and lp router (#77) * update * update docs * fix test * fix fee receiver addr * fix test * add rescue endpoint * update doc * bump * polish and add tests * drop and boostrap stake router * bump v1.9.0-4 * update doc * add feeToken params * bump v1.9.0-5 * added tests * fix and more tests * skip swap and route tests * tests for ldot as fee token * fix * clean up swap and lp stuff * cleanup * fix * swap and lp router This reverts commit 41dd5702c4b903ca8b085907f784c1b6e2e21141. * polish * add tests * increase timeout * tests for rescue --- README.md | 94 +++++++ .../__snapshots__/swapAndLp.test.ts.snap | 19 ++ src/__tests__/bootstrap.test.ts | 55 +++- src/__tests__/configs/acala.yml | 2 +- src/__tests__/euphrates.test.ts | 14 +- src/__tests__/swapAndLp.test.ts | 259 ++++++++++++++++++ src/__tests__/testConsts.ts | 6 + src/__tests__/testUtils.ts | 14 +- src/api/index.ts | 1 + src/api/swapAndLp.ts | 139 ++++++++++ src/consts.ts | 4 + src/middlewares/router.ts | 16 ++ src/utils/configureEnv.ts | 1 + src/utils/validate.ts | 14 + vitest.config.ts | 2 +- 15 files changed, 615 insertions(+), 25 deletions(-) create mode 100644 src/__tests__/__snapshots__/swapAndLp.test.ts.snap create mode 100644 src/__tests__/swapAndLp.test.ts create mode 100644 src/api/swapAndLp.ts diff --git a/README.md b/README.md index aea52e7..51ef81f 100644 --- a/README.md +++ b/README.md @@ -609,6 +609,100 @@ POST /routeDropAndBootstrap } ``` +### `/shouldRouteSwapAndLp` +checks if the relayer can route this request, returns router address +``` +GET /shouldRouteSwapAndLp +params: { + poolId: string; // euphrates pool id + recipient: string; // dest evm address + swapAmount: string; // how many token to swap before adding liquidity + minShareAmount?: string; // add liquidity min share amount (default: 0) +} +``` + +example +``` +GET /shouldRouteSwapAndLp?recipient=0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6&swapAmount=100000000&poolId=7 +=> +{ + "data": { + "shouldRoute": true, + "routerAddr": "0xC5a363695957469963c15Bf87B00B25eAbE8D234" + } +} +``` + +### `/routeSwapAndLp` +- swap small amount of token and airdrop ACA to recipient +- swap `swapAmount` token to LDOT, then add LP, refund the remaining token recipient +- stake Lp to euphrates for the recipient +- returns the txhash +``` +POST /routeSwapAndLp +data: { + poolId: string; // euphrates pool id + recipient: string; // dest evm address + token: string; // token to route + swapAmount: string; // how many token to swap before adding liquidity + minShareAmount?: string; // add liquidity min share amount (default: 0) +} +``` + +example +``` +POST /routeSwapAndLp +data: { + "poolId": 7, + "recipient": "0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6", + "token": "0xa7fb00459f5896c3bd4df97870b44e868ae663d7", + "swapAmount": 100000000 +} + +=> tx hash +{ + data: '0xe1c82c53796d82d87d2e31e289b3cc8ff18e304b8ac95f2bd7548a1706bb8655' +} + +/* ---------- when error ---------- */ +// similar to /routeXcm +``` + +### `/rescueSwapAndLp` +- perform gas drop +- rescue token to recipient + +``` +POST /rescueSwapAndLp +data: { + poolId: string; // euphrates pool id + recipient: string; // dest evm address + token: string; // token to route + swapAmount: string; // how many token to swap before adding liquidity + minShareAmount?: string; // add liquidity min share amount (default: 0) +} +``` + +example +``` +POST /rescueSwapAndLp +data: { + "poolId": 7, + "recipient": "0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6", + "token": "0xa7fb00459f5896c3bd4df97870b44e868ae663d7", + "swapAmount": 100000000 +} + +=> tx hash +{ + data: '0xe1c82c53796d82d87d2e31e289b3cc8ff18e304b8ac95f2bd7548a1706bb8655' +} + +/* ---------- when error ---------- */ +// similar to /routeXcm +``` + + ## Routing Process A complete working flow can be found in [routing e2e tests](./src/__tests__/route.test.ts). diff --git a/src/__tests__/__snapshots__/swapAndLp.test.ts.snap b/src/__tests__/__snapshots__/swapAndLp.test.ts.snap new file mode 100644 index 0000000..a2781b9 --- /dev/null +++ b/src/__tests__/__snapshots__/swapAndLp.test.ts.snap @@ -0,0 +1,19 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`/shouldRouteSwapAndLp > when should route 1`] = ` +{ + "data": { + "routerAddr": "0x8ed211e6e940955A8ccF2ec2BA792eDfd7Cca9bD", + "shouldRoute": true, + }, +} +`; + +exports[`/shouldRouteSwapAndLp > when should route 2`] = ` +{ + "data": { + "routerAddr": "0x8ed211e6e940955A8ccF2ec2BA792eDfd7Cca9bD", + "shouldRoute": true, + }, +} +`; diff --git a/src/__tests__/bootstrap.test.ts b/src/__tests__/bootstrap.test.ts index 4d142fb..69f91a7 100644 --- a/src/__tests__/bootstrap.test.ts +++ b/src/__tests__/bootstrap.test.ts @@ -1,5 +1,4 @@ -import { ADDRESSES, ROUTER_TOKEN_INFO } from '@acala-network/asset-router/dist/consts'; -import { AcalaJsonRpcProvider } from '@acala-network/eth-providers'; +import { ADDRESSES } from '@acala-network/asset-router/dist/consts'; 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'; @@ -10,30 +9,29 @@ 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 { EUPHRATES_ADDR } from '../consts'; import { + JITOSOL_ADDR, + JITOSOL_DECIMALS, JITOSOL_LDOT_LP_PREDEPLOY_CODE, + LDOT_DECIMALS, NEW_DEX_CODE, TEST_ADDR_RELAYER, TEST_ADDR_USER, - TEST_KEY, } from './testConsts'; import { + alice, api, expectError, + provider, + relayer, 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'; @@ -73,8 +71,8 @@ describe('prepare', () => { { Erc20: JITOSOL_ADDR }, 1, 1, - parseUnits('10', 10).toBigInt(), - parseUnits('10', 9).toBigInt(), + parseUnits('0.1', 10).toBigInt(), + parseUnits('0.1', 9).toBigInt(), 0, ); await sudoSendAndWait(api, tx); @@ -87,6 +85,8 @@ describe('prepare', () => { JITOSOL_ADDR, 5, ); + + await api.disconnect(); }); }); @@ -482,3 +482,34 @@ describe('/routeDropAndBootstrap', () => { expect(bal3.userBal.sub(bal2.userBal).toBigInt()).to.eq(0n); }); }); + +describe('end bootstrap', () => { + it('end bootstrap', async () => { + const api = await ApiPromise.create({ + provider: new WsProvider('ws://localhost:8000'), + }); + + console.log('end provisioning ...'); + const endProvisionTx = api.tx.dex.endProvisioning( + { Token: 'LDOT' }, + { Erc20: JITOSOL_ADDR }, + ); + const tx = api.tx.sudo.sudoAs(alice.address, endProvisionTx); + await sudoSendAndWait(api, tx); + + // console.log('enable trading pair ...'); + // tx = api.tx.dex.enableTradingPair( + // { Token: 'LDOT' }, + // { Erc20: JITOSOL_ADDR }, + // ); + // await sudoSendAndWait(api, tx); + + const tradingPairStatus = await api.query.dex.tradingPairStatuses([ + { Token: 'LDOT' }, + { Erc20: JITOSOL_ADDR }, + ]); + expect(tradingPairStatus.toHuman()).to.eq('Enabled'); + + await api.disconnect(); + }); +}); diff --git a/src/__tests__/configs/acala.yml b/src/__tests__/configs/acala.yml index d60ace8..24d4ba7 100644 --- a/src/__tests__/configs/acala.yml +++ b/src/__tests__/configs/acala.yml @@ -1,6 +1,6 @@ endpoint: - - wss://acala-rpc.aca-api.network - wss://acala-rpc.dwellir.com + - wss://acala-rpc.aca-api.network mock-signature-host: true # block: ${env.ACALA_BLOCK_NUMBER} db: ./db.sqlite diff --git a/src/__tests__/euphrates.test.ts b/src/__tests__/euphrates.test.ts index f285369..74fd34c 100644 --- a/src/__tests__/euphrates.test.ts +++ b/src/__tests__/euphrates.test.ts @@ -1,32 +1,28 @@ import { ADDRESSES } from '@acala-network/asset-router/dist/consts'; -import { AcalaJsonRpcProvider } from '@acala-network/eth-providers'; import { DOT, LCDOT_13 as LCDOT, LDOT } from '@acala-network/contracts/utils/AcalaTokens'; import { ERC20__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts'; import { FeeRegistry__factory } from '@acala-network/asset-router/dist/typechain-types'; import { HOMA } from '@acala-network/contracts/utils/Predeploy'; import { IHoma__factory } from '@acala-network/contracts/typechain'; import { ONE_ACA, almostEq, toHuman } from '@acala-network/asset-router/dist/utils'; -import { Wallet } from 'ethers'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { formatEther, parseEther, parseUnits } from 'ethers/lib/utils'; -import { ETH_RPC, EUPHRATES_ADDR, EUPHRATES_POOLS } from '../consts'; +import { EUPHRATES_ADDR, EUPHRATES_POOLS } from '../consts'; import { RouteParamsEuphrates } from '../utils'; import { TEST_ADDR_RELAYER, TEST_ADDR_USER, - TEST_KEY, } from './testConsts'; import { api, expectError, + provider, + relayer, transferToken, + user, } from './testUtils'; -const provider = new AcalaJsonRpcProvider(ETH_RPC.LOCAL); -const relayer = new Wallet(TEST_KEY.RELAYER, provider); // 0xe3234f433914d4cfCF846491EC5a7831ab9f0bb3 -const user = new Wallet(TEST_KEY.USER, provider); // 0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6 - // [inToken, outToken] const WTDOT = '0xe1bd4306a178f86a9214c39abcd53d021bedb0f9'; const EUPHRAETS_POOL_INFO = { diff --git a/src/__tests__/swapAndLp.test.ts b/src/__tests__/swapAndLp.test.ts new file mode 100644 index 0000000..6fc7824 --- /dev/null +++ b/src/__tests__/swapAndLp.test.ts @@ -0,0 +1,259 @@ +import { ADDRESSES } from '@acala-network/asset-router/dist/consts'; +import { BigNumber } from 'ethers'; +import { ERC20__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts'; +import { FeeRegistry__factory } from '@acala-network/asset-router/dist/typechain-types'; +import { describe, expect, it } from 'vitest'; +import { formatUnits, parseEther, parseUnits } from 'ethers/lib/utils'; +import { toHuman } from '@acala-network/asset-router/dist/utils'; + +import { + JITOSOL_ADDR, + JITOSOL_DECIMALS, + TEST_ADDR_RELAYER, + TEST_ADDR_USER, +} from './testConsts'; +import { SwapAndLpParams } from '../utils'; +import { + api, + expectError, + provider, + relayer, + transferToken, + user, +} from './testUtils'; + +const recipient = TEST_ADDR_USER; +const poolId = '7'; +const swapAmount = parseUnits('0.12', JITOSOL_DECIMALS).toString(); +const minShareAmount = '0'; + +describe.concurrent('/shouldRouteSwapAndLp', () => { + const testShouldRouteSwapAndLp = async (params: SwapAndLpParams) => { + let res = await api.shouldRouteSwapAndLp(params); + expect(res).toMatchSnapshot(); + + // should be case insensitive + res = await api.shouldRouteSwapAndLp({ + ...params, + recipient: params.recipient.toLowerCase(), + }); + expect(res).toMatchSnapshot(); + }; + + it('when should route', async () => { + await testShouldRouteSwapAndLp({ + recipient, + poolId, + swapAmount, + minShareAmount, + }); + }); + + describe('when should not route', () => { + it('when missing params', async () => { + try { + await api.shouldRouteSwapAndLp({ + recipient, + poolId, + minShareAmount, + }); + expect.fail('did not throw an err'); + } catch (err) { + expectError(err, ['swapAmount is a required field'], 400); + } + + try { + await api.shouldRouteSwapAndLp({ + recipient, + swapAmount, + minShareAmount, + }); + expect.fail('did not throw an err'); + } catch (err) { + expectError(err, ['poolId is a required field'], 400); + } + + try { + await api.shouldRouteSwapAndLp({ + poolId, + swapAmount, + minShareAmount, + }); + expect.fail('did not throw an err'); + } catch (err) { + expectError(err, ['recipient is a required field'], 400); + } + }); + + it('when bad params', async () => { + const res = await api.shouldRouteSwapAndLp({ + recipient, + poolId: '999', + swapAmount, + minShareAmount, + }); + expect(res).toMatchInlineSnapshot(` + { + "data": { + "msg": "euphrates poolId 999 is not supported", + "shouldRoute": false, + }, + } + `); + }); + }); +}); + +describe('route and rescue', () => { + let routerAddr: string; + const jitosol = ERC20__factory.connect(JITOSOL_ADDR, relayer); + + const fetchTokenBalances = async () => { + if (!routerAddr) throw new Error('routerAddr not set'); + + const [ + userBal, + userTokenBal, + relayerTokenBal, + routerTokenBal, + ] = await Promise.all([ + provider.getBalance(TEST_ADDR_USER), + jitosol.balanceOf(TEST_ADDR_USER), + jitosol.balanceOf(TEST_ADDR_RELAYER), + jitosol.balanceOf(routerAddr), + ]); + + console.log({ + userBal: toHuman(userBal, 18), + userTokenBal: toHuman(userTokenBal, JITOSOL_DECIMALS), + relayerTokenBal: toHuman(relayerTokenBal, JITOSOL_DECIMALS), + routerTokenBal: toHuman(routerTokenBal, JITOSOL_DECIMALS), + }); + + return { + userBal, + userTokenBal, + relayerTokenBal, + routerTokenBal, + }; + }; + + it('/routeSwapAndLp', async () => { + const relayerBal = await relayer.getBalance(); + expect(relayerBal.gt(parseEther('10'))).to.be.true; + + const routeArgs = { + recipient: user.address, + poolId, + swapAmount, + minShareAmount, + }; + + const shouldRouteRes = await api.shouldRouteSwapAndLp(routeArgs); + ({ routerAddr } = shouldRouteRes.data); + console.log({ routerAddr }); + + // make sure user has enough token to transfer to router + const bal = await fetchTokenBalances(); + const trasnferAmount = BigNumber.from(swapAmount).mul(2); + if (bal.userTokenBal.lt(trasnferAmount)) { + if (bal.relayerTokenBal.lt(trasnferAmount)) { + 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(TEST_ADDR_USER, trasnferAmount)).wait(); + } + + console.log('transferring token to router ...'); + const transferAmountHuman = Number(formatUnits(trasnferAmount, JITOSOL_DECIMALS)); + await transferToken(routerAddr, user, JITOSOL_ADDR, transferAmountHuman); + + const bal0 = await fetchTokenBalances(); + + console.log('routing ...'); + const routeRes = await api.routeSwapAndLp({ + ...routeArgs, + token: JITOSOL_ADDR, + }); + const txHash = routeRes.data; + console.log(`route finished! txHash: ${txHash}`); + + const bal1 = await fetchTokenBalances(); + + // router should be destroyed + const routerCode = await provider.getCode(routerAddr); + expect(routerCode).to.eq('0x'); + expect(bal1.routerTokenBal.toNumber()).to.eq(0); + + // relayer should receive routing fee and swap fee + const routingFee = await FeeRegistry__factory.connect(ADDRESSES.ACALA.feeAddr, provider) + .getFee(JITOSOL_ADDR); + const swapFee = parseUnits('0.0035', 9); + expect(bal1.relayerTokenBal.sub(bal0.relayerTokenBal).toBigInt()) + .to.eq(routingFee.add(swapFee).toBigInt()); + + // user should receive 3 ACA drop + expect(bal1.userBal.sub(bal0.userBal).toBigInt()).to.eq(parseEther('3').toBigInt()); + }); + + it('/rescueSwapAndLp', async () => { + const relayerBal = await relayer.getBalance(); + expect(relayerBal.gt(parseEther('10'))).to.be.true; + + const routeArgs = { + recipient: user.address, + poolId, + swapAmount, + minShareAmount, + }; + + const shouldRouteRes = await api.shouldRouteSwapAndLp(routeArgs); + ({ routerAddr } = shouldRouteRes.data); + console.log({ routerAddr }); + + // make sure user has enough token to transfer to router + const bal = await fetchTokenBalances(); + const trasnferAmount = BigNumber.from(swapAmount).mul(2); + if (bal.userTokenBal.lt(trasnferAmount)) { + if (bal.relayerTokenBal.lt(trasnferAmount)) { + 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(TEST_ADDR_USER, trasnferAmount)).wait(); + } + + console.log('transferring token to router ...'); + const transferAmountHuman = Number(formatUnits(trasnferAmount, JITOSOL_DECIMALS)); + await transferToken(routerAddr, user, JITOSOL_ADDR, transferAmountHuman); + + const bal0 = await fetchTokenBalances(); + + console.log('rescuing ...'); + const rescueRes = await api.rescueSwapAndLp({ + ...routeArgs, + token: JITOSOL_ADDR, + }); + const txHash = rescueRes.data; + console.log(`rescue finished! txHash: ${txHash}`); + + const bal1 = await fetchTokenBalances(); + + // router should be destroyed + const routerCode = await provider.getCode(routerAddr); + expect(routerCode).to.eq('0x'); + expect(bal1.routerTokenBal.toNumber()).to.eq(0); + + // relayer should receive swap fee + const swapFee = parseUnits('0.0035', 9); + expect(bal1.relayerTokenBal.sub(bal0.relayerTokenBal).toBigInt()).to.eq(swapFee.toBigInt()); + + // user should receive 3 ACA drop and token back + expect(bal1.userBal.sub(bal0.userBal).toBigInt()).to.eq(parseEther('3').toBigInt()); + expect(bal1.userTokenBal.sub(bal0.userTokenBal).toBigInt()) + .to.eq(trasnferAmount.sub(swapFee).toBigInt()); + }); +}); + + diff --git a/src/__tests__/testConsts.ts b/src/__tests__/testConsts.ts index fed4983..76d4ec9 100644 --- a/src/__tests__/testConsts.ts +++ b/src/__tests__/testConsts.ts @@ -1,3 +1,4 @@ +import { ROUTER_TOKEN_INFO } from '@acala-network/asset-router/dist/consts'; import { Wallet } from 'ethers'; export const TEST_KEY = { @@ -11,6 +12,11 @@ export const PROD_ADDR = '0xBbBBa9Ebe50f9456E106e6ef2992179182889999'; export const NOT_SUPPORTED_ADDRESS = ''; +export const JITOSOL_DECIMALS = 9; +export const LDOT_DECIMALS = 10; +export const JITOSOL_ADDR = ROUTER_TOKEN_INFO.jitosol.acalaAddr; + + 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 d60739f..0937aa1 100644 --- a/src/__tests__/testUtils.ts +++ b/src/__tests__/testUtils.ts @@ -1,3 +1,4 @@ +import { AcalaJsonRpcProvider } from '@acala-network/eth-providers'; import { ApiPromise, Keyring, WsProvider } from '@polkadot/api'; import { ERC20__factory } from '@acala-network/asset-router/dist/typechain-types'; import { JsonRpcProvider } from '@ethersproject/providers'; @@ -7,10 +8,15 @@ import { expect } from 'vitest'; import { parseUnits } from 'ethers/lib/utils'; import axios from 'axios'; -import { apiUrl } from '../consts'; +import { ETH_RPC, apiUrl } from '../consts'; +import { TEST_KEY } from './testConsts'; const keyring = new Keyring({ type: 'sr25519' }); -const alice = keyring.addFromUri('//Alice'); +export const alice = keyring.addFromUri('//Alice'); + +export const provider = new AcalaJsonRpcProvider(ETH_RPC.LOCAL); +export const relayer = new Wallet(TEST_KEY.RELAYER, provider); // 0xe3234f433914d4cfCF846491EC5a7831ab9f0bb3 +export const user = new Wallet(TEST_KEY.USER, provider); // 0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6 export const sudoTransferToken = async ( fromAddr: string, @@ -153,6 +159,10 @@ export const api = { shouldRouteDropAndBootstrap: _axiosGet(apiUrl.shouldRouteDropAndBootstrap), routeDropAndBootstrap: _axiosPost(apiUrl.routeDropAndBootstrap), + shouldRouteSwapAndLp: _axiosGet(apiUrl.shouldRouteSwapAndLp), + routeSwapAndLp: _axiosPost(apiUrl.routeSwapAndLp), + rescueSwapAndLp: _axiosPost(apiUrl.rescueSwapAndLp), + routerInfo: _axiosGet(apiUrl.routerInfo), saveRouterInfo: _axiosPost(apiUrl.saveRouterInfo), diff --git a/src/api/index.ts b/src/api/index.ts index dd15763..24d858b 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -7,3 +7,4 @@ export * from './homa'; export * from './euphrates'; export * from './health'; export * from './swapAndRoute'; +export * from './swapAndLp'; diff --git a/src/api/swapAndLp.ts b/src/api/swapAndLp.ts new file mode 100644 index 0000000..794618e --- /dev/null +++ b/src/api/swapAndLp.ts @@ -0,0 +1,139 @@ +import { ACA, LDOT } from '@acala-network/contracts/utils/AcalaTokens'; +import { DEX } from '@acala-network/contracts/utils/Predeploy'; +import { DropAndSwapStakeFactory__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, EUPHRATES_POOLS, SWAP_SUPPLY_TOKENS } from '../consts'; +import { + Mainnet, + RouteError, + SwapAndLpParams, + _populateRelayTx, + _populateRouteTx, + getChainConfig, + getMainnetChainId, +} from '../utils'; + +const JITOSOL_ADDR = ROUTER_TOKEN_INFO.jitosol.acalaAddr; +const DEFAULT_SWAP_AND_LP_PARAMS = { + euphrates: EUPHRATES_ADDR, + dex: DEX, + dropToken: ACA, + dropFee: DROP_SWAP_AMOUNT_JITOSOL, + path: [JITOSOL_ADDR, LDOT], +}; + +const prepareSwapAndLp = async (chain: Mainnet) => { + const chainId = getMainnetChainId(chain); + const chainConfig = await getChainConfig(chainId); + const { feeAddr, dropAndSwapStakeFactoryAddr, wallet } = chainConfig; + + const factory = DropAndSwapStakeFactory__factory.connect(dropAndSwapStakeFactoryAddr!, wallet); + return { factory, feeAddr, relayerAddr: wallet.address }; +}; + +export const shouldRouteSwapAndLp = async (params: SwapAndLpParams) => { + try { + const { factory, feeAddr, relayerAddr } = await prepareSwapAndLp(Mainnet.Acala); + + if (!EUPHRATES_POOLS.includes(params.poolId)) { + throw new RouteError(`euphrates poolId ${params.poolId} is not supported`, params); + } + + const insts = { + ...DEFAULT_SWAP_AND_LP_PARAMS, + recipient: params.recipient, + feeReceiver: relayerAddr, + swapAmount: params.swapAmount, + poolId: params.poolId, + minShareAmount: params.minShareAmount ?? 0, + }; + + /* ---------- 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(DROP_AMOUNT_ACA)) { + console.log('granting allowance'); + await (await aca.approve(factory.address, constants.MaxUint256)).wait(); + } else { + console.log('allowance ok'); + } + /* ----------------------------------------------------------------------- */ + + const routerAddr = await factory.callStatic.deployDropAndSwapStakeRouter( + feeAddr, + insts, + DROP_AMOUNT_ACA, + ); + + return { + shouldRoute: true, + routerAddr, + }; + } catch (err) { + return { + shouldRoute: false, + msg: err.message, + }; + } +}; + +export const routeSwapAndLp = async (params: SwapAndLpParams) => { + if (params.token === undefined) { + throw new RouteError('[token] param is required for swap and lp', params); + } + + if (!SWAP_SUPPLY_TOKENS.includes(params.token as any)) { + throw new RouteError(`token ${params.token} is not supported for swapping`, params); + } + + const { factory, feeAddr, relayerAddr } = await prepareSwapAndLp(Mainnet.Acala); + const insts = { + ...DEFAULT_SWAP_AND_LP_PARAMS, + recipient: params.recipient, + feeReceiver: relayerAddr, + swapAmount: params.swapAmount, + poolId: params.poolId, + minShareAmount: params.minShareAmount ?? 0, + }; + + const tx = await factory.deployDropAndSwapStakeRouterAndRoute( + feeAddr, + insts, + params.token, + DROP_AMOUNT_ACA, + ); + const receipt = await tx.wait(); + + return receipt.transactionHash; +}; + +export const rescueSwapAndLp = async (params: SwapAndLpParams) => { + if (params.token === undefined) { + throw new RouteError('[token] param is required for swap and lp', params); + } + + const { factory, feeAddr, relayerAddr } = await prepareSwapAndLp(Mainnet.Acala); + const insts = { + ...DEFAULT_SWAP_AND_LP_PARAMS, + recipient: params.recipient, + feeReceiver: relayerAddr, + swapAmount: params.swapAmount, + poolId: params.poolId, + minShareAmount: params.minShareAmount ?? 0, + }; + + const isGasDrop = true; + const tx = await factory.deployDropAndSwapStakeRouterAndRescue( + feeAddr, + insts, + params.token, + DROP_AMOUNT_ACA, + isGasDrop, + ); + const receipt = await tx.wait(); + + return receipt.transactionHash; +}; diff --git a/src/consts.ts b/src/consts.ts index 7b1c3ff..df743fa 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -67,6 +67,10 @@ export const apiUrl = { shouldRouteDropAndBootstrap: getRelayerUrl('/shouldRouteDropAndBootstrap'), routeDropAndBootstrap: getRelayerUrl('/routeDropAndBootstrap'), + shouldRouteSwapAndLp: getRelayerUrl('/shouldRouteSwapAndLp'), + routeSwapAndLp: getRelayerUrl('/routeSwapAndLp'), + rescueSwapAndLp: getRelayerUrl('/rescueSwapAndLp'), + routerInfo: getRelayerUrl('/routerInfo'), saveRouterInfo: getRelayerUrl('/saveRouterInfo'), diff --git a/src/middlewares/router.ts b/src/middlewares/router.ts index 3863e74..c65f690 100644 --- a/src/middlewares/router.ts +++ b/src/middlewares/router.ts @@ -14,6 +14,7 @@ import { routeWormholeSchema, routeXcmSchema, shouldRelaySchema, + swapAndLpSchema, swapAndRouteSchema, } from '../utils'; import { @@ -23,14 +24,17 @@ import { relay, relayAndRoute, relayAndRouteBatch, + rescueSwapAndLp, routeEuphrates, routeHoma, routeHomaAuto, + routeSwapAndLp, routeWormhole, routeXcm, shouldRelay, shouldRouteEuphrates, shouldRouteHoma, + shouldRouteSwapAndLp, shouldRouteWormhole, shouldRouteXcm, shouldSwapAndRoute, @@ -73,6 +77,10 @@ const ROUTER_CONFIGS: { schema: swapAndRouteSchema, handler: shouldSwapAndRoute, }, + '/shouldRouteSwapAndLp': { + schema: swapAndLpSchema, + handler: shouldRouteSwapAndLp, + }, '/shouldRouteDropAndBootstrap': { schema: dropAndBootstrapSchema, handler: shouldRouteDropAndBoostrap, @@ -126,6 +134,14 @@ const ROUTER_CONFIGS: { schema: swapAndRouteSchema, handler: swapAndRoute, }, + '/routeSwapAndLp': { + schema: swapAndLpSchema, + handler: routeSwapAndLp, + }, + '/rescueSwapAndLp': { + schema: swapAndLpSchema, + handler: rescueSwapAndLp, + }, '/routeDropAndBootstrap': { schema: dropAndBootstrapSchema, handler: routeDropAndBoostrap, diff --git a/src/utils/configureEnv.ts b/src/utils/configureEnv.ts index d18aac1..9c8475e 100644 --- a/src/utils/configureEnv.ts +++ b/src/utils/configureEnv.ts @@ -21,6 +21,7 @@ export type ChainConfig = { accountHelperAddr?: string; euphratesFactoryAddr?: string; swapAndStakeFactoryAddr?: string; + dropAndSwapStakeFactoryAddr?: string; dropAndBootstrapStakeFactoryAddr?: string; isTestnet: boolean; }; diff --git a/src/utils/validate.ts b/src/utils/validate.ts index fec5efd..ddad4d1 100644 --- a/src/utils/validate.ts +++ b/src/utils/validate.ts @@ -53,6 +53,12 @@ export interface RouteParamsEuphrates { // does not support general swap yet export type SwapAndRouteParams = RouteParamsEuphrates; +// does not support general swap yet +export interface SwapAndLpParams extends RouteParamsEuphrates { + swapAmount: string; // how many token to swap before adding liquidity + minShareAmount?: string; // add liquidity min share amount +} + export interface DropAndBootstrapParams { recipient: string; // dest evm address gasDrop: boolean; // swap jitosol for aca gas drop? @@ -119,6 +125,14 @@ export const routeEuphratesSchema: ObjectSchema = object({ export const swapAndRouteSchema = routeEuphratesSchema; +export const swapAndLpSchema: ObjectSchema = object({ + poolId: string().required(), + recipient: string().required(), + swapAmount: string().required(), + token: string(), + minShareAmount: string(), +}); + export const dropAndBootstrapSchema: ObjectSchema = object({ recipient: string().required(), gasDrop: boolean().required(), diff --git a/vitest.config.ts b/vitest.config.ts index 31c4ef8..55015e3 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,6 +4,6 @@ export default defineConfig({ test: { root: './', environment: 'node', - testTimeout: 500000, + testTimeout: 1_200_000, // 20 mins }, });