diff --git a/examples/mrl-simple/index.ts b/examples/mrl-simple/index.ts index 8f32c373..e2881d52 100644 --- a/examples/mrl-simple/index.ts +++ b/examples/mrl-simple/index.ts @@ -16,6 +16,7 @@ import { cryptoWaitReady } from '@polkadot/util-crypto'; import { http, type Address, createWalletClient } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { moonbaseAlpha as moonbaseAlphaViem } from 'viem/chains'; +import type { EvmSigner } from '../../packages/sdk/build'; // disable unnecessary warning logs console.warn = () => null; @@ -54,12 +55,46 @@ main() .finally(() => process.exit()); async function main() { - const isAutomatic = true; - // await fromFantomToPeaq(ftm, 0.011, isAutomatic); - // await fromFantomToMoonbase(ftm, 0.01, isAutomatic); + const isAutomatic = false; + + // await fromFantomToPeaq(agng, 10, isAutomatic); + // await fromFantomToMoonbase(dev, 1.23, isAutomatic); // await fromMoonbaseToFantom(ftmwh, 0.01, isAutomatic); - await fromPeaqToFantom(agng, 20, isAutomatic); + // await fromPeaqToFantom(ftmwh, 0.0121, isAutomatic); // await fromPeaqEvmToFantom(ftmwh, 1.5, isAutomatic); + + await redeemInMoonbaseAlpha(); + // await redeemInFantomTestnet(); +} + +async function redeemInMoonbaseAlpha() { + const txId = + '0x59e70ad73c57bce44cbb3e3308fc6a31d29ff0dcbb2957055b05025969545bed'; + const walletClient = createWalletClient({ + account, + chain: moonbaseAlphaViem, + transport: http(), + }); + + const data = await Mrl().getRedeemData({ txId, chain: moonbaseAlpha }); + console.log('data', data); + + await data.redeem(walletClient as EvmSigner); +} + +async function redeemInFantomTestnet() { + const txId = + '0xa0032ff270885f7278a42d4d974fceab9e4feb039263db35b09beafe57bd6683'; + const walletClient = createWalletClient({ + account, + chain: fantomTestnet.getViemChain(), + transport: http(), + }); + + const data = await Mrl().getRedeemData({ txId, chain: fantomTestnet }); + console.log('data', data); + + await data.redeem(walletClient as EvmSigner); } async function fromFantomToPeaq( @@ -86,7 +121,7 @@ async function fromFantomToPeaq( await data.transfer(amount, isAutomatic, { polkadotSigner: pair, - evmSigner: walletClient, + evmSigner: walletClient as EvmSigner, }); } @@ -112,10 +147,11 @@ async function fromFantomToMoonbase( console.log(data); - await data.transfer(amount, isAutomatic, { + const hash = await data.transfer(amount, isAutomatic, { polkadotSigner: pair, - evmSigner: walletClient, + evmSigner: walletClient as EvmSigner, }); + console.log('hash', hash); } async function fromMoonbaseToFantom( @@ -141,7 +177,7 @@ async function fromMoonbaseToFantom( await data.transfer(amount, isAutomatic, { polkadotSigner: pair, - evmSigner: walletClient, + evmSigner: walletClient as EvmSigner, }); } @@ -188,5 +224,7 @@ async function fromPeaqEvmToFantom( console.log(data); - await data.transfer(amount, isAutomatic, { evmSigner: walletClient }); + await data.transfer(amount, isAutomatic, { + evmSigner: walletClient as EvmSigner, + }); } diff --git a/packages/builder/src/mrl/MrlBuilder.interfaces.ts b/packages/builder/src/mrl/MrlBuilder.interfaces.ts index 0e9456b9..a3c83f3f 100644 --- a/packages/builder/src/mrl/MrlBuilder.interfaces.ts +++ b/packages/builder/src/mrl/MrlBuilder.interfaces.ts @@ -15,6 +15,11 @@ export type MrlConfigBuilder = ConfigBuilder< MrlBuilderParams >; +export type MrlRedeemConfigBuilder = ConfigBuilder< + ContractConfig, + MrlRedeemBuilderParams +>; + export interface MrlBuilderParams extends BuilderParams { isAutomatic: boolean; moonApi: ApiPromise; @@ -24,6 +29,10 @@ export interface MrlBuilderParams extends BuilderParams { transact?: Transact; } +export interface MrlRedeemBuilderParams { + bytes?: Uint8Array; +} + export interface Transact { call: HexString; txWeight: { diff --git a/packages/builder/src/mrl/providers/wormhole/contract/Gmp/Gmp.ts b/packages/builder/src/mrl/providers/wormhole/contract/Gmp/Gmp.ts new file mode 100644 index 00000000..9d2bf23e --- /dev/null +++ b/packages/builder/src/mrl/providers/wormhole/contract/Gmp/Gmp.ts @@ -0,0 +1,27 @@ +import { u8aToHex } from '@polkadot/util'; +import { ContractConfig } from '../../../../../contract'; +import type { MrlRedeemConfigBuilder } from '../../../../MrlBuilder.interfaces'; +import { GMP_ABI } from './GmpAbi'; + +const module = 'GMP'; + +export const GMP_CONTRACT_ADDRESS = + '0x0000000000000000000000000000000000000816'; + +export function Gmp() { + return { + wormholeTransferERC20: (): MrlRedeemConfigBuilder => ({ + build: ({ bytes }) => { + const hex = u8aToHex(bytes); + + return new ContractConfig({ + address: GMP_CONTRACT_ADDRESS, + abi: GMP_ABI, + args: [hex], + func: 'wormholeTransferERC20', + module, + }); + }, + }), + }; +} diff --git a/packages/builder/src/mrl/providers/wormhole/contract/Gmp/GmpAbi.ts b/packages/builder/src/mrl/providers/wormhole/contract/Gmp/GmpAbi.ts new file mode 100644 index 00000000..d6026468 --- /dev/null +++ b/packages/builder/src/mrl/providers/wormhole/contract/Gmp/GmpAbi.ts @@ -0,0 +1,15 @@ +export const GMP_ABI = [ + { + inputs: [ + { + internalType: 'bytes', + name: 'vaa', + type: 'bytes', + }, + ], + name: 'wormholeTransferERC20', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/packages/builder/src/mrl/providers/wormhole/contract/Gmp/index.ts b/packages/builder/src/mrl/providers/wormhole/contract/Gmp/index.ts new file mode 100644 index 00000000..3e0209db --- /dev/null +++ b/packages/builder/src/mrl/providers/wormhole/contract/Gmp/index.ts @@ -0,0 +1 @@ +export * from './Gmp'; diff --git a/packages/builder/src/mrl/providers/wormhole/contract/index.ts b/packages/builder/src/mrl/providers/wormhole/contract/index.ts index 478deb08..8209ddeb 100644 --- a/packages/builder/src/mrl/providers/wormhole/contract/index.ts +++ b/packages/builder/src/mrl/providers/wormhole/contract/index.ts @@ -1,7 +1,8 @@ import { Batch } from './Batch'; +import { Gmp } from './Gmp'; import { TokenBridge } from './TokenBridge'; import { TokenBridgeRelayer } from './TokenBridgeRelayer'; export function contract() { - return { Batch, TokenBridge, TokenBridgeRelayer }; + return { Batch, Gmp, TokenBridge, TokenBridgeRelayer }; } diff --git a/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts b/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts index 24960128..5dd16aa6 100644 --- a/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts +++ b/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts @@ -7,12 +7,10 @@ import type { MrlBuilderParams, MrlConfigBuilder, } from '../../../MrlBuilder.interfaces'; +import { GMP_CONTRACT_ADDRESS } from '../contract/Gmp'; import { WormholeConfig } from './WormholeConfig'; import { wormholeFactory } from './wormholeFactory'; -export const GMP_CONTRACT_ADDRESS = - '0x0000000000000000000000000000000000000816'; - export function wormhole() { return { tokenTransfer: (): MrlConfigBuilder => ({ diff --git a/packages/config/src/mrl-configs/fantomTestnet.ts b/packages/config/src/mrl-configs/fantomTestnet.ts index 1f9f8739..c8c22ee1 100644 --- a/packages/config/src/mrl-configs/fantomTestnet.ts +++ b/packages/config/src/mrl-configs/fantomTestnet.ts @@ -1,5 +1,5 @@ import { BalanceBuilder, MrlBuilder } from '@moonbeam-network/xcm-builder'; -import { dev, ftm, ftmwh } from '../assets'; +import { agng, dev, ftm, ftmwh } from '../assets'; import { fantomTestnet, moonbaseAlpha, @@ -42,6 +42,37 @@ export const fantomTestnetRoutes = new MrlChainRoutes({ }, }, }, + { + source: { + asset: agng, + balance: BalanceBuilder().evm().erc20(), + destinationFee: { + asset: agng, + balance: BalanceBuilder().evm().erc20(), + }, + }, + destination: { + asset: agng, + chain: peaqAlphanet, + balance: BalanceBuilder().substrate().system().account(), + fee: { + asset: agng, + amount: 1, + }, + }, + mrl: { + isAutomaticPossible: false, + transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), + moonChain: { + asset: agng, + fee: { + asset: dev, + amount: 0.1, + balance: BalanceBuilder().substrate().system().account(), + }, + }, + }, + }, { source: { asset: ftm, diff --git a/packages/mrl/src/getTransferData/getRedeemData.ts b/packages/mrl/src/getTransferData/getRedeemData.ts new file mode 100644 index 00000000..04e5fd83 --- /dev/null +++ b/packages/mrl/src/getTransferData/getRedeemData.ts @@ -0,0 +1,49 @@ +import { type ContractConfig, MrlBuilder } from '@moonbeam-network/xcm-builder'; +import { EvmService, type EvmSigner } from '@moonbeam-network/xcm-sdk'; +import type { EvmChain, EvmParachain } from '@moonbeam-network/xcm-types'; +import { WormholeService } from '../services/wormhole'; + +export interface WormholeRedeemParams { + txId: string; + chain: EvmChain | EvmParachain; +} + +export async function getRedeemData({ txId, chain }: WormholeRedeemParams) { + // TODO this is just for wormhole + const wh = WormholeService.create(chain); + + const vaa = await wh.getVaa(txId); + const tokenTransfer = await wh.getTokenTransfer(vaa, txId); + + const isXcm = vaa.payloadName === 'TransferWithPayload'; + + return { + vaa, + tokenTransfer, + async redeem(signer: EvmSigner) { + const isComplete = await wh.isComplete(vaa, tokenTransfer); + + if (isComplete) { + throw new Error('This transaction is already finalized in Wormhole'); + } + + if (isXcm) { + const bytes = await wh.getVaaBytes(vaa); + + const contract = MrlBuilder() + .wormhole() + .contract() + .Gmp() + .wormholeTransferERC20() + .build({ bytes }) as ContractConfig; + + const evm = EvmService.create(chain); + const hash = await evm.transfer(signer, contract); + + return hash; + } + + return await wh.completeTransfer(tokenTransfer, signer); + }, + }; +} diff --git a/packages/mrl/src/mrl.interfaces.ts b/packages/mrl/src/mrl.interfaces.ts index 9abe5060..a552d5ed 100644 --- a/packages/mrl/src/mrl.interfaces.ts +++ b/packages/mrl/src/mrl.interfaces.ts @@ -1,7 +1,11 @@ -import type { SourceChainTransferData } from '@moonbeam-network/xcm-sdk'; +import type { + EvmSigner, + SourceChainTransferData, +} from '@moonbeam-network/xcm-sdk'; import type { AnyChain, AssetAmount } from '@moonbeam-network/xcm-types'; import type { Signer } from '@polkadot/api/types'; import type { IKeyringPair } from '@polkadot/types/types'; +import type { TokenTransfer } from '@wormhole-foundation/sdk-connect'; import type { WalletClient } from 'viem'; export interface Signers { @@ -41,3 +45,10 @@ export interface ChainTransferData { fee: AssetAmount; min: AssetAmount; } + +// TODO this is just for Wormhole +export type RedeemData = { + vaa: TokenTransfer.VAA; + tokenTransfer: TokenTransfer; + transfer(signer: EvmSigner): Promise; +}; diff --git a/packages/mrl/src/mrl.ts b/packages/mrl/src/mrl.ts index 1c511e30..33c5c26e 100644 --- a/packages/mrl/src/mrl.ts +++ b/packages/mrl/src/mrl.ts @@ -4,6 +4,10 @@ import type { AnyChain, Ecosystem, } from '@moonbeam-network/xcm-types'; +import { + type WormholeRedeemParams, + getRedeemData, +} from './getTransferData/getRedeemData'; import { getTransferData } from './getTransferData/getTransferData'; const DEFAULT_SERVICE = new ConfigService({ routes: mrlRoutesMap }); @@ -56,5 +60,8 @@ export function Mrl(options?: MrlOptions) { }, }; }, + getRedeemData({ txId, chain }: WormholeRedeemParams) { + return getRedeemData({ txId, chain }); + }, }; } diff --git a/packages/mrl/src/services/wormhole/WormholeService.ts b/packages/mrl/src/services/wormhole/WormholeService.ts index 1d852096..3eaac618 100644 --- a/packages/mrl/src/services/wormhole/WormholeService.ts +++ b/packages/mrl/src/services/wormhole/WormholeService.ts @@ -45,4 +45,44 @@ export class WormholeService { return xfer.initiateTransfer(new WormholeWagmiSigner(this.chain, signer)); } + + async getVaa(txId: string) { + return await TokenTransfer.getTransferVaa(this.#wh, txId); + } + + async getVaaBytes(vaa: TokenTransfer.VAA) { + return ( + (await this.#wh.getVaaBytes({ + chain: vaa.emitterChain, + emitter: vaa.emitterAddress, + sequence: vaa.sequence, + })) || undefined + ); + } + + async getTokenTransfer(vaa: TokenTransfer.VAA, txId: string) { + return await TokenTransfer.from(this.#wh, { + chain: vaa.emitterChain, + txid: txId, + }); + } + + async isComplete(vaa: TokenTransfer.VAA, tokenTransfer: TokenTransfer) { + const isComplete = await TokenTransfer.isTransferComplete( + tokenTransfer.toChain, + vaa, + ); + return isComplete; + } + + async completeTransfer( + tokenTransfer: TokenTransfer, + signer: EvmSigner, + ): Promise { + const txIds = await tokenTransfer.completeTransfer( + new WormholeWagmiSigner(this.chain, signer), + ); + + return txIds[0]; + } }