Skip to content

Commit

Permalink
bubble up provider and api to config
Browse files Browse the repository at this point in the history
  • Loading branch information
shunjizhan committed Aug 7, 2023
1 parent 4240868 commit bb03e72
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 105 deletions.
52 changes: 20 additions & 32 deletions src/api/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Bridge__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts';
import { ChainId, hexToUint8Array } from '@certusone/wormhole-sdk';
import { Factory__factory } from '@acala-network/asset-router/dist/typechain-types';
import { JsonRpcProvider } from '@ethersproject/providers';
import { XcmInstructionsStruct } from '@acala-network/asset-router/dist/typechain-types/src/Factory';
import { hexToUint8Array } from '@certusone/wormhole-sdk';

import {
DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID,
Expand All @@ -11,12 +10,10 @@ import {
RelayAndRouteParams,
RouteParamsWormhole,
RouteParamsXcm,
_prepareRoute,
checkShouldRelayBeforeRouting,
getApi,
getChainConfig,
getEthExtrinsic,
getRouterChainTokenAddr,
getSigner,
logger,
prepareRouteWormhole,
prepareRouteXcm,
Expand All @@ -25,13 +22,13 @@ import {
} from '../utils';

export const routeXcm = async (routeParamsXcm: RouteParamsXcm): Promise<string> => {
const { chainConfig, signer } = await prepareRouteXcm(routeParamsXcm);
const { chainConfig } = await prepareRouteXcm(routeParamsXcm);

const xcmInstruction: XcmInstructionsStruct = {
dest: routeParamsXcm.dest,
weight: '0x00',
};
const factory = Factory__factory.connect(chainConfig.factoryAddr, signer);
const factory = Factory__factory.connect(chainConfig.factoryAddr, chainConfig.wallet);
const routerChainTokenAddr = await getRouterChainTokenAddr(routeParamsXcm.originAddr, chainConfig);

const tx = await factory.deployXcmRouterAndRoute(
Expand All @@ -46,22 +43,22 @@ export const routeXcm = async (routeParamsXcm: RouteParamsXcm): Promise<string>
};

export const _populateRelayTx = async (params: RelayAndRouteParams) => {
const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId] as ChainId;
const { chainConfig, signer } = await _prepareRoute(routerChainId);
await checkShouldRelayBeforeRouting(params, chainConfig, signer);
const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId];
const chainConfig = await getChainConfig(routerChainId);
await checkShouldRelayBeforeRouting(params, chainConfig);

const bridge = Bridge__factory.connect(chainConfig.tokenBridgeAddr, signer);
const bridge = Bridge__factory.connect(chainConfig.tokenBridgeAddr, chainConfig.wallet);
return await bridge.populateTransaction.completeTransfer(hexToUint8Array(params.signedVAA));
};

export const _populateRouteTx = async (routeParamsXcm: RelayAndRouteParams) => {
const { chainConfig, signer } = await prepareRouteXcm(routeParamsXcm);
const { chainConfig } = await prepareRouteXcm(routeParamsXcm);

const xcmInstruction: XcmInstructionsStruct = {
dest: routeParamsXcm.dest,
weight: '0x00',
};
const factory = Factory__factory.connect(chainConfig.factoryAddr, signer);
const factory = Factory__factory.connect(chainConfig.factoryAddr, chainConfig.wallet);
const routerChainTokenAddr = await getRouterChainTokenAddr(routeParamsXcm.originAddr, chainConfig);

return await factory.populateTransaction.deployXcmRouterAndRoute(
Expand All @@ -77,35 +74,26 @@ export const relayAndRouteBatch = async (params: RelayAndRouteParams): Promise<s
_populateRouteTx(params),
]);

const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId] as ChainId;
const { chainConfig } = await _prepareRoute(routerChainId);

const [
{ addr, api },
signer,
] = await Promise.all([
getApi(chainConfig),
getSigner(chainConfig),
]);
const provider = signer.provider! as JsonRpcProvider;
const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId];
const { api, provider, relayerSubstrateAddr } = await getChainConfig(routerChainId);

