Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add standalone redeeming capability #381

Merged
merged 8 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions examples/mrl-simple/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Rihyx marked this conversation as resolved.
Show resolved Hide resolved
// 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(
Expand All @@ -86,7 +121,7 @@ async function fromFantomToPeaq(

await data.transfer(amount, isAutomatic, {
polkadotSigner: pair,
evmSigner: walletClient,
evmSigner: walletClient as EvmSigner,
ekenigs marked this conversation as resolved.
Show resolved Hide resolved
});
}

Expand All @@ -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(
Expand All @@ -141,7 +177,7 @@ async function fromMoonbaseToFantom(

await data.transfer(amount, isAutomatic, {
polkadotSigner: pair,
evmSigner: walletClient,
evmSigner: walletClient as EvmSigner,
});
}

Expand Down Expand Up @@ -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,
});
}
9 changes: 9 additions & 0 deletions packages/builder/src/mrl/MrlBuilder.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export type MrlConfigBuilder = ConfigBuilder<
MrlBuilderParams
>;

export type MrlRedeemConfigBuilder = ConfigBuilder<
ContractConfig,
MrlRedeemBuilderParams
>;

export interface MrlBuilderParams extends BuilderParams<AnyChain> {
isAutomatic: boolean;
moonApi: ApiPromise;
Expand All @@ -24,6 +29,10 @@ export interface MrlBuilderParams extends BuilderParams<AnyChain> {
transact?: Transact;
}

export interface MrlRedeemBuilderParams {
bytes?: Uint8Array;
}

export interface Transact {
call: HexString;
txWeight: {
Expand Down
27 changes: 27 additions & 0 deletions packages/builder/src/mrl/providers/wormhole/contract/Gmp/Gmp.ts
Original file line number Diff line number Diff line change
@@ -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,
});
},
}),
};
}
15 changes: 15 additions & 0 deletions packages/builder/src/mrl/providers/wormhole/contract/Gmp/GmpAbi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const GMP_ABI = [
{
inputs: [
{
internalType: 'bytes',
name: 'vaa',
type: 'bytes',
},
],
name: 'wormholeTransferERC20',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Gmp';
Original file line number Diff line number Diff line change
@@ -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 };
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ({
Expand Down
33 changes: 32 additions & 1 deletion packages/config/src/mrl-configs/fantomTestnet.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand Down
49 changes: 49 additions & 0 deletions packages/mrl/src/getTransferData/getRedeemData.ts
Original file line number Diff line number Diff line change
@@ -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
Rihyx marked this conversation as resolved.
Show resolved Hide resolved
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);
},
};
}
13 changes: 12 additions & 1 deletion packages/mrl/src/mrl.interfaces.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -41,3 +45,10 @@ export interface ChainTransferData {
fee: AssetAmount;
min: AssetAmount;
}

// TODO this is just for Wormhole
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what this comment means

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that the RedeemData only make sense if we consider that the provider is Wormhole. If you look at the properties (vaa, tokenTransfer), its types are from the wormhole-sdk. So if in the future we add another provider / bridge, this data will not make sense.
Not sure how to fix it for the moment

export type RedeemData = {
vaa: TokenTransfer.VAA;
tokenTransfer: TokenTransfer;
transfer(signer: EvmSigner): Promise<string[]>;
};
7 changes: 7 additions & 0 deletions packages/mrl/src/mrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down Expand Up @@ -56,5 +60,8 @@ export function Mrl(options?: MrlOptions) {
},
};
},
getRedeemData({ txId, chain }: WormholeRedeemParams) {
return getRedeemData({ txId, chain });
},
};
}
40 changes: 40 additions & 0 deletions packages/mrl/src/services/wormhole/WormholeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
const txIds = await tokenTransfer.completeTransfer(
new WormholeWagmiSigner(this.chain, signer),
);

return txIds[0];
}
}