From 458d6d8f5fb34c55ce67ec60654e709cdf77d4a9 Mon Sep 17 00:00:00 2001 From: Mario J Maurello Date: Thu, 24 Oct 2024 14:56:49 +0200 Subject: [PATCH] Add redeeming functionalities for Womrhole --- examples/mrl-simple/index.ts | 56 ++++++++++++++++--- .../builder/src/mrl/MrlBuilder.interfaces.ts | 9 +++ .../providers/wormhole/contract/Gmp/Gmp.ts | 28 ++++++++++ .../providers/wormhole/contract/Gmp/GmpAbi.ts | 15 +++++ .../providers/wormhole/contract/Gmp/index.ts | 1 + .../mrl/providers/wormhole/contract/index.ts | 3 +- .../providers/wormhole/wormhole/wormhole.ts | 4 +- .../mrl/src/getTransferData/getRedeemData.ts | 49 ++++++++++++++++ packages/mrl/src/mrl.interfaces.ts | 13 ++++- packages/mrl/src/mrl.ts | 10 ++++ .../src/services/wormhole/WormholeService.ts | 40 +++++++++++++ 11 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 packages/builder/src/mrl/providers/wormhole/contract/Gmp/Gmp.ts create mode 100644 packages/builder/src/mrl/providers/wormhole/contract/Gmp/GmpAbi.ts create mode 100644 packages/builder/src/mrl/providers/wormhole/contract/Gmp/index.ts create mode 100644 packages/mrl/src/getTransferData/getRedeemData.ts diff --git a/examples/mrl-simple/index.ts b/examples/mrl-simple/index.ts index 8f32c373..251d9b7a 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 tx = + '0x7c2771b74211420284c44116fe436003df31713a7ffefb034b2ce21ef3711cef'; + const walletClient = createWalletClient({ + account, + chain: moonbaseAlphaViem, + transport: http(), + }); + + const data = await Mrl().setTxHashToRedeem(tx).setRedeemChain(moonbaseAlpha); + console.log('data', data); + + await data.redeem(walletClient as EvmSigner); +} + +async function redeemInFantomTestnet() { + const tx = + '0xa0032ff270885f7278a42d4d974fceab9e4feb039263db35b09beafe57bd6683'; + const walletClient = createWalletClient({ + account, + chain: fantomTestnet.getViemChain(), + transport: http(), + }); + + const data = await Mrl().setTxHashToRedeem(tx).setRedeemChain(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..e76f0a71 --- /dev/null +++ b/packages/builder/src/mrl/providers/wormhole/contract/Gmp/Gmp.ts @@ -0,0 +1,28 @@ +import type { Address } from 'viem'; +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 = + `0x${Buffer.from(bytes as Uint8Array).toString('hex')}` as Address; + + 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/mrl/src/getTransferData/getRedeemData.ts b/packages/mrl/src/getTransferData/getRedeemData.ts new file mode 100644 index 00000000..3fe68776 --- /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 GetRedeemDataParams { + txId: string; + chain: EvmChain | EvmParachain; +} + +export async function getRedeemData({ txId, chain }: GetRedeemDataParams) { + // 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..c9a44e8a 100644 --- a/packages/mrl/src/mrl.ts +++ b/packages/mrl/src/mrl.ts @@ -3,7 +3,10 @@ import type { AnyAsset, AnyChain, Ecosystem, + EvmChain, + EvmParachain, } from '@moonbeam-network/xcm-types'; +import { getRedeemData } from './getTransferData/getRedeemData'; import { getTransferData } from './getTransferData/getTransferData'; const DEFAULT_SERVICE = new ConfigService({ routes: mrlRoutesMap }); @@ -56,5 +59,12 @@ export function Mrl(options?: MrlOptions) { }, }; }, + setTxHashToRedeem(txId: string) { + return { + setRedeemChain(chain: EvmChain | EvmParachain) { + 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]; + } }