const [relayExtrinsic, routeExtrinsic] = await Promise.all([
getEthExtrinsic(api, provider, relayTx),
getEthExtrinsic(api, provider, routeTx),
]);

const batchTx = api.tx.utility.batchAll([relayExtrinsic, routeExtrinsic]);
await batchTx.signAsync(addr);
await batchTx.signAsync(relayerSubstrateAddr);

const txHash = await sendExtrinsic(api, provider, batchTx);

return txHash;
};

export const relayAndRoute = async (params: RelayAndRouteParams): Promise<[string, string]> => {
const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId] as ChainId;
const { chainConfig, signer } = await _prepareRoute(routerChainId);
await checkShouldRelayBeforeRouting(params, chainConfig, signer);
const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId];
const chainConfig = await getChainConfig(routerChainId);
await checkShouldRelayBeforeRouting(params, chainConfig);

const wormholeReceipt = await relayEVM(chainConfig, params.signedVAA);
logger.debug({ txHash: wormholeReceipt.transactionHash }, 'relay finished');
Expand All @@ -117,16 +105,16 @@ export const relayAndRoute = async (params: RelayAndRouteParams): Promise<[strin
export const routeWormhole = async (routeParamsWormhole: RouteParamsWormhole): Promise<string> => {
const {
chainConfig,
signer,
routerChainTokenAddr,
wormholeInstructions,
} = await prepareRouteWormhole(routeParamsWormhole);
const { feeAddr, tokenBridgeAddr, factoryAddr, wallet } = chainConfig;

const factory = Factory__factory.connect(chainConfig.factoryAddr, signer);
const factory = Factory__factory.connect(factoryAddr, wallet);
const tx = await factory.deployWormholeRouterAndRoute(
chainConfig.feeAddr,
feeAddr,
wormholeInstructions,
chainConfig.tokenBridgeAddr,
tokenBridgeAddr,
routerChainTokenAddr,
);
const receipt = await tx.wait();
Expand Down
106 changes: 57 additions & 49 deletions src/utils/configureEnv.ts
Original file line number Diff line number Diff line change
@@ -1,95 +1,103 @@
import { ADDRESSES } from '@acala-network/asset-router/dist/consts';
import { CHAIN_ID_ACALA, CHAIN_ID_KARURA, ChainId } from '@certusone/wormhole-sdk';
import { AcalaJsonRpcProvider } from '@acala-network/eth-providers';
import { ApiPromise } from '@polkadot/api';
import { CHAIN_ID_ACALA, CHAIN_ID_KARURA } from '@certusone/wormhole-sdk';
import { Wallet } from 'ethers';
import dotenv from 'dotenv';

import { ROUTER_CHAIN_ID } from './utils';
import { ROUTER_CHAIN_ID, getApi } from './utils';

dotenv.config({ path: '.env' });

export type RelayerEnvironment = {
supportedChains: ChainConfig[];
};

export type ChainConfig = {
chainId: ROUTER_CHAIN_ID;
ethRpc: string;
nodeUrl: string;
walletPrivateKey: string;
provider: AcalaJsonRpcProvider;
wallet: Wallet;
api: ApiPromise;
relayerSubstrateAddr: string;
tokenBridgeAddr: string;
feeAddr: string;
factoryAddr: string;
isTestnet: boolean;
};

const isTestnet = Boolean(Number(process.env.TESTNET_MODE ?? 1));

export function prepareEnvironment(): RelayerEnvironment {
const supportedChains: ChainConfig[] = [];
supportedChains.push(configKarura());
supportedChains.push(configAcala());

return { supportedChains };
}

function configKarura(): ChainConfig {
const requiredEnvVars = [
'KARURA_ETH_RPC',
'KARURA_PRIVATE_KEY',
'KARURA_NODE_URL',
];
export type RelayerEnvironment = ChainConfig[];

for (const envVar of requiredEnvVars) {
const ensureEnvVars = (envVars: string[]) => {
for (const envVar of envVars) {
if (!process.env[envVar]) {
console.error(`Missing environment variable ${envVar}`);
process.exit(1);
}
}
};

const isTestnet = Boolean(Number(process.env.TESTNET_MODE ?? 1));

export const prepareEnvironment = (): Promise<RelayerEnvironment> => Promise.all([
configKarura(),
configAcala(),
]);

const configKarura = async (): Promise<ChainConfig> => {
ensureEnvVars([
'KARURA_ETH_RPC',
'KARURA_PRIVATE_KEY',
'KARURA_NODE_URL',
]);

const addresses = isTestnet
? ADDRESSES.KARURA_TESTNET
: ADDRESSES.KARURA;

const provider = new AcalaJsonRpcProvider(process.env.KARURA_ETH_RPC!);
const wallet = new Wallet(process.env.KARURA_PRIVATE_KEY!, provider);
const { substrateAddr, api } = await getApi(
process.env.KARURA_PRIVATE_KEY!,
process.env.KARURA_NODE_URL!,
);

return {
chainId: CHAIN_ID_KARURA,
ethRpc: process.env.KARURA_ETH_RPC!,
nodeUrl: process.env.KARURA_NODE_URL!,
walletPrivateKey: process.env.KARURA_PRIVATE_KEY!,
provider,
wallet,
api,
relayerSubstrateAddr: substrateAddr,
isTestnet,
...addresses,
};
}
};

function configAcala(): ChainConfig {
const requiredEnvVars = [
const configAcala = async (): Promise<ChainConfig> => {
ensureEnvVars([
'ACALA_ETH_RPC',
'ACALA_PRIVATE_KEY',
'ACALA_NODE_URL',
];

for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
console.error(`Missing environment variable ${envVar}`);
process.exit(1);
}
}
]);

const addresses = isTestnet
? ADDRESSES.ACALA_TESTNET
: ADDRESSES.ACALA;

const provider = new AcalaJsonRpcProvider(process.env.ACALA_ETH_RPC!);
const wallet = new Wallet(process.env.ACALA_PRIVATE_KEY!, provider);
const { substrateAddr, api } = await getApi(
process.env.ACALA_PRIVATE_KEY!,
process.env.ACALA_NODE_URL!,
);

return {
chainId: CHAIN_ID_ACALA,
ethRpc: process.env.ACALA_ETH_RPC!,
nodeUrl: process.env.ACALA_NODE_URL!,
walletPrivateKey: process.env.ACALA_PRIVATE_KEY!,
provider,
wallet,
api,
relayerSubstrateAddr: substrateAddr,
isTestnet,
...addresses,
};
}

const env: RelayerEnvironment = prepareEnvironment();
};

// TODO: maybe export signer and api directly from here?
export const getChainConfig = (chainId: ChainId): ChainConfig | undefined => (
env.supportedChains.find((x) => x.chainId === chainId)
const env = prepareEnvironment();
export const getChainConfig = async (chainId: ROUTER_CHAIN_ID): Promise<ChainConfig> => (
(await env).find((x) => x.chainId === chainId)!
);
15 changes: 6 additions & 9 deletions src/utils/relay.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigNumber, ContractReceipt, Signer } from 'ethers';
import { BigNumber, ContractReceipt } from 'ethers';
import { Bridge__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts';
import { ERC20__factory, FeeRegistry__factory } from '@acala-network/asset-router/dist/typechain-types';
import {
Expand All @@ -13,7 +13,6 @@ import { RELAYER_SUPPORTED_ADDRESSES_AND_THRESHOLDS } from '../consts';
import { RelayAndRouteParams } from './validate';
import { RelayError } from '../middlewares/error';
import { VaaInfo, parseVaaPayload } from './wormhole';
import { getSigner } from './utils';
import { logger } from './logger';

interface ShouldRelayResult {
Expand Down Expand Up @@ -75,10 +74,10 @@ const VAA_MAX_DECIMALS = 8;
export const checkShouldRelayBeforeRouting = async (
params: RelayAndRouteParams,
chainConfig: ChainConfig,
signer: Signer,
) => {
const tokenBridge = Bridge__factory.connect(chainConfig.tokenBridgeAddr, signer);
const feeRegistry = FeeRegistry__factory.connect(chainConfig.feeAddr, signer);
const { tokenBridgeAddr, feeAddr, wallet } = chainConfig;
const tokenBridge = Bridge__factory.connect(tokenBridgeAddr, wallet);
const feeRegistry = FeeRegistry__factory.connect(feeAddr, wallet);

const vaaInfo = await parseVaaPayload(hexToUint8Array(params.signedVAA));
const {
Expand All @@ -97,7 +96,7 @@ export const checkShouldRelayBeforeRouting = async (
throw new RelayError('unsupported token', { ...vaaInfo, amount: BigNumber.from(vaaAmount) });
}

const erc20 = ERC20__factory.connect(wrappedAddr, signer);
const erc20 = ERC20__factory.connect(wrappedAddr, wallet);
const originDecimals = await erc20.decimals();
const realAmount = originDecimals <= VAA_MAX_DECIMALS
? vaaAmount
Expand All @@ -112,11 +111,9 @@ export const relayEVM = async (
chainConfig: ChainConfig,
signedVAA: string,
): Promise<ContractReceipt> => {
const signer = await getSigner(chainConfig);

const receipt = await redeemOnEth(
chainConfig.tokenBridgeAddr,
signer,
chainConfig.wallet,
hexToUint8Array(signedVAA),
);

Expand Down
25 changes: 10 additions & 15 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
import { AcalaJsonRpcProvider } from '@acala-network/eth-providers';
import { ApiPromise, Keyring, WsProvider } from '@polkadot/api';
import { BigNumber, Contract, PopulatedTransaction, Signer, Wallet, ethers } from 'ethers';
import { BigNumber, Contract, PopulatedTransaction, Wallet } from 'ethers';
import { CHAIN_ID_ACALA, CHAIN_ID_AVAX, CHAIN_ID_KARURA, CONTRACTS, hexToUint8Array } from '@certusone/wormhole-sdk';
import { DispatchError } from '@polkadot/types/interfaces';
import { ISubmittableResult } from '@polkadot/types/types';
import { JsonRpcProvider } from '@ethersproject/providers';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { SubstrateSigner } from '@acala-network/bodhi';
import { options } from '@acala-network/api';
import { parseUnits } from 'ethers/lib/utils';
// import { waitReady } from '@polkadot/wasm-crypto';
import { cryptoWaitReady } from '@polkadot/util-crypto';

import { ChainConfig } from './configureEnv';
import { RelayerError } from '../middlewares';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { bridgeToken, getSignedVAAFromSequence } from './wormhole';

export type ROUTER_CHAIN_ID = typeof CHAIN_ID_KARURA | typeof CHAIN_ID_ACALA;

export const getSigner = async (config: ChainConfig): Promise<Signer> => {
const provider = new AcalaJsonRpcProvider(config.ethRpc);
return new ethers.Wallet(config.walletPrivateKey, provider);
};

// TODO: reuse api
export const getApi = async (config: ChainConfig) => {
export const getApi = async (privateKey: string, nodeUrl: string) => {
await cryptoWaitReady();
const keyring = new Keyring({ type: 'sr25519' });
const keyPair = keyring.addFromSeed(hexToUint8Array(config.walletPrivateKey));
const addr = keyPair.address;
const keyPair = keyring.addFromSeed(hexToUint8Array(privateKey));
const substrateAddr = keyPair.address;

const api = await ApiPromise.create(options({
provider: new WsProvider(config.nodeUrl),
provider: new WsProvider(nodeUrl),
}));

api.setSigner(new SubstrateSigner(api.registry, keyPair));

return { addr, api };
return { substrateAddr, api };
};

export const parseAmount = async (tokenAddr: string, amount: string, provider: any): Promise<BigNumber> => {
Expand Down

0 comments on commit bb03e72

Please sign in to comment.