From 9e3107cbd3801d0d353ba3eda0cc13de14e1eeb5 Mon Sep 17 00:00:00 2001 From: Ankit Boghra Date: Thu, 21 Aug 2025 21:17:53 +0400 Subject: [PATCH] feat: bridge adapter --- src/lxly/bridge/abi/bridge-adapter.ts | 20 +++++++++ src/lxly/bridge/abi/index.ts | 8 ++++ src/lxly/bridge/bridge-adapter.ts | 61 +++++++++++++++++++++++++++ src/lxly/bridge/index.ts | 9 ++++ src/lxly/index.ts | 1 + src/lxly/tokens/erc20.ts | 36 ++++++++++++++++ src/types/bridge.ts | 7 +++ 7 files changed, 142 insertions(+) create mode 100644 src/lxly/bridge/abi/bridge-adapter.ts create mode 100644 src/lxly/bridge/abi/index.ts create mode 100644 src/lxly/bridge/bridge-adapter.ts create mode 100644 src/lxly/bridge/index.ts diff --git a/src/lxly/bridge/abi/bridge-adapter.ts b/src/lxly/bridge/abi/bridge-adapter.ts new file mode 100644 index 0000000..761d3ba --- /dev/null +++ b/src/lxly/bridge/abi/bridge-adapter.ts @@ -0,0 +1,20 @@ +/** + * Bridge Adapter ABI + * + * Polygon ZkEVM Bridge Adapter contract ABI for custom ERC20 bridging + */ + +export const bridgeAdapterAbi = [ + { + inputs: [ + { internalType: 'address', name: 'recipient', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { internalType: 'uint32', name: 'destinationNetworkId', type: 'uint32' }, + { internalType: 'bool', name: 'forceUpdateGlobalExitRoot', type: 'bool' }, + ], + name: 'bridgeToken', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/lxly/bridge/abi/index.ts b/src/lxly/bridge/abi/index.ts new file mode 100644 index 0000000..47d8f0d --- /dev/null +++ b/src/lxly/bridge/abi/index.ts @@ -0,0 +1,8 @@ +/** + * Bridge ABI Exports + * + * Central export point for all bridge-related ABIs + */ + +export { bridgeAbi } from './bridge'; +export { bridgeAdapterAbi } from './bridge-adapter'; diff --git a/src/lxly/bridge/bridge-adapter.ts b/src/lxly/bridge/bridge-adapter.ts new file mode 100644 index 0000000..39c4705 --- /dev/null +++ b/src/lxly/bridge/bridge-adapter.ts @@ -0,0 +1,61 @@ +/** + * Bridge Adapter Implementation + * + * Custom ERC20 bridging functionality using bridge adapter contract + */ + +import { encodeFunctionData, type Address } from 'viem'; +import { ValidationUtils } from '../utils'; +import { BaseContract } from '../base/contract'; +import type { BridgeTokenParams, TransactionParams } from '../../types'; +import { bridgeAdapterAbi } from './abi/bridge-adapter'; + +export interface BridgeAdapterConfig { + bridgeAdapterAddress: string; + rpcUrl: string; + chainId: number; +} + +export class BridgeAdapter extends BaseContract { + private bridgeAdapterAddress: string; + + constructor(config: BridgeAdapterConfig) { + super({ rpcUrl: config.rpcUrl, chainId: config.chainId }); + this.bridgeAdapterAddress = config.bridgeAdapterAddress; + } + + /** + * Build bridge token transaction for custom ERC20 + */ + async buildBridgeToken( + params: BridgeTokenParams, + from?: string + ): Promise { + ValidationUtils.validateAddress(params.recipient, 'Recipient address'); + ValidationUtils.validateAmount(params.amount, 'Amount'); + + const data = encodeFunctionData({ + abi: bridgeAdapterAbi, + functionName: 'bridgeToken', + args: [ + params.recipient as Address, + BigInt(params.amount), + params.destinationNetworkId, + params.forceUpdateGlobalExitRoot, + ], + }); + + const [nonce, gas] = await Promise.all([ + this.getNonce(from), + this.estimateGas(data, this.bridgeAdapterAddress, from), + ]); + + return { + from, + to: this.bridgeAdapterAddress, + data, + gas, + nonce, + }; + } +} diff --git a/src/lxly/bridge/index.ts b/src/lxly/bridge/index.ts new file mode 100644 index 0000000..dcd077a --- /dev/null +++ b/src/lxly/bridge/index.ts @@ -0,0 +1,9 @@ +/** + * Bridge Exports + * + * Central export point for all bridge-related functionality + */ + +export { Bridge } from './bridge'; +export { BridgeAdapter } from './bridge-adapter'; +export { BridgeUtil } from './util'; diff --git a/src/lxly/index.ts b/src/lxly/index.ts index 41308d8..9b566c0 100644 --- a/src/lxly/index.ts +++ b/src/lxly/index.ts @@ -16,6 +16,7 @@ export interface LxlyConfig { export { ERC20 } from './tokens/erc20'; export { Bridge } from './bridge/bridge'; +export { BridgeAdapter } from './bridge/bridge-adapter'; export { BridgeUtil } from './bridge/util'; export class LxlyClient { diff --git a/src/lxly/tokens/erc20.ts b/src/lxly/tokens/erc20.ts index 03f1d6f..b3ed335 100644 --- a/src/lxly/tokens/erc20.ts +++ b/src/lxly/tokens/erc20.ts @@ -19,6 +19,7 @@ import { buildTransferFrom as buildTransferFromTx, } from './build'; import { Bridge } from '../bridge/bridge'; +import { BridgeAdapter } from '../bridge/bridge-adapter'; import { chainRegistry } from '../../chains/registry'; import { getAbi } from '../services/abi'; @@ -174,6 +175,41 @@ export class ERC20 extends BaseContract { ); } + /** + * Bridge custom ERC20 token to another network using bridge adapter + */ + async bridgeToken( + recipient: string, + amount: string, + destinationNetworkId: number, + bridgeAdapterAddress: string, + forceUpdateGlobalExitRoot = true, + from?: string + ): Promise { + ValidationUtils.validateAddress(recipient, 'Recipient address'); + ValidationUtils.validateAmount(amount, 'Amount'); + ValidationUtils.validateAddress( + bridgeAdapterAddress, + 'Bridge adapter address' + ); + + const bridgeAdapter = new BridgeAdapter({ + bridgeAdapterAddress, + rpcUrl: this.config.rpcUrl, + chainId: this.config.chainId, + }); + + return bridgeAdapter.buildBridgeToken( + { + recipient, + amount, + destinationNetworkId, + forceUpdateGlobalExitRoot, + }, + from + ); + } + /** * Get wrapped version of this token on destination network */ diff --git a/src/types/bridge.ts b/src/types/bridge.ts index 0a792b9..bac91f9 100644 --- a/src/types/bridge.ts +++ b/src/types/bridge.ts @@ -13,6 +13,13 @@ export interface BridgeAssetParams { permitData?: string; } +export interface BridgeTokenParams { + recipient: string; + amount: string; + destinationNetworkId: number; + forceUpdateGlobalExitRoot: boolean; +} + export interface BridgeOptions { forceUpdateGlobalExitRoot?: boolean; permitData?: string;