diff --git a/packages/builder/src/mrl/MrlBuilder.interfaces.ts b/packages/builder/src/mrl/MrlBuilder.interfaces.ts index 8d85587c..432739cc 100644 --- a/packages/builder/src/mrl/MrlBuilder.interfaces.ts +++ b/packages/builder/src/mrl/MrlBuilder.interfaces.ts @@ -11,10 +11,16 @@ import type { ExtrinsicConfig } from '../extrinsic'; import type { WormholeConfig } from './providers/wormhole/wormhole'; export type MrlConfigBuilder = ConfigBuilder< - ContractConfig | ExtrinsicConfig | WormholeConfig, + ContractConfig | ExtrinsicConfig | ExtrinsicWormholeConfig | WormholeConfig, MrlBuilderParams >; +// TODO mjm improve this +export interface ExtrinsicWormholeConfig { + extrinsic: ExtrinsicConfig; + wormholeConfig: WormholeConfig; +} + export interface MrlBuilderParams extends BuilderPrams { isAutomatic: boolean; moonApi: ApiPromise; diff --git a/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.ts b/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.ts index a7e7cee7..9167b84b 100644 --- a/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.ts +++ b/packages/builder/src/mrl/providers/wormhole/extrinsic/polkadotXcm/polkadotXcm.ts @@ -2,7 +2,9 @@ import { type AnyParachain, AssetAmount } from '@moonbeam-network/xcm-types'; import { getMultilocationDerivedAddresses } from '@moonbeam-network/xcm-utils'; import { ExtrinsicBuilder } from '../../../../../extrinsic/ExtrinsicBuilder'; import { ExtrinsicConfig } from '../../../../../types/substrate/ExtrinsicConfig'; +import { MrlBuilder } from '../../../../MrlBuilder'; import type { MrlConfigBuilder } from '../../../../MrlBuilder.interfaces'; +import type { WormholeConfig } from '../../wormhole'; // TODO: Can we move them somewhere? const BUY_EXECUTION_FEE = 100_000_000_000_000_000n; @@ -11,20 +13,20 @@ const CROSS_CHAIN_FEE = 100_000_000_000_000_000n; export function polkadotXcm() { return { send: (): MrlConfigBuilder => ({ - build: ({ - asset, - destination, - destinationAddress, - destinationApi, - fee, - moonAsset, - moonChain, - moonApi, - source, - sourceAddress, - sourceApi, - transact, - }) => { + build: (params) => { + const { + asset, + destination, + fee, + moonAsset, + moonChain, + moonApi, + source, + sourceAddress, + sourceApi, + transact, + } = params; + if (!destination.wh?.name) { throw new Error('Destination chain does not have a wormhole name'); } @@ -165,11 +167,19 @@ export function polkadotXcm() { ); // TODO add here ability to only send the remote execution (only `send`) - return new ExtrinsicConfig({ + const extrinsic = new ExtrinsicConfig({ module: 'utility', func: 'batchAll', getArgs: () => [[assetTransferTx, feeAssetTransferTx, send]], }); + + const wormholeConfig = MrlBuilder() + .wormhole() + .wormhole() + .tokenTransfer() + .build(params) as WormholeConfig; // TODO make wormhole build to return this? + + return { extrinsic, wormholeConfig }; }, }), }; diff --git a/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts b/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts index c574982c..cff3d654 100644 --- a/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts +++ b/packages/builder/src/mrl/providers/wormhole/wormhole/wormhole.ts @@ -6,7 +6,7 @@ import type { MrlBuilderParams, MrlConfigBuilder, } from '../../../MrlBuilder.interfaces'; -import { WormholeConfig } from './WormholeConfig'; +import { WormholeConfig, type WormholeFunctionArgs } from './WormholeConfig'; import { wormholeFactory } from './wormholeFactory'; export const GMP_CONTRACT_ADDRESS = @@ -15,53 +15,16 @@ export const GMP_CONTRACT_ADDRESS = export function wormhole() { return { tokenTransfer: (): MrlConfigBuilder => ({ - build: ({ - asset, - destination, - destinationAddress, - isAutomatic, - moonApi, - moonChain, - source, - sourceAddress, - }) => { - const isNativeAsset = asset.isSame(source.nativeAsset); - const isDestinationMoonChain = destination.isEqual(moonChain); - const isDestinationEvmChain = EvmChain.is(destination); - const tokenAddress = isNativeAsset ? 'native' : asset.address; - - if (!tokenAddress) { - throw new Error(`Asset ${asset.key} has no address`); - } - - const wh = wormholeFactory(source); - const whSource = wh.getChain(source.getWormholeName()); - const whDestination = isDestinationEvmChain - ? wh.getChain(destination.getWormholeName()) - : wh.getChain(moonChain.getWormholeName()); - const whAsset = Wormhole.tokenId(whSource.chain, tokenAddress); - const whSourceAddress = Wormhole.chainAddress( - whSource.chain, - sourceAddress, - ); - const whDestinationAddress = Wormhole.chainAddress( - whDestination.chain, - isDestinationMoonChain || isDestinationEvmChain - ? destinationAddress - : GMP_CONTRACT_ADDRESS, - ); + build: (params): WormholeConfig => { + const isDestinationEvmChain = EvmChain.is(params.destination); + + // TODO unify this, this is just as demonstration + const args = isDestinationEvmChain + ? generateDemoArgs(params) + : generateWormholeArgs(params); return new WormholeConfig({ - args: [ - whAsset, - asset.amount, - whSourceAddress, - whDestinationAddress, - isAutomatic, - isDestinationMoonChain || isDestinationEvmChain - ? undefined - : getPayload({ destination, destinationAddress, moonApi }), - ], + args, func: 'tokenTransfer', }); }, @@ -69,6 +32,52 @@ export function wormhole() { }; } +// TODO mjm move from here? +export function generateWormholeArgs({ + asset, + destination, + destinationAddress, + isAutomatic, + moonApi, + moonChain, + source, + sourceAddress, +}: MrlBuilderParams): WormholeFunctionArgs { + const isNativeAsset = asset.isSame(source.nativeAsset); + const isDestinationMoonChain = destination.isEqual(moonChain); + const isDestinationEvmChain = EvmChain.is(destination); + const tokenAddress = isNativeAsset ? 'native' : asset.address; + + if (!tokenAddress) { + throw new Error(`Asset ${asset.key} has no address`); + } + + const wh = wormholeFactory(source); + const whSource = wh.getChain(source.getWormholeName()); + const whDestination = isDestinationEvmChain + ? wh.getChain(destination.getWormholeName()) + : wh.getChain(moonChain.getWormholeName()); + const whAsset = Wormhole.tokenId(whSource.chain, tokenAddress); + const whSourceAddress = Wormhole.chainAddress(whSource.chain, sourceAddress); + const whDestinationAddress = Wormhole.chainAddress( + whDestination.chain, + isDestinationMoonChain || isDestinationEvmChain + ? destinationAddress + : GMP_CONTRACT_ADDRESS, + ); + + return [ + whAsset, + asset.amount, + whSourceAddress, + whDestinationAddress, + isAutomatic, + isDestinationMoonChain || isDestinationEvmChain + ? undefined + : getPayload({ destination, destinationAddress, moonApi }), + ]; +} + /* * Extrinsic to GMP precompile * https://docs.moonbeam.network/builders/ethereum/precompiles/interoperability/gmp/ @@ -114,3 +123,49 @@ export function getPayload({ return versioned.toU8a(); } + +// TODO remove this, this is just as demonstration +function generateDemoArgs({ + asset, + destination, + destinationAddress, + isAutomatic, + moonChain, + source, +}: MrlBuilderParams): WormholeFunctionArgs { + console.log('moonchain', moonChain); + const isNativeAsset = asset.isSame(moonChain.nativeAsset); + // const isDestinationMoonChain = destination.isEqual(moonChain); + const isDestinationEvmChain = EvmChain.is(destination); + const tokenAddress = isNativeAsset + ? 'native' + : moonChain.getChainAsset(asset).address; + + if (!tokenAddress) { + throw new Error(`Asset ${asset.key} has no address`); + } + + const wh = wormholeFactory(source); + const whSource = wh.getChain(moonChain.getWormholeName()); + const whDestination = isDestinationEvmChain + ? wh.getChain(destination.getWormholeName()) + : wh.getChain(moonChain.getWormholeName()); + const whAsset = Wormhole.tokenId(whSource.chain, tokenAddress); + const whSourceAddress = Wormhole.chainAddress( + whSource.chain, + destinationAddress, + ); // really it is computedOriginAccount + const whDestinationAddress = Wormhole.chainAddress( + whDestination.chain, + destinationAddress, + ); + + return [ + whAsset, + asset.amount, + whSourceAddress, + whDestinationAddress, + isAutomatic, + undefined, + ]; +} diff --git a/packages/config/src/chains.ts b/packages/config/src/chains.ts index 659a338b..e43dcdf9 100644 --- a/packages/config/src/chains.ts +++ b/packages/config/src/chains.ts @@ -460,11 +460,15 @@ export const fantomTestnet = new EvmChain({ ChainAsset.fromAsset(ftm, { decimals: 18, }), - // TODO should be WGLMR + // TODO should be WGLMR ? ChainAsset.fromAsset(dev, { address: '0x41E3CFDFC255A4bF3C8D3560Bc8D3D9b5080338e', decimals: 18, }), + ChainAsset.fromAsset(agng, { + address: '0xBb4D53C75654D28f69470546414401A2b31b586c', + decimals: 18, + }), ], ecosystem: Ecosystem.AlphanetRelay, explorer: 'https://testnet.ftmscan.com', diff --git a/packages/config/src/mrl-configs/fantomTestnet.ts b/packages/config/src/mrl-configs/fantomTestnet.ts index 4269db98..3b742504 100644 --- a/packages/config/src/mrl-configs/fantomTestnet.ts +++ b/packages/config/src/mrl-configs/fantomTestnet.ts @@ -1,6 +1,11 @@ import { BalanceBuilder, MrlBuilder } from '@moonbeam-network/xcm-builder'; import { dev, ftm, ftmwh } from '../assets'; -import { fantomTestnet, moonbaseAlpha, peaqAlphanet } from '../chains'; +import { + fantomTestnet, + moonbaseAlpha, + moonbaseBeta, + peaqAlphanet, +} from '../chains'; import { ChainRoutes } from '../types/ChainRoutes'; export const fantomTestnetRoutes = new ChainRoutes({ @@ -25,7 +30,38 @@ export const fantomTestnetRoutes = new ChainRoutes({ }, }, mrl: { - isAutomatic: false, // TODO should be isAutomaticPossible + isAutomaticPossible: false, + transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), + moonChain: { + asset: ftmwh, + fee: { + asset: dev, + amount: 0.1, + balance: BalanceBuilder().substrate().system().account(), + }, + }, + }, + }, + { + source: { + asset: ftm, + balance: BalanceBuilder().evm().native(), + destinationFee: { + asset: ftm, + balance: BalanceBuilder().evm().native(), + }, + }, + destination: { + asset: ftmwh, + chain: moonbaseBeta, + balance: BalanceBuilder().substrate().assets().account(), + fee: { + asset: ftmwh, + amount: 0.06, + }, + }, + mrl: { + isAutomaticPossible: false, transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), moonChain: { asset: ftmwh, @@ -56,7 +92,7 @@ export const fantomTestnetRoutes = new ChainRoutes({ }, }, mrl: { - isAutomatic: true, + isAutomaticPossible: true, transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), moonChain: { asset: ftmwh, @@ -87,7 +123,7 @@ export const fantomTestnetRoutes = new ChainRoutes({ }, }, mrl: { - isAutomatic: false, + isAutomaticPossible: true, transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), moonChain: { asset: dev, diff --git a/packages/config/src/mrl-configs/index.ts b/packages/config/src/mrl-configs/index.ts index 4636267a..fdfb929e 100644 --- a/packages/config/src/mrl-configs/index.ts +++ b/packages/config/src/mrl-configs/index.ts @@ -1,11 +1,13 @@ import type { ChainRoutes } from '../types/ChainRoutes'; import { fantomTestnetRoutes } from './fantomTestnet'; import { moonbaseAlphaRoutes } from './moonbaseAlpha'; +import { moonbaseBetaRoutes } from './moonbaseBeta'; import { peaqAlphanetRoutes } from './peaqAlphanet'; export const mrlRoutesList: ChainRoutes[] = [ fantomTestnetRoutes, moonbaseAlphaRoutes, + moonbaseBetaRoutes, peaqAlphanetRoutes, ]; diff --git a/packages/config/src/mrl-configs/moonbaseAlpha.ts b/packages/config/src/mrl-configs/moonbaseAlpha.ts index ac238499..1c587a6a 100644 --- a/packages/config/src/mrl-configs/moonbaseAlpha.ts +++ b/packages/config/src/mrl-configs/moonbaseAlpha.ts @@ -25,7 +25,7 @@ export const moonbaseAlphaRoutes = new ChainRoutes({ }, }, mrl: { - isAutomatic: true, + isAutomaticPossible: true, transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), moonChain: { asset: ftmwh, @@ -56,7 +56,7 @@ export const moonbaseAlphaRoutes = new ChainRoutes({ }, }, mrl: { - isAutomatic: true, + isAutomaticPossible: true, transfer: MrlBuilder().wormhole().wormhole().tokenTransfer(), moonChain: { asset: dev, diff --git a/packages/config/src/mrl-configs/moonbaseBeta.ts b/packages/config/src/mrl-configs/moonbaseBeta.ts new file mode 100644 index 00000000..c32e5bb4 --- /dev/null +++ b/packages/config/src/mrl-configs/moonbaseBeta.ts @@ -0,0 +1,45 @@ +import { BalanceBuilder, MrlBuilder } from '@moonbeam-network/xcm-builder'; +import { betaDEV, dev, ftm, ftmwh } from '../assets'; +import { fantomTestnet, moonbaseBeta } from '../chains'; +import { ChainRoutes } from '../types/ChainRoutes'; + +export const moonbaseBetaRoutes = new ChainRoutes({ + chain: moonbaseBeta, + routes: [ + { + source: { + asset: ftmwh, + balance: BalanceBuilder().substrate().assets().account(), + destinationFee: { + asset: ftmwh, + balance: BalanceBuilder().substrate().assets().account(), + }, + fee: { + asset: betaDEV, + balance: BalanceBuilder().substrate().system().account(), + }, + }, + destination: { + asset: ftm, + chain: fantomTestnet, + balance: BalanceBuilder().evm().native(), + fee: { + asset: ftm, + amount: 0, + }, + }, + mrl: { + isAutomaticPossible: true, + transfer: MrlBuilder().wormhole().extrinsic().polkadotXcm().send(), + moonChain: { + asset: ftmwh, + fee: { + asset: dev, + amount: 0.1, + balance: BalanceBuilder().substrate().system().account(), + }, + }, + }, + }, + ], +}); diff --git a/packages/config/src/mrl-configs/peaqAlphanet.ts b/packages/config/src/mrl-configs/peaqAlphanet.ts index 4328fd21..4795c67a 100644 --- a/packages/config/src/mrl-configs/peaqAlphanet.ts +++ b/packages/config/src/mrl-configs/peaqAlphanet.ts @@ -29,7 +29,7 @@ export const peaqAlphanetRoutes = new ChainRoutes({ }, }, mrl: { - isAutomatic: true, + isAutomaticPossible: true, transfer: MrlBuilder().wormhole().extrinsic().polkadotXcm().send(), moonChain: { asset: ftmwh, @@ -51,19 +51,19 @@ export const peaqAlphanetRoutes = new ChainRoutes({ }, }, destination: { - asset: ftm, + asset: agng, chain: fantomTestnet, balance: BalanceBuilder().evm().native(), fee: { - asset: ftm, - amount: 0, + asset: agng, + amount: 0.2, }, }, mrl: { - isAutomatic: true, + isAutomaticPossible: true, transfer: MrlBuilder().wormhole().extrinsic().polkadotXcm().send(), moonChain: { - asset: ftmwh, + asset: agng, fee: { asset: dev, amount: 0.1, diff --git a/packages/config/src/types/AssetRoute.ts b/packages/config/src/types/AssetRoute.ts index 31895dc5..e8d4069e 100644 --- a/packages/config/src/types/AssetRoute.ts +++ b/packages/config/src/types/AssetRoute.ts @@ -46,7 +46,7 @@ export interface FeeConfig { } export interface MrlConfig { - isAutomatic: boolean; + isAutomaticPossible: boolean; transfer: MrlConfigBuilder; moonChain: MoonChainConfig; } diff --git a/packages/mrl/src/getTransferData/getSourceData.ts b/packages/mrl/src/getTransferData/getSourceData.ts index e2dc3bfc..fe69a9aa 100644 --- a/packages/mrl/src/getTransferData/getSourceData.ts +++ b/packages/mrl/src/getTransferData/getSourceData.ts @@ -1,11 +1,11 @@ import { ContractConfig, - type ExtrinsicConfig, + ExtrinsicConfig, + type ExtrinsicWormholeConfig, WormholeConfig, } from '@moonbeam-network/xcm-builder'; import type { AssetRoute, FeeConfig } from '@moonbeam-network/xcm-config'; import { - type SourceChainTransferData, getAssetMin, getBalance, getContractFee, @@ -21,6 +21,7 @@ import { type EvmChain, type EvmParachain, } from '@moonbeam-network/xcm-types'; +import type { SourceTransferData } from '../mrl.interfaces'; import { WormholeService } from '../services/wormhole'; import { buildTransfer } from './getTransferData.utils'; @@ -36,7 +37,7 @@ export async function getSourceData({ destinationAddress, destinationFee, sourceAddress, -}: GetSourceDataParams): Promise { +}: GetSourceDataParams): Promise { if (!route.mrl) { throw new Error( `MrlConfigBuilder is not defined for source chain ${route.source.chain.name} and asset ${route.source.asset.originSymbol}`, @@ -95,6 +96,12 @@ export async function getSourceData({ sourceAddress, }); + const relayFee = await getRelayFee({ + balance, + chain: source, + transfer, + }); + const max = getMax({ balance, existentialDeposit, @@ -111,6 +118,7 @@ export async function getSourceData({ feeBalance, max, min, + relayFee, }; } @@ -121,7 +129,21 @@ export interface GetFeeParams { feeBalance: AssetAmount; feeConfig?: FeeConfig; sourceAddress: string; - transfer: ContractConfig | ExtrinsicConfig | WormholeConfig; + transfer: + | ContractConfig + | ExtrinsicConfig + | ExtrinsicWormholeConfig + | WormholeConfig; +} + +export interface GetRelayFeeParams { + balance: AssetAmount; + chain: AnyChain; + transfer: + | ContractConfig + | ExtrinsicConfig + | ExtrinsicWormholeConfig + | WormholeConfig; } export async function getFee({ @@ -134,15 +156,9 @@ export async function getFee({ sourceAddress, }: GetFeeParams): Promise { if (WormholeConfig.is(transfer)) { - const wh = WormholeService.create(chain as EvmChain | EvmParachain); - const fee = await wh.getFee(transfer); - - console.log('fee in getFee.WormholeConfig', fee); - - // TODO technically this is not the fee on source chain, it's relayer fee - // source fee should be the fee paid to send the message in polkadot to eth or to sign the transaction in eth to polkadot - return AssetAmount.fromChainAsset(chain.getChainAsset(balance), { - amount: fee.relayFee?.amount || 0n, + // TODO + return AssetAmount.fromChainAsset(chain.getChainAsset(feeBalance), { + amount: 0n, }); } @@ -157,12 +173,37 @@ export async function getFee({ }); } + const extrinsic = ExtrinsicConfig.is(transfer) + ? transfer + : transfer.extrinsic; + return getExtrinsicFee({ address: sourceAddress, balance, chain: chain as AnyParachain, - extrinsic: transfer as ExtrinsicConfig, + extrinsic, feeBalance, feeConfig, }); } + +export async function getRelayFee({ + balance, + chain, + transfer, +}: GetRelayFeeParams): Promise { + if (ExtrinsicConfig.is(transfer) || ContractConfig.is(transfer)) { + return undefined; + } + + const wormholeConfig = WormholeConfig.is(transfer) + ? transfer + : transfer.wormholeConfig; + + const wh = WormholeService.create(chain as EvmChain | EvmParachain); + const fee = await wh.getFee(wormholeConfig); + + return AssetAmount.fromChainAsset(chain.getChainAsset(balance), { + amount: fee.relayFee?.amount || 0n, + }); +} diff --git a/packages/mrl/src/getTransferData/getTransferData.utils.ts b/packages/mrl/src/getTransferData/getTransferData.utils.ts index a76bed5b..ef82e655 100644 --- a/packages/mrl/src/getTransferData/getTransferData.utils.ts +++ b/packages/mrl/src/getTransferData/getTransferData.utils.ts @@ -124,7 +124,7 @@ export async function buildTransfer({ destinationAddress, destinationApi, fee: destinationFee, - isAutomatic: route.mrl.isAutomatic, // TODO + isAutomatic: route.mrl.isAutomaticPossible, // TODO moonApi, moonAsset: moonChain.nativeAsset, moonChain, diff --git a/packages/mrl/src/mrl.interfaces.ts b/packages/mrl/src/mrl.interfaces.ts index 29e02134..20bfb17a 100644 --- a/packages/mrl/src/mrl.interfaces.ts +++ b/packages/mrl/src/mrl.interfaces.ts @@ -1,3 +1,4 @@ +import type { 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'; @@ -21,8 +22,10 @@ export interface TransferData { ): Promise; } -export interface SourceTransferData extends ChainTransferData { +// TODO mjm is this needed? +export interface SourceTransferData extends SourceChainTransferData { destinationFeeBalance: AssetAmount; + relayFee?: AssetAmount; feeBalance: AssetAmount; max: AssetAmount; }