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 35ee997..8d40c82 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@acala-network/api": "~6.0.4", - "@acala-network/asset-router": "~1.0.19-8", + "@acala-network/asset-router": "~1.0.19-9", "@acala-network/bodhi": "~2.7.13", "@acala-network/contracts": "^4.5.0", "@acala-network/eth-providers": "~2.7.14", diff --git a/src/__tests__/configs/acala.yml b/src/__tests__/configs/acala.yml index 9276a7c..0aa007a 100644 --- a/src/__tests__/configs/acala.yml +++ b/src/__tests__/configs/acala.yml @@ -1,11 +1,11 @@ endpoint: # - wss://acala-rpc.aca-api.network # - wss://acala-rpc.dwellir.com - - wss://crosschain-dev.polkawallet.io:9915 + - wss://crosschain-dev.polkawallet.io/chopsticksAcala mock-signature-host: true # block: ${env.ACALA_BLOCK_NUMBER} # block: 6548800 -block: 6987959 +# block: 6987959 db: ./db.sqlite runtime-log-level: 5 diff --git a/src/api/dropAndBootstrap.ts b/src/api/dropAndBootstrap.ts new file mode 100644 index 0000000..41eb6e2 --- /dev/null +++ b/src/api/dropAndBootstrap.ts @@ -0,0 +1,129 @@ +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, + otherContributionToken: LDOT, + 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(); + + console.log({ gasDrop: params.gasDrop }); + const dropFee = params.gasDrop ? DROP_SWAP_AMOUNT_JITOSOL : 0; + const dropAmountAca = params.gasDrop ? DROP_AMOUNT_ACA : 0; + + const insts = { + ...DEFAULT_DROP_AND_BOOTSTRAP_PARAMS, + recipient: params.recipient, + feeReceiver: relayerAddr, + dropFee, + }; + + /* ---------- 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 insts = { + ...DEFAULT_DROP_AND_BOOTSTRAP_PARAMS, + recipient: params.recipient, + feeReceiver: relayerAddr, + dropFee, + }; + + const tx = await factory.deployDropAndBootstrapStakeRouterAndRoute( + feeAddr, + insts, + JITOSOL_ADDR, + dropAmountAca, + ); + const receipt = await tx.wait(); + + return receipt.transactionHash; +}; + +export const rescueDropAndBoostrap = 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 insts = { + ...DEFAULT_DROP_AND_BOOTSTRAP_PARAMS, + recipient: params.recipient, + feeReceiver: relayerAddr, + dropFee, + }; + + const tx = await factory.deployDropAndBootstrapStakeRouterAndRescue( + feeAddr, + insts, + JITOSOL_ADDR, + dropAmountAca, + params.gasDrop, + ); + const receipt = await tx.wait(); + + return receipt.transactionHash; +}; diff --git a/src/api/swapAndLp.ts b/src/api/swapAndLp.ts index debf78c..794618e 100644 --- a/src/api/swapAndLp.ts +++ b/src/api/swapAndLp.ts @@ -52,9 +52,8 @@ export const shouldRouteSwapAndLp = async (params: SwapAndLpParams) => { }; /* ---------- TODO: remove this check later after approved max ---------- */ - const signerAddr = await factory.signer.getAddress(); const aca = ERC20__factory.connect(ACA, factory.signer); - const allowance = await aca.allowance(signerAddr, factory.address); + 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(); diff --git a/src/consts.ts b/src/consts.ts index 8115e36..c350fca 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -3,8 +3,8 @@ import { CHAIN_ID_KARURA, } from '@certusone/wormhole-sdk'; import { ROUTER_TOKEN_INFO } from '@acala-network/asset-router/dist/consts'; -import dotenv from 'dotenv'; import { parseUnits } from 'ethers/lib/utils'; +import dotenv from 'dotenv'; dotenv.config({ path: '.env' }); diff --git a/src/middlewares/router.ts b/src/middlewares/router.ts index b0b7c5a..101226e 100644 --- a/src/middlewares/router.ts +++ b/src/middlewares/router.ts @@ -3,6 +3,7 @@ import { Schema } from 'yup'; import { NoRouteError, + dropAndBootstrapSchema, logger, parseIp, relayAndRouteSchema, @@ -39,6 +40,7 @@ import { shouldSwapAndRoute, swapAndRoute, } from '../api'; +import { rescueDropAndBoostrap, routeDropAndBoostrap, shouldRouteDropAndBoostrap } from '../api/dropAndBootstrap'; interface RouterConfig { schema?: Schema; @@ -79,6 +81,10 @@ const ROUTER_CONFIGS: { schema: swapAndLpSchema, handler: shouldRouteSwapAndLp, }, + '/shouldRouteDropAndBootstrap': { + schema: dropAndBootstrapSchema, + handler: shouldRouteDropAndBoostrap, + }, '/health': { handler: healthCheck, }, @@ -136,6 +142,14 @@ const ROUTER_CONFIGS: { schema: swapAndLpSchema, handler: rescueSwapAndLp, }, + '/routeDropAndBootstrap': { + schema: dropAndBootstrapSchema, + handler: routeDropAndBoostrap, + }, + '/rescueDropAndBootstrap': { + schema: dropAndBootstrapSchema, + handler: rescueDropAndBoostrap, + }, }, }; @@ -154,8 +168,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 44f1bac..9c8475e 100644 --- a/src/utils/configureEnv.ts +++ b/src/utils/configureEnv.ts @@ -22,6 +22,7 @@ export type ChainConfig = { euphratesFactoryAddr?: string; swapAndStakeFactoryAddr?: string; dropAndSwapStakeFactoryAddr?: string; + dropAndBootstrapStakeFactoryAddr?: string; isTestnet: boolean; }; diff --git a/src/utils/validate.ts b/src/utils/validate.ts index d6f1821..c630692 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', @@ -59,6 +59,11 @@ export interface SwapAndLpParams extends RouteParamsEuphrates { minShareAmount?: string; // add liquidity min share amount } +export interface DropAndBootstrapParams { + recipient: string; // dest evm address + gasDrop: boolean; // swap jitosol for aca gas drop? +} + export interface RelayAndRouteParams extends RouteParamsXcm { signedVAA: string; } @@ -127,6 +132,11 @@ export const swapAndLpSchema: ObjectSchema = object({ minShareAmount: string(), }); +export const dropAndBootstrapSchema: ObjectSchema = object({ + recipient: string().required(), + gasDrop: boolean().required(), +}); + export const routeStatusSchema: ObjectSchema = object({ id: string(), destAddr: string(), diff --git a/yarn.lock b/yarn.lock index 0054291..d7356e3 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.19-8": - version "1.0.19-8" - resolved "https://registry.yarnpkg.com/@acala-network/asset-router/-/asset-router-1.0.19-8.tgz#12cd5d7c74b4f79af8ef9fdc04ee7deb3038867f" - integrity sha512-ND2lFvO8fLTy1793RYg9ZLS2JbFuUD/h/nXEA9dr2tiE+40L5vqc4sY2YDP5rM1KOyiPrw0SxT/WqHcmIGUtfg== +"@acala-network/asset-router@~1.0.19-9": + version "1.0.19-9" + resolved "https://registry.yarnpkg.com/@acala-network/asset-router/-/asset-router-1.0.19-9.tgz#f5ab351fcdf71f59a803e3a3d92cb3cb2d6bc752" + integrity sha512-dON0JO01TX93NVTTNGiElrs8l5WLwWFWU5qr7zPTYW39OoHGfgxYS1fa35Fcyz+ayUL6idBXJjq2t90pIUL1cw== dependencies: "@acala-network/contracts" "^4.5.0" "@acala-network/eth-providers" "^2.7.13"