@@ -253,22 +345,24 @@ export default function TransferContainer() {
{/* Action buttons */}
-
+ {!isCrossChain && (
+
+ )}
diff --git a/packages/nextjs/components/modals/PortFolioModal.tsx b/packages/nextjs/components/modals/PortFolioModal.tsx
index 041aa7a8..9dec0261 100644
--- a/packages/nextjs/components/modals/PortFolioModal.tsx
+++ b/packages/nextjs/components/modals/PortFolioModal.tsx
@@ -4,7 +4,7 @@ import React from "react";
import Image from "next/image";
import { Button } from "../ui/button";
import { Sheet, SheetClose, SheetContent, SheetTitle, SheetTrigger } from "../ui/sheet";
-import { ResolvedToken } from "@polypay/shared";
+import { ResolvedToken, formatDisplayValue } from "@polypay/shared";
import { Eye, EyeOff, MoveDown, MoveUp, X } from "lucide-react";
import { Address } from "viem";
import NetworkBadge from "~~/components/Common/NetworkBadge";
@@ -49,7 +49,7 @@ function TokenBalanceRow({ token, balance, usdValue, isLoading, chainId }: Token
{/* Balance & USD Value */}
- {isLoading ? "..." : `${balance} ${token.symbol}`}
+ {isLoading ? "..." : `${formatDisplayValue(balance, token.symbol)} ${token.symbol}`}
{isLoading
diff --git a/packages/nextjs/components/popovers/TokenPillPopover.tsx b/packages/nextjs/components/popovers/TokenPillPopover.tsx
index 80c732f0..c1654a44 100644
--- a/packages/nextjs/components/popovers/TokenPillPopover.tsx
+++ b/packages/nextjs/components/popovers/TokenPillPopover.tsx
@@ -2,7 +2,7 @@
import React, { useRef, useState } from "react";
import Image from "next/image";
-import { ResolvedToken } from "@polypay/shared";
+import { ResolvedToken, formatDisplayValue } from "@polypay/shared";
import NetworkBadge from "~~/components/Common/NetworkBadge";
import { useMetaMultiSigWallet, useTokenPrices } from "~~/hooks";
import { useNetworkTokens } from "~~/hooks/app/useNetworkTokens";
@@ -112,7 +112,7 @@ export function TokenPillPopover({
$ {isLoadingPrices ? "..." : getTokenUsdValue(token)}
- {balances[token.address] || "0"} {token.symbol}
+ {formatDisplayValue(balances[token.address] || "0", token.symbol)} {token.symbol}
diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts
index 6b9a414a..f39329b6 100644
--- a/packages/nextjs/contracts/deployedContracts.ts
+++ b/packages/nextjs/contracts/deployedContracts.ts
@@ -7,7 +7,7 @@ import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";
const deployedContracts = {
2651420: {
MetaMultiSigWallet: {
- address: "0x1EaCA128069b2bb1cd476ef66E2701F98cAB148E",
+ address: "0x3c57e227ca5264ac0378800fA6E11FF112dB8538",
abi: [
{
inputs: [
@@ -178,6 +178,44 @@ const deployedContracts = {
stateMutability: "nonpayable",
type: "function",
},
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "token",
+ type: "address",
+ },
+ {
+ internalType: "address",
+ name: "spender",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "approveAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "address",
+ name: "callTarget",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "callValue",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "callData",
+ type: "bytes",
+ },
+ ],
+ name: "approveAndCall",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
{
inputs: [
{
@@ -525,7 +563,7 @@ const deployedContracts = {
},
],
inheritedFunctions: {},
- deployedOnBlock: 6615581,
+ deployedOnBlock: 10856432,
},
},
11155111: {
diff --git a/packages/nextjs/hooks/app/transaction/useTransactionVote.ts b/packages/nextjs/hooks/app/transaction/useTransactionVote.ts
index 3ac1f9f8..8ae54c98 100644
--- a/packages/nextjs/hooks/app/transaction/useTransactionVote.ts
+++ b/packages/nextjs/hooks/app/transaction/useTransactionVote.ts
@@ -1,19 +1,29 @@
import { useState } from "react";
import {
+ LZ_ENDPOINT_IDS,
+ OP_BRIDGE_ADDRESSES,
SignerData,
TxStatus,
TxType,
ZERO_ADDRESS,
encodeAddSigners,
+ encodeApproveAndCall,
encodeBatchTransfer,
encodeBatchTransferMulti,
+ encodeBridgeETHTo,
encodeERC20Transfer,
+ encodeLzSend,
encodeRemoveSigners,
encodeUpdateThreshold,
+ getBridgeContract,
+ getBridgeMechanism,
+ getOftCmd,
+ getTokenByAddress,
+ removeDust,
} from "@polypay/shared";
import { useQueryClient } from "@tanstack/react-query";
import { useWalletClient } from "wagmi";
-import { accountKeys, useMetaMultiSigWallet, userKeys } from "~~/hooks";
+import { accountKeys, useMetaMultiSigWallet, useNetworkTokens, userKeys } from "~~/hooks";
import { useApproveTransaction, useDenyTransaction, useExecuteTransaction } from "~~/hooks/api/useTransaction";
import { useGenerateProof } from "~~/hooks/app/useGenerateProof";
import { formatErrorMessage } from "~~/lib/form/utils";
@@ -56,6 +66,9 @@ export interface TransactionRowData {
oldThreshold?: number;
newThreshold?: number;
batchData?: BatchTransfer[];
+ destChainId?: number;
+ bridgeFee?: string;
+ bridgeMinAmount?: string;
contact?: {
id: string;
name: string;
@@ -70,9 +83,15 @@ export interface TransactionRowData {
}
/**
- * Build callData, to, and value based on transaction type
+ * Build callData, to, and value based on transaction type.
+ * For cross-chain TRANSFER, must reconstruct bridge execution params
+ * to produce the same txHash as the creator and backend.
*/
-function buildTransactionParams(tx: TransactionRowData): {
+function buildTransactionParams(
+ tx: TransactionRowData,
+ sourceChainId: number,
+ bridgeFee?: string,
+): {
to: `0x${string}`;
value: bigint;
callData: `0x${string}`;
@@ -82,7 +101,12 @@ function buildTransactionParams(tx: TransactionRowData): {
let value = BigInt(tx.amount || "0");
if (tx.type === TxType.TRANSFER) {
- // Check if ERC20 transfer
+ const isCrossChain = tx.destChainId && tx.destChainId !== sourceChainId;
+
+ if (isCrossChain) {
+ return buildBridgeTransactionParams(tx, sourceChainId, bridgeFee);
+ }
+
if (tx.tokenAddress && tx.tokenAddress !== ZERO_ADDRESS) {
to = tx.tokenAddress as `0x${string}`;
value = 0n;
@@ -117,6 +141,69 @@ function buildTransactionParams(tx: TransactionRowData): {
return { to, value, callData };
}
+function buildBridgeTransactionParams(
+ tx: TransactionRowData,
+ sourceChainId: number,
+ bridgeFee?: string,
+): { to: `0x${string}`; value: bigint; callData: `0x${string}` } {
+ const destChainId = tx.destChainId!;
+ const isNativeETH = !tx.tokenAddress || tx.tokenAddress === ZERO_ADDRESS;
+ const tokenSymbol = isNativeETH ? "ETH" : getTokenSymbolFromAddress(tx.tokenAddress!, sourceChainId);
+ const mechanism = getBridgeMechanism(sourceChainId, destChainId, tokenSymbol);
+ const amount = BigInt(tx.amount || "0");
+ const fee = bridgeFee ? BigInt(bridgeFee) : 0n;
+ const recipient = tx.recipientAddress!;
+
+ if (mechanism === "OP_STACK") {
+ const bridgeAddress = OP_BRIDGE_ADDRESSES[sourceChainId];
+ return {
+ to: bridgeAddress as `0x${string}`,
+ value: amount,
+ callData: encodeBridgeETHTo(recipient) as `0x${string}`,
+ };
+ }
+
+ // LAYERZERO
+ const dstEid = LZ_ENDPOINT_IDS[destChainId];
+ const oftEntry = getBridgeContract(tokenSymbol, sourceChainId);
+
+ if (!dstEid || !oftEntry) {
+ throw new Error(`No bridge route for ${tokenSymbol} from ${sourceChainId} to ${destChainId}`);
+ }
+
+ const token = getTokenByAddress(tx.tokenAddress, sourceChainId);
+ const minAmount = tx.bridgeMinAmount ? BigInt(tx.bridgeMinAmount) : removeDust(amount, token.decimals);
+ const oftCmd = getOftCmd(oftEntry);
+
+ const lzSendData = encodeLzSend(dstEid, recipient, amount, minAmount, fee, tx.accountAddress, oftCmd);
+
+ if (oftEntry.type === "adapter") {
+ return {
+ to: tx.accountAddress as `0x${string}`,
+ value: 0n,
+ callData: encodeApproveAndCall(
+ tx.tokenAddress!,
+ oftEntry.address,
+ amount,
+ oftEntry.address,
+ fee,
+ lzSendData,
+ ) as `0x${string}`,
+ };
+ }
+
+ return {
+ to: oftEntry.address as `0x${string}`,
+ value: fee,
+ callData: lzSendData as `0x${string}`,
+ };
+}
+
+function getTokenSymbolFromAddress(tokenAddress: string, chainId: number): string {
+ const token = getTokenByAddress(tokenAddress, chainId);
+ return token.symbol;
+}
+
export const useTransactionVote = (options?: UseTransactionVoteOptions) => {
const [isLoading, setIsLoading] = useState(false);
const [loadingState, setLoadingState] = useState("");
@@ -124,6 +211,7 @@ export const useTransactionVote = (options?: UseTransactionVoteOptions) => {
const { commitment } = useIdentityStore();
const { data: walletClient } = useWalletClient();
const metaMultiSigWallet = useMetaMultiSigWallet();
+ const { chainId: sourceChainId } = useNetworkTokens();
const { mutateAsync: approveApi } = useApproveTransaction();
const { mutateAsync: denyApi } = useDenyTransaction();
@@ -146,8 +234,8 @@ export const useTransactionVote = (options?: UseTransactionVoteOptions) => {
setIsLoading(true);
try {
- // 1. Build callData based on tx type
- const { to, value, callData } = buildTransactionParams(tx);
+ // 1. Build callData based on tx type (pass sourceChainId for cross-chain bridge reconstruction)
+ const { to, value, callData } = buildTransactionParams(tx, sourceChainId, tx.bridgeFee);
// 2. Get txHash from contract
const txHash = (await metaMultiSigWallet.read.getTransactionHash([
diff --git a/packages/nextjs/hooks/app/transaction/useTransferTransaction.ts b/packages/nextjs/hooks/app/transaction/useTransferTransaction.ts
index 4bdc4fd7..0d677a93 100644
--- a/packages/nextjs/hooks/app/transaction/useTransferTransaction.ts
+++ b/packages/nextjs/hooks/app/transaction/useTransferTransaction.ts
@@ -1,11 +1,28 @@
import { useState } from "react";
-import { ResolvedToken, TxType, ZERO_ADDRESS, encodeERC20Transfer, parseTokenAmount } from "@polypay/shared";
-import { parseEther } from "viem";
+import {
+ LZ_ENDPOINT_IDS,
+ OFT_ABI,
+ OP_BRIDGE_ADDRESSES,
+ ResolvedToken,
+ TxType,
+ ZERO_ADDRESS,
+ encodeApproveAndCall,
+ encodeBridgeETHTo,
+ encodeERC20Transfer,
+ encodeLzSend,
+ getBridgeContract,
+ getBridgeMechanism,
+ getOftCmd,
+ parseTokenAmount,
+ removeDust,
+} from "@polypay/shared";
+import { type Hex, createPublicClient, http, parseEther } from "viem";
import { useWalletClient } from "wagmi";
-import { useMetaMultiSigWallet } from "~~/hooks";
+import { useMetaMultiSigWallet, useNetworkTokens } from "~~/hooks";
import { useCreateTransaction, useReserveNonce } from "~~/hooks/api/useTransaction";
import { useGenerateProof } from "~~/hooks/app/useGenerateProof";
import { formatErrorMessage } from "~~/lib/form/utils";
+import scaffoldConfig from "~~/scaffold.config";
import { notification } from "~~/utils/scaffold-eth";
interface TransferParams {
@@ -13,6 +30,7 @@ interface TransferParams {
amount: string;
token: ResolvedToken;
contactId?: string | null;
+ destChainId?: number;
}
interface UseTransferTransactionOptions {
@@ -25,19 +43,21 @@ export const useTransferTransaction = (options?: UseTransferTransactionOptions)
const { data: walletClient } = useWalletClient();
const metaMultiSigWallet = useMetaMultiSigWallet();
+ const { chainId: sourceChainId } = useNetworkTokens();
const { mutateAsync: createTransaction } = useCreateTransaction();
const { mutateAsync: reserveNonce } = useReserveNonce();
const { generateProof } = useGenerateProof({
onLoadingStateChange: setLoadingState,
});
- const transfer = async ({ recipient, amount, token, contactId }: TransferParams) => {
+ const transfer = async ({ recipient, amount, token, contactId, destChainId }: TransferParams) => {
if (!walletClient || !metaMultiSigWallet) {
notification.error("Wallet not connected");
return;
}
const isNativeETH = token.address === ZERO_ADDRESS;
+ const isCrossChain = destChainId !== undefined && destChainId !== sourceChainId;
setIsLoading(true);
try {
@@ -55,9 +75,31 @@ export const useTransferTransaction = (options?: UseTransferTransactionOptions)
// 4. Calculate txHash (different for ETH vs ERC20)
let txHash: `0x${string}`;
+ let bridgeFee: string | undefined;
+ let bridgeMinAmount: string | undefined;
- if (isNativeETH) {
- // ETH: to = recipient, value = amount, data = 0x
+ if (isCrossChain) {
+ const bridgeResult = await buildBridgeParams({
+ recipient,
+ valueInSmallestUnit,
+ token,
+ isNativeETH,
+ sourceChainId,
+ destChainId,
+ walletAddress: metaMultiSigWallet.address,
+ setLoadingState,
+ });
+
+ bridgeFee = bridgeResult.bridgeFee;
+ bridgeMinAmount = bridgeResult.bridgeMinAmount;
+
+ txHash = (await metaMultiSigWallet.read.getTransactionHash([
+ BigInt(nonce),
+ bridgeResult.to as `0x${string}`,
+ BigInt(bridgeResult.value),
+ bridgeResult.data as `0x${string}`,
+ ])) as `0x${string}`;
+ } else if (isNativeETH) {
txHash = (await metaMultiSigWallet.read.getTransactionHash([
BigInt(nonce),
recipient as `0x${string}`,
@@ -89,6 +131,9 @@ export const useTransferTransaction = (options?: UseTransferTransactionOptions)
value: valueInSmallestUnit,
tokenAddress: isNativeETH ? undefined : token.address,
contactId: contactId || undefined,
+ destChainId: isCrossChain ? destChainId : undefined,
+ bridgeFee,
+ bridgeMinAmount,
proof: Array.from(proof),
publicInputs,
nullifier: nullifier.toString(),
@@ -97,7 +142,10 @@ export const useTransferTransaction = (options?: UseTransferTransactionOptions)
});
if (result) {
- notification.success("Transfer transaction created! Waiting for approvals.");
+ const msg = isCrossChain
+ ? "Cross-chain transfer created! Waiting for approvals."
+ : "Transfer transaction created! Waiting for approvals.";
+ notification.success(msg);
}
options?.onSuccess?.();
@@ -116,3 +164,130 @@ export const useTransferTransaction = (options?: UseTransferTransactionOptions)
loadingState,
};
};
+
+// ─── Bridge param builder (stateless helper) ───
+
+async function buildBridgeParams({
+ recipient,
+ valueInSmallestUnit,
+ token,
+ isNativeETH,
+ sourceChainId,
+ destChainId,
+ walletAddress,
+ setLoadingState,
+}: {
+ recipient: string;
+ valueInSmallestUnit: string;
+ token: ResolvedToken;
+ isNativeETH: boolean;
+ sourceChainId: number;
+ destChainId: number;
+ walletAddress: string;
+ setLoadingState: (s: string) => void;
+}): Promise<{ to: string; value: string; data: Hex; bridgeFee?: string; bridgeMinAmount?: string }> {
+ const tokenSymbol = isNativeETH ? "ETH" : token.symbol;
+ const mechanism = getBridgeMechanism(sourceChainId, destChainId, tokenSymbol);
+
+ if (!mechanism) {
+ throw new Error(`No bridge route for ${tokenSymbol} from ${sourceChainId} to ${destChainId}`);
+ }
+
+ const amount = BigInt(valueInSmallestUnit);
+
+ if (mechanism === "OP_STACK") {
+ const bridgeAddress = OP_BRIDGE_ADDRESSES[sourceChainId];
+ if (!bridgeAddress) throw new Error(`No OP bridge on chain ${sourceChainId}`);
+
+ return {
+ to: bridgeAddress,
+ value: amount.toString(),
+ data: encodeBridgeETHTo(recipient),
+ };
+ }
+
+ // LAYERZERO
+ const dstEid = LZ_ENDPOINT_IDS[destChainId];
+ if (!dstEid) throw new Error(`No LZ endpoint for chain ${destChainId}`);
+
+ const oftEntry = getBridgeContract(tokenSymbol, sourceChainId);
+ if (!oftEntry) throw new Error(`No OFT contract for ${tokenSymbol} on chain ${sourceChainId}`);
+
+ const chain = scaffoldConfig.targetNetworks.find(n => n.id === sourceChainId);
+ if (!chain) throw new Error(`Chain ${sourceChainId} not in target networks`);
+
+ const publicClient = createPublicClient({ chain, transport: http() });
+
+ const oftCmd = getOftCmd(oftEntry);
+ const toBytes32 = `0x${recipient.slice(2).padStart(64, "0")}` as `0x${string}`;
+
+ // For Stargate OFTs, call quoteOFT to get amountReceivedLD (accounts for protocol fee).
+ // For standard OFTs, removeDust is sufficient.
+ let minAmount: bigint;
+ if (oftEntry.stargate) {
+ setLoadingState("Quoting Stargate fee...");
+ const quoteResult = (await publicClient.readContract({
+ address: oftEntry.address as `0x${string}`,
+ abi: OFT_ABI,
+ functionName: "quoteOFT",
+ args: [
+ {
+ dstEid,
+ to: toBytes32,
+ amountLD: amount,
+ minAmountLD: 0n,
+ extraOptions: "0x" as Hex,
+ composeMsg: "0x" as Hex,
+ oftCmd: oftCmd as Hex,
+ },
+ ],
+ })) as [
+ { minAmountLD: bigint; maxAmountLD: bigint },
+ { feeAmountLD: bigint; description: string }[],
+ { amountSentLD: bigint; amountReceivedLD: bigint },
+ ];
+ minAmount = quoteResult[2].amountReceivedLD;
+ } else {
+ minAmount = removeDust(amount, token.decimals);
+ }
+
+ const sendParam = {
+ dstEid,
+ to: toBytes32,
+ amountLD: amount,
+ minAmountLD: minAmount,
+ extraOptions: "0x" as Hex,
+ composeMsg: "0x" as Hex,
+ oftCmd: oftCmd as Hex,
+ };
+
+ setLoadingState("Quoting LayerZero fee...");
+ const quotedFee = (await publicClient.readContract({
+ address: oftEntry.address as `0x${string}`,
+ abi: OFT_ABI,
+ functionName: "quoteSend",
+ args: [sendParam, false],
+ })) as { nativeFee: bigint; lzTokenFee: bigint };
+
+ const nativeFee = quotedFee.nativeFee;
+
+ const lzSendData = encodeLzSend(dstEid, recipient, amount, minAmount, nativeFee, walletAddress, oftCmd as Hex);
+
+ if (oftEntry.type === "adapter") {
+ return {
+ to: walletAddress,
+ value: "0",
+ data: encodeApproveAndCall(token.address, oftEntry.address, amount, oftEntry.address, nativeFee, lzSendData),
+ bridgeFee: nativeFee.toString(),
+ bridgeMinAmount: minAmount.toString(),
+ };
+ }
+
+ return {
+ to: oftEntry.address,
+ value: nativeFee.toString(),
+ data: lzSendData,
+ bridgeFee: nativeFee.toString(),
+ bridgeMinAmount: minAmount.toString(),
+ };
+}
diff --git a/packages/nextjs/utils/format.ts b/packages/nextjs/utils/format.ts
index ebba9d1a..b329bd45 100644
--- a/packages/nextjs/utils/format.ts
+++ b/packages/nextjs/utils/format.ts
@@ -1,4 +1,4 @@
-import { formatTokenAmount, getTokenByAddress } from "@polypay/shared";
+import { formatDisplayAmount, getTokenByAddress } from "@polypay/shared";
/**
* Format amount with token symbol
@@ -10,7 +10,7 @@ import { formatTokenAmount, getTokenByAddress } from "@polypay/shared";
export function formatAmount(amount: string, chainId: number, tokenAddress?: string | null): string {
try {
const token = getTokenByAddress(tokenAddress, chainId);
- const formatted = formatTokenAmount(amount, token.decimals);
+ const formatted = formatDisplayAmount(amount, token.decimals, token.symbol);
return `${formatted} ${token.symbol}`;
} catch {
return amount;
diff --git a/packages/shared/src/constants/bridge.ts b/packages/shared/src/constants/bridge.ts
new file mode 100644
index 00000000..8c6e8b99
--- /dev/null
+++ b/packages/shared/src/constants/bridge.ts
@@ -0,0 +1,177 @@
+// ─── Cross-chain version gate ───
+
+export const CROSS_CHAIN_MIN_CONTRACT_VERSION = 2;
+
+export function isCrossChainEnabled(contractVersion: number): boolean {
+ return contractVersion >= CROSS_CHAIN_MIN_CONTRACT_VERSION;
+}
+
+// Chain IDs (mirrored from token.ts for bridge-local use)
+const HORIZEN_MAINNET = 26514;
+const HORIZEN_TESTNET = 2651420;
+const BASE_MAINNET = 8453;
+const BASE_SEPOLIA = 84532;
+
+// Mainnet chain pairs
+const BASE_CHAINS = [BASE_MAINNET, BASE_SEPOLIA] as const;
+const HORIZEN_CHAINS = [HORIZEN_MAINNET, HORIZEN_TESTNET] as const;
+
+export type BridgeMechanism = "OP_STACK" | "LAYERZERO";
+
+// ─── LayerZero Endpoint IDs (NOT chain IDs) ───
+
+export const LZ_ENDPOINT_IDS: Record
= {
+ [BASE_MAINNET]: 30184,
+ [BASE_SEPOLIA]: 40245,
+ [HORIZEN_MAINNET]: 30399,
+ [HORIZEN_TESTNET]: 40435,
+};
+
+// ─── OP Stack Bridge (Base only, bridging TO Horizen) ───
+
+export const OP_BRIDGE_ADDRESSES: Record = {
+ [BASE_MAINNET]: "0xf4a6cc4171fda694439f856d912777aa6ab05369",
+ [BASE_SEPOLIA]: "0xc2ce54c609489c44fa46f00b034e53c3cd150eb8",
+};
+
+export const OP_BRIDGE_MIN_GAS_LIMIT = 200_000;
+
+// ─── OFT / Adapter addresses per chain per token ───
+
+export interface OftContractEntry {
+ type: "oft" | "adapter";
+ address: string;
+ stargate?: boolean;
+}
+
+// Stargate V2 uses taxi mode (non-empty oftCmd) for immediate delivery.
+// Bus mode (empty oftCmd) requires an active bus driver and may fail.
+export const STARGATE_TAXI_OFT_CMD = "0x01" as const;
+
+export function getOftCmd(entry: OftContractEntry | null): `0x${string}` {
+ return entry?.stargate ? STARGATE_TAXI_OFT_CMD : "0x";
+}
+
+export const BRIDGE_CONTRACTS: Record<
+ string,
+ Record
+> = {
+ ZEN: {
+ [BASE_MAINNET]: {
+ type: "adapter",
+ address: "0x57da2D504bf8b83Ef304759d9f2648522D7a9280",
+ },
+ [HORIZEN_MAINNET]: {
+ type: "oft",
+ address: "0x57da2D504bf8b83Ef304759d9f2648522D7a9280",
+ },
+ [BASE_SEPOLIA]: {
+ type: "adapter",
+ address: "0x2ead4B0beBD8e54F9B7cC1007DF4c44a27b9a339",
+ },
+ [HORIZEN_TESTNET]: {
+ type: "oft",
+ address: "0xb06EC4ce262D8dbDc24Fac87479A49A7DC4cFb87",
+ },
+ },
+ USDC: {
+ [BASE_MAINNET]: {
+ type: "adapter",
+ address: "0x27a16dc786820b16e5c9028b75b99f6f604b5d26",
+ stargate: true,
+ },
+ [HORIZEN_MAINNET]: {
+ type: "adapter",
+ address: "0x3a1293Bdb83bBbDd5Ebf4fAc96605aD2021BbC0f",
+ stargate: true,
+ },
+ // No testnet OFT contracts for USDC
+ },
+};
+
+// ─── Chain pairing ───
+
+function getPairedChainId(chainId: number): number | null {
+ if (chainId === BASE_MAINNET) return HORIZEN_MAINNET;
+ if (chainId === HORIZEN_MAINNET) return BASE_MAINNET;
+ if (chainId === BASE_SEPOLIA) return HORIZEN_TESTNET;
+ if (chainId === HORIZEN_TESTNET) return BASE_SEPOLIA;
+ return null;
+}
+
+function isBaseChain(chainId: number): boolean {
+ return (BASE_CHAINS as readonly number[]).includes(chainId);
+}
+
+function isTestnet(chainId: number): boolean {
+ return chainId === BASE_SEPOLIA || chainId === HORIZEN_TESTNET;
+}
+
+// ─── Route availability ───
+
+/**
+ * Returns chain IDs the user can transfer to for a given source chain + token.
+ * Always includes the source chain itself (same-chain transfer).
+ */
+export function getAvailableDestChains(
+ srcChainId: number,
+ tokenSymbol: string,
+): number[] {
+ const paired = getPairedChainId(srcChainId);
+ const result = [srcChainId];
+
+ if (!paired) return result;
+
+ if (tokenSymbol === "ETH") {
+ // ETH bridge only Base → Horizen (OP Stack). Horizen → Base excluded cause rollups mechanism.
+ if (isBaseChain(srcChainId)) {
+ result.push(paired);
+ }
+ return result;
+ }
+
+ if (tokenSymbol === "USDC") {
+ // USDC OFT only on mainnet
+ if (!isTestnet(srcChainId)) {
+ result.push(paired);
+ }
+ return result;
+ }
+
+ // ZEN: bidirectional on all environments
+ if (tokenSymbol === "ZEN") {
+ result.push(paired);
+ }
+
+ return result;
+}
+
+export function isBridgeAvailable(
+ srcChainId: number,
+ destChainId: number,
+ tokenSymbol: string,
+): boolean {
+ if (srcChainId === destChainId) return false;
+ return getAvailableDestChains(srcChainId, tokenSymbol).includes(destChainId);
+}
+
+export function getBridgeMechanism(
+ srcChainId: number,
+ destChainId: number,
+ tokenSymbol: string,
+): BridgeMechanism | null {
+ if (!isBridgeAvailable(srcChainId, destChainId, tokenSymbol)) return null;
+
+ if (tokenSymbol === "ETH") return "OP_STACK";
+ return "LAYERZERO";
+}
+
+/**
+ * Get the OFT/Adapter contract address for a token on a specific chain.
+ */
+export function getBridgeContract(
+ tokenSymbol: string,
+ chainId: number,
+): OftContractEntry | null {
+ return BRIDGE_CONTRACTS[tokenSymbol]?.[chainId] ?? null;
+}
diff --git a/packages/shared/src/constants/index.ts b/packages/shared/src/constants/index.ts
index e07a903a..93bfc6d0 100644
--- a/packages/shared/src/constants/index.ts
+++ b/packages/shared/src/constants/index.ts
@@ -2,3 +2,4 @@ export * from "./socket-events";
export * from "./room";
export * from "./token";
export * from "./campaign";
+export * from "./bridge";
diff --git a/packages/shared/src/constants/token.ts b/packages/shared/src/constants/token.ts
index 2cf7e3ea..ccab083a 100644
--- a/packages/shared/src/constants/token.ts
+++ b/packages/shared/src/constants/token.ts
@@ -39,7 +39,7 @@ export const NATIVE_ETH: Token = {
export const ZEN_TOKEN: Token = {
addresses: {
- [HORIZEN_TESTNET]: "0x4b36cb6E7c257E9aA246122a997be0F7Dc1eFCd1",
+ [HORIZEN_TESTNET]: "0xb06EC4ce262D8dbDc24Fac87479A49A7DC4cFb87",
[HORIZEN_MAINNET]: "0x57da2D504bf8b83Ef304759d9f2648522D7a9280",
[BASE_MAINNET]: "0xf43eB8De897Fbc7F2502483B2Bef7Bb9EA179229",
[BASE_SEPOLIA]: "0x107fdE93838e3404934877935993782F977324BB",
@@ -166,6 +166,44 @@ export function formatTokenAmount(amount: string, decimals: number): string {
return `${intPart}.${decStr}`;
}
+const STABLECOINS = ["USDC", "USDT"];
+
+function getDisplayPrecision(value: number, tokenSymbol?: string): number {
+ if (tokenSymbol && STABLECOINS.includes(tokenSymbol.toUpperCase())) return 2;
+ if (value >= 1000) return 2;
+ if (value >= 100) return 4;
+ if (value >= 10) return 5;
+ if (value >= 1) return 6;
+ return 8;
+}
+
+export function formatDisplayValue(
+ value: string,
+ tokenSymbol?: string,
+): string {
+ const num = parseFloat(value);
+ if (isNaN(num) || num === 0) return "0";
+
+ const isStable =
+ tokenSymbol && STABLECOINS.includes(tokenSymbol.toUpperCase());
+ const precision = getDisplayPrecision(num, tokenSymbol);
+
+ if (num > 0 && isStable && num < 0.01) return "< 0.01";
+ if (num > 0 && num < 1e-8) return "< 0.00000001";
+
+ const formatted = num.toFixed(precision);
+ return formatted.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
+}
+
+export function formatDisplayAmount(
+ amount: string,
+ decimals: number,
+ tokenSymbol?: string,
+): string {
+ const full = formatTokenAmount(amount, decimals);
+ return formatDisplayValue(full, tokenSymbol);
+}
+
export function parseTokenAmount(amount: string, decimals: number): string {
const [intPart, decPart = ""] = amount.split(".");
const paddedDec = decPart.padEnd(decimals, "0").slice(0, decimals);
diff --git a/packages/shared/src/contracts/MetaMultiSigWallet.ts b/packages/shared/src/contracts/MetaMultiSigWallet.ts
index 1a3e8d9b..9c54e96e 100644
--- a/packages/shared/src/contracts/MetaMultiSigWallet.ts
+++ b/packages/shared/src/contracts/MetaMultiSigWallet.ts
@@ -169,6 +169,62 @@ export const METAMULTISIG_ABI = [
stateMutability: "nonpayable",
type: "function",
},
+ {
+ inputs: [
+ {
+ internalType: "address",
+ name: "token",
+ type: "address",
+ },
+ {
+ internalType: "address",
+ name: "spender",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "approveAmount",
+ type: "uint256",
+ },
+ {
+ internalType: "address",
+ name: "callTarget",
+ type: "address",
+ },
+ {
+ internalType: "uint256",
+ name: "callValue",
+ type: "uint256",
+ },
+ {
+ internalType: "bytes",
+ name: "callData",
+ type: "bytes",
+ },
+ ],
+ name: "approveAndCall",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ internalType: "uint256[]",
+ name: "newCommitments",
+ type: "uint256[]",
+ },
+ {
+ internalType: "uint256",
+ name: "newSigRequired",
+ type: "uint256",
+ },
+ ],
+ name: "addSigners",
+ outputs: [],
+ stateMutability: "nonpayable",
+ type: "function",
+ },
{
inputs: [
{
@@ -518,4 +574,4 @@ export const METAMULTISIG_ABI = [
// ============ Bytecode ============
export const METAMULTISIG_BYTECODE =
- "0x60c06040523480156200001157600080fd5b50604051620024a0380380620024a08339810160408190526200003491620002db565b6001600160a01b038516620000905760405162461bcd60e51b815260206004820152601360248201527f496e76616c6964207a6b7620616464726573730000000000000000000000000060448201526064015b60405180910390fd5b60008111620000e25760405162461bcd60e51b815260206004820152601e60248201527f4d757374206265206e6f6e2d7a65726f20736967732072657175697265640000604482015260640162000087565b6000825111620001355760405162461bcd60e51b815260206004820152601660248201527f4e656564206174206c656173742031207369676e657200000000000000000000604482015260640162000087565b8151811115620001885760405162461bcd60e51b815260206004820152601660248201527f5369677320726571756972656420746f6f206869676800000000000000000000604482015260640162000087565b6001600160a01b03851660805260a0849052600083815560018290555b8251811015620002b957828181518110620001c457620001c4620003e6565b6020026020010151600003620002125760405162461bcd60e51b8152602060048201526012602482015271125b9d985b1a590818dbdb5b5a5d1b595b9d60721b604482015260640162000087565b6002838281518110620002295762000229620003e6565b6020908102919091018101518254600181018455600093845291909220015582518390829081106200025f576200025f620003e6565b60200260200101517f33129f9e36e06e860f22ce83276273f4d83d31e9fd4a0d3a4dbac9d2da4f9d0d60016040516200029c911515815260200190565b60405180910390a280620002b081620003fc565b915050620001a5565b50505050505062000424565b634e487b7160e01b600052604160045260246000fd5b600080600080600060a08688031215620002f457600080fd5b85516001600160a01b03811681146200030c57600080fd5b602087810151604089015160608a01519398509096509450906001600160401b03808211156200033b57600080fd5b818901915089601f8301126200035057600080fd5b815181811115620003655762000365620002c5565b8060051b604051601f19603f830116810181811085821117156200038d576200038d620002c5565b60405291825284820192508381018501918c831115620003ac57600080fd5b938501935b82851015620003cc57845184529385019392850192620003b1565b809750505050505050608086015190509295509295909350565b634e487b7160e01b600052603260045260246000fd5b6000600182016200041d57634e487b7160e01b600052601160045260246000fd5b5060010190565b60805160a0516120486200045860003960008181610221015261167b0152600081816102d5015261173301526120486000f3fe6080604052600436106101235760003560e01c80639a8a0592116100a0578063aad2406111610064578063aad24061146103d1578063ce757d2914610401578063e4cf5a2c14610417578063eaaba5591461044b578063f1ea66d41461046b57600080fd5b80639a8a05921461032f5780639e4e731814610345578063a0c1deb41461035a578063a8898a201461036f578063a8d2c852146103b157600080fd5b8063545a4a3c116100e7578063545a4a3c1461024357806364451212146102635780636717e41c146102835780637ee68373146102c357806388d695b21461030f57600080fd5b80631b108c07146101695780633034a7421461019f5780634791ca34146101c157806349ce8997146101e15780634fe840f51461020f57600080fd5b36610164576040805134815247602082015233917f90890809c654f11d6e72a28fa60149770a0d11ec6c92319d6ceb2bb0a4ea1a15910160405180910390a2005b600080fd5b34801561017557600080fd5b5061018961018436600461185a565b61048d565b604051610196919061196c565b60405180910390f35b3480156101ab57600080fd5b506101bf6101ba366004611986565b610835565b005b3480156101cd57600080fd5b506101bf6101dc36600461199f565b61089b565b3480156101ed57600080fd5b506102016101fc366004611986565b610b29565b604051908152602001610196565b34801561021b57600080fd5b506102017f000000000000000000000000000000000000000000000000000000000000000081565b34801561024f57600080fd5b5061020161025e366004611a01565b610b4a565b34801561026f57600080fd5b506101bf61027e366004611ad6565b610b87565b34801561028f57600080fd5b506102b361029e366004611986565b60036020526000908152604090205460ff1681565b6040519015158152602001610196565b3480156102cf57600080fd5b506102f77f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610196565b34801561031b57600080fd5b506101bf61032a366004611b70565b610f44565b34801561033b57600080fd5b5061020160005481565b34801561035157600080fd5b50610201611128565b34801561036657600080fd5b50600254610201565b34801561037b57600080fd5b5061020160405169756c747261706c6f6e6b60b01b6020820152602a016040516020818303038152906040528051906020012081565b3480156103bd57600080fd5b506101bf6103cc36600461199f565b611188565b3480156103dd57600080fd5b506102b36103ec366004611986565b60046020526000908152604090205460ff1681565b34801561040d57600080fd5b5061020160015481565b34801561042357600080fd5b506102017f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000181565b34801561045757600080fd5b50610201610466366004611bdc565b61145e565b34801561047757600080fd5b5061048061154f565b6040516101969190611bfe565b60008781526003602052604090205460609060ff16156104e95760405162461bcd60e51b8152602060048201526012602482015271139bdb98d948185b1c9958591e481d5cd95960721b60448201526064015b60405180910390fd5b60015482101561052f5760405162461bcd60e51b81526020600482015260116024820152704e6f7420656e6f7567682070726f6f667360781b60448201526064016104e0565b6000805460405161054e9130918c908c908c908c908c90602001611c42565b60405160208183030381529060405280519060200120905060005b8381101561072f576004600086868481811061058757610587611c94565b90506020028101906105999190611caa565b60209081013582528101919091526040016000205460ff16156105f75760405162461bcd60e51b8152602060048201526016602482015275139d5b1b1a599a595c88185b1c9958591e481d5cd95960521b60448201526064016104e0565b61062485858381811061060c5761060c611c94565b905060200281019061061e9190611caa565b356115a7565b6106675760405162461bcd60e51b81526020600482015260146024820152732737ba10309031bab93932b73a1039b4b3b732b960611b60448201526064016104e0565b6106948286868481811061067d5761067d611c94565b905060200281019061068f9190611caa565b6115fd565b6106d05760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b210383937b7b360991b60448201526064016104e0565b6001600460008787858181106106e8576106e8611c94565b90506020028101906106fa9190611caa565b6020908101358252810191909152604001600020805460ff19169115159190911790558061072781611ce0565b915050610569565b50600089815260036020526040808220805460ff191660011790555181906001600160a01b038b16908a90610767908b908b90611cf9565b60006040518083038185875af1925050503d80600081146107a4576040519150601f19603f3d011682016040523d82523d6000602084013e6107a9565b606091505b5091509150816107e75760405162461bcd60e51b8152602060048201526009602482015268151e0819985a5b195960ba1b60448201526064016104e0565b8a7f1654479f61781d185c419742a20e599a227ae1840317c8a74ceda5eb6166b8268b8b8b8b8660405161081f959493929190611d09565b60405180910390a29a9950505050505050505050565b3330146108545760405162461bcd60e51b81526004016104e090611d6a565b600081116108745760405162461bcd60e51b81526004016104e090611d8c565b6002548111156108965760405162461bcd60e51b81526004016104e090611dc3565b600155565b3330146108ba5760405162461bcd60e51b81526004016104e090611d6a565b816108f55760405162461bcd60e51b815260206004820152600b60248201526a456d70747920617272617960a81b60448201526064016104e0565b60025482106109465760405162461bcd60e51b815260206004820152601960248201527f43616e6e6f742072656d6f766520616c6c207369676e6572730000000000000060448201526064016104e0565b600081116109665760405162461bcd60e51b81526004016104e090611d8c565b600254610974908390611df3565b8111156109935760405162461bcd60e51b81526004016104e090611dc3565b60005b82811015610b21576000805b600254811015610ac9578585848181106109be576109be611c94565b90506020020135600282815481106109d8576109d8611c94565b906000526020600020015403610ab757600280546109f890600190611df3565b81548110610a0857610a08611c94565b906000526020600020015460028281548110610a2657610a26611c94565b6000918252602090912001556002805480610a4357610a43611e06565b6001900381819060005260206000200160009055905560019150858584818110610a6f57610a6f611c94565b905060200201357f33129f9e36e06e860f22ce83276273f4d83d31e9fd4a0d3a4dbac9d2da4f9d0d6000604051610aaa911515815260200190565b60405180910390a2610ac9565b80610ac181611ce0565b9150506109a2565b5080610b0e5760405162461bcd60e51b815260206004820152601460248201527310dbdb5b5a5d1b595b9d081b9bdd08199bdd5b9960621b60448201526064016104e0565b5080610b1981611ce0565b915050610996565b506001555050565b60028181548110610b3957600080fd5b600091825260209091200154905081565b60008054604051610b679130918890889088908890602001611e1c565b604051602081830303815290604052805190602001209050949350505050565b333014610ba65760405162461bcd60e51b81526004016104e090611d6a565b848314610bc55760405162461bcd60e51b81526004016104e090611e77565b848114610be45760405162461bcd60e51b81526004016104e090611e77565b84610c1f5760405162461bcd60e51b815260206004820152600b60248201526a08adae0e8f240c4c2e8c6d60ab1b60448201526064016104e0565b60005b85811015610f3b576000878783818110610c3e57610c3e611c94565b9050602002016020810190610c539190611ea0565b6001600160a01b031603610c9d5760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081c9958da5c1a595b9d607a1b60448201526064016104e0565b6000838383818110610cb157610cb1611c94565b9050602002016020810190610cc69190611ea0565b6001600160a01b031603610db1576000878783818110610ce857610ce8611c94565b9050602002016020810190610cfd9190611ea0565b6001600160a01b0316868684818110610d1857610d18611c94565b9050602002013560405160006040518083038185875af1925050503d8060008114610d5f576040519150601f19603f3d011682016040523d82523d6000602084013e610d64565b606091505b5050905080610dab5760405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b60448201526064016104e0565b50610f29565b600080848484818110610dc657610dc6611c94565b9050602002016020810190610ddb9190611ea0565b6001600160a01b0316898985818110610df657610df6611c94565b9050602002016020810190610e0b9190611ea0565b888886818110610e1d57610e1d611c94565b6040516001600160a01b039094166024850152602002919091013560448301525060640160408051601f198184030181529181526020820180516001600160e01b031663a9059cbb60e01b17905251610e769190611ebb565b6000604051808303816000865af19150503d8060008114610eb3576040519150601f19603f3d011682016040523d82523d6000602084013e610eb8565b606091505b5091509150818015610ee2575080511580610ee2575080806020019051810190610ee29190611ecd565b610f265760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b60448201526064016104e0565b50505b80610f3381611ce0565b915050610c22565b50505050505050565b333014610f635760405162461bcd60e51b81526004016104e090611d6a565b828114610f825760405162461bcd60e51b81526004016104e090611e77565b82610fbd5760405162461bcd60e51b815260206004820152600b60248201526a08adae0e8f240c4c2e8c6d60ab1b60448201526064016104e0565b60005b83811015611121576000858583818110610fdc57610fdc611c94565b9050602002016020810190610ff19190611ea0565b6001600160a01b03160361103b5760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081c9958da5c1a595b9d607a1b60448201526064016104e0565b600085858381811061104f5761104f611c94565b90506020020160208101906110649190611ea0565b6001600160a01b031684848481811061107f5761107f611c94565b9050602002013560405160006040518083038185875af1925050503d80600081146110c6576040519150601f19603f3d011682016040523d82523d6000602084013e6110cb565b606091505b505090508061110e5760405162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b60448201526064016104e0565b508061111981611ce0565b915050610fc0565b5050505050565b604080516000815260208101918290526002916111459190611ebb565b602060405180830381855afa158015611162573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906111859190611eef565b81565b3330146111a75760405162461bcd60e51b81526004016104e090611d6a565b816111e25760405162461bcd60e51b815260206004820152600b60248201526a456d70747920617272617960a81b60448201526064016104e0565b600081116112025760405162461bcd60e51b81526004016104e090611d8c565b600254611210908390611f08565b81111561122f5760405162461bcd60e51b81526004016104e090611dc3565b60005b82811015610b215783838281811061124c5761124c611c94565b905060200201356000036112975760405162461bcd60e51b8152602060048201526012602482015271125b9d985b1a590818dbdb5b5a5d1b595b9d60721b60448201526064016104e0565b60005b60025481101561132e578484838181106112b6576112b6611c94565b90506020020135600282815481106112d0576112d0611c94565b90600052602060002001540361131c5760405162461bcd60e51b8152602060048201526011602482015270436f6d6d69746d656e742065786973747360781b60448201526064016104e0565b8061132681611ce0565b91505061129a565b5060005b818110156113c05784848381811061134c5761134c611c94565b9050602002013585858381811061136557611365611c94565b90506020020135036113ae5760405162461bcd60e51b8152602060048201526012602482015271111d5c1b1a58d85d19481a5b881a5b9c1d5d60721b60448201526064016104e0565b806113b881611ce0565b915050611332565b5060028484838181106113d5576113d5611c94565b8354600181018555600094855260209485902091909402929092013591909201555083838281811061140957611409611c94565b905060200201357f33129f9e36e06e860f22ce83276273f4d83d31e9fd4a0d3a4dbac9d2da4f9d0d6001604051611444911515815260200190565b60405180910390a28061145681611ce0565b915050611232565b60008061148b7f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000185611f1b565b905060006114b97f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000185611f1b565b604080518082018252848152602081018390529051632b0aac7f60e11b8152919250733333333C0A88F9BE4fd23ed0536F9B6c427e3B939163561558fe9161150391600401611f3d565b602060405180830381865af4158015611520573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115449190611eef565b925050505b92915050565b6060600280548060200260200160405190810160405280929190818152602001828054801561159d57602002820191906000526020600020905b815481526020019060010190808311611589575b5050505050905090565b6000805b6002548110156115f45782600282815481106115c9576115c9611c94565b9060005260206000200154036115e25750600192915050565b806115ec81611ce0565b9150506115ab565b50600092915050565b60008061160b84600161145e565b604080516020808201849052863582840152868101356060808401919091528351808403909101815260808301845269756c747261706c6f6e6b60b01b60a08401528351608a81850301815260aa840180865281519190930120600080845260ca909401948590529495509391927f0000000000000000000000000000000000000000000000000000000000000000916002916116a89190611ebb565b602060405180830381855afa1580156116c5573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906116e89190611eef565b8480519060200120604051602001611719949392919093845260208401929092526040830152606082015260800190565b6040516020818303038152906040528051906020012090507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663a78f9e36866060013587604001358489806080019061177b9190611f6e565b8b60a001358c60c001356040518863ffffffff1660e01b81526004016117a79796959493929190611fb8565b602060405180830381865afa1580156117c4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117e89190611ecd565b9695505050505050565b80356001600160a01b038116811461180957600080fd5b919050565b60008083601f84011261182057600080fd5b50813567ffffffffffffffff81111561183857600080fd5b6020830191508360208260051b850101111561185357600080fd5b9250929050565b600080600080600080600060a0888a03121561187557600080fd5b87359650611885602089016117f2565b955060408801359450606088013567ffffffffffffffff808211156118a957600080fd5b818a0191508a601f8301126118bd57600080fd5b8135818111156118cc57600080fd5b8b60208285010111156118de57600080fd5b6020830196508095505060808a01359150808211156118fc57600080fd5b506119098a828b0161180e565b989b979a50959850939692959293505050565b60005b8381101561193757818101518382015260200161191f565b50506000910152565b6000815180845261195881602086016020860161191c565b601f01601f19169290920160200192915050565b60208152600061197f6020830184611940565b9392505050565b60006020828403121561199857600080fd5b5035919050565b6000806000604084860312156119b457600080fd5b833567ffffffffffffffff8111156119cb57600080fd5b6119d78682870161180e565b909790965060209590950135949350505050565b634e487b7160e01b600052604160045260246000fd5b60008060008060808587031215611a1757600080fd5b84359350611a27602086016117f2565b925060408501359150606085013567ffffffffffffffff80821115611a4b57600080fd5b818701915087601f830112611a5f57600080fd5b813581811115611a7157611a716119eb565b604051601f8201601f19908116603f01168101908382118183101715611a9957611a996119eb565b816040528281528a6020848701011115611ab257600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b60008060008060008060608789031215611aef57600080fd5b863567ffffffffffffffff80821115611b0757600080fd5b611b138a838b0161180e565b90985096506020890135915080821115611b2c57600080fd5b611b388a838b0161180e565b90965094506040890135915080821115611b5157600080fd5b50611b5e89828a0161180e565b979a9699509497509295939492505050565b60008060008060408587031215611b8657600080fd5b843567ffffffffffffffff80821115611b9e57600080fd5b611baa8883890161180e565b90965094506020870135915080821115611bc357600080fd5b50611bd08782880161180e565b95989497509550505050565b60008060408385031215611bef57600080fd5b50508035926020909101359150565b6020808252825182820181905260009190848201906040850190845b81811015611c3657835183529284019291840191600101611c1a565b50909695505050505050565b60006bffffffffffffffffffffffff19808a60601b168352886014840152876034840152808760601b166054840152508460688301528284608884013750600091016088019081529695505050505050565b634e487b7160e01b600052603260045260246000fd5b6000823560de19833603018112611cc057600080fd5b9190910192915050565b634e487b7160e01b600052601160045260246000fd5b600060018201611cf257611cf2611cca565b5060010190565b8183823760009101908152919050565b6001600160a01b0386168152602081018590526080604082018190528101839052828460a0830137600060a084830101526000601f19601f850116820160a0838203016060840152611d5e60a0820185611940565b98975050505050505050565b6020808252600890820152672737ba1029b2b63360c11b604082015260600190565b6020808252601e908201527f4d757374206265206e6f6e2d7a65726f20736967732072657175697265640000604082015260600190565b6020808252601690820152750a6d2cee640e4cae2ead2e4cac840e8dede40d0d2ced60531b604082015260600190565b8181038181111561154957611549611cca565b634e487b7160e01b600052603160045260246000fd5b60006bffffffffffffffffffffffff19808960601b168352876014840152866034840152808660601b166054840152508360688301528251611e6581608885016020870161191c565b91909101608801979650505050505050565b6020808252600f908201526e098cadccee8d040dad2e6dac2e8c6d608b1b604082015260600190565b600060208284031215611eb257600080fd5b61197f826117f2565b60008251611cc081846020870161191c565b600060208284031215611edf57600080fd5b8151801515811461197f57600080fd5b600060208284031215611f0157600080fd5b5051919050565b8082018082111561154957611549611cca565b600082611f3857634e487b7160e01b600052601260045260246000fd5b500690565b60408101818360005b6002811015611f65578151835260209283019290910190600101611f46565b50505092915050565b6000808335601e19843603018112611f8557600080fd5b83018035915067ffffffffffffffff821115611fa057600080fd5b6020019150600581901b360382131561185357600080fd5b87815286602082015285604082015260c060608201528360c0820152600060018060fb1b03851115611fe957600080fd5b8460051b808760e085013760808301949094525060a08101919091520160e0019594505050505056fea2646970667358221220925328ba1cb1d89f7ed11053888b9080bbb4027159fb1003d3575f3e6578fd1464736f6c63430008140033";
+ "0x60c06040523480156200001157600080fd5b5060405162002721380380620027218339810160408190526200003491620002db565b6001600160a01b038516620000905760405162461bcd60e51b815260206004820152601360248201527f496e76616c6964207a6b7620616464726573730000000000000000000000000060448201526064015b60405180910390fd5b60008111620000e25760405162461bcd60e51b815260206004820152601e60248201527f4d757374206265206e6f6e2d7a65726f20736967732072657175697265640000604482015260640162000087565b6000825111620001355760405162461bcd60e51b815260206004820152601660248201527f4e656564206174206c656173742031207369676e657200000000000000000000604482015260640162000087565b8151811115620001885760405162461bcd60e51b815260206004820152601660248201527f5369677320726571756972656420746f6f206869676800000000000000000000604482015260640162000087565b6001600160a01b03851660805260a0849052600083815560018290555b8251811015620002b957828181518110620001c457620001c4620003e6565b6020026020010151600003620002125760405162461bcd60e51b8152602060048201526012602482015271125b9d985b1a590818dbdb5b5a5d1b595b9d60721b604482015260640162000087565b6002838281518110620002295762000229620003e6565b6020908102919091018101518254600181018455600093845291909220015582518390829081106200025f576200025f620003e6565b60200260200101517f33129f9e36e06e860f22ce83276273f4d83d31e9fd4a0d3a4dbac9d2da4f9d0d60016040516200029c911515815260200190565b60405180910390a280620002b081620003fc565b915050620001a5565b50505050505062000424565b634e487b7160e01b600052604160045260246000fd5b600080600080600060a08688031215620002f457600080fd5b85516001600160a01b03811681146200030c57600080fd5b602087810151604089015160608a01519398509096509450906001600160401b03808211156200033b57600080fd5b818901915089601f8301126200035057600080fd5b815181811115620003655762000365620002c5565b8060051b604051601f19603f830116810181811085821117156200038d576200038d620002c5565b60405291825284820192508381018501918c831115620003ac57600080fd5b938501935b82851015620003cc57845184529385019392850192620003b1565b809750505050505050608086015190509295509295909350565b634e487b7160e01b600052603260045260246000fd5b6000600182016200041d57634e487b7160e01b600052601160045260246000fd5b5060010190565b60805160a0516122c9620004586000396000818161024c0152611872015260008181610300015261192a01526122c96000f3fe60806040526004361061012e5760003560e01c806388d695b2116100ab578063a8d2c8521161006f578063a8d2c852146103dc578063aad24061146103fc578063ce757d291461042c578063e4cf5a2c14610442578063eaaba55914610476578063f1ea66d41461049657600080fd5b806388d695b21461033a5780639a8a05921461035a5780639e4e731814610370578063a0c1deb414610385578063a8898a201461039a57600080fd5b80634fe840f5116100f25780634fe840f51461023a578063545a4a3c1461026e578063644512121461028e5780636717e41c146102ae5780637ee68373146102ee57600080fd5b80631b108c07146101745780633034a742146101aa5780633a624581146101cc5780634791ca34146101ec57806349ce89971461020c57600080fd5b3661016f576040805134815247602082015233917f90890809c654f11d6e72a28fa60149770a0d11ec6c92319d6ceb2bb0a4ea1a15910160405180910390a2005b600080fd5b34801561018057600080fd5b5061019461018f366004611a93565b6104b8565b6040516101a19190611b77565b60405180910390f35b3480156101b657600080fd5b506101ca6101c5366004611b91565b610860565b005b3480156101d857600080fd5b506101ca6101e7366004611baa565b6108c6565b3480156101f857600080fd5b506101ca610207366004611c20565b610a92565b34801561021857600080fd5b5061022c610227366004611b91565b610d20565b6040519081526020016101a1565b34801561024657600080fd5b5061022c7f000000000000000000000000000000000000000000000000000000000000000081565b34801561027a57600080fd5b5061022c610289366004611c82565b610d41565b34801561029a57600080fd5b506101ca6102a9366004611d57565b610d7e565b3480156102ba57600080fd5b506102de6102c9366004611b91565b60036020526000908152604090205460ff1681565b60405190151581526020016101a1565b3480156102fa57600080fd5b506103227f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016101a1565b34801561034657600080fd5b506101ca610355366004611df1565b61113b565b34801561036657600080fd5b5061022c60005481565b34801561037c57600080fd5b5061022c61131f565b34801561039157600080fd5b5060025461022c565b3480156103a657600080fd5b5061022c60405169756c747261706c6f6e6b60b01b6020820152602a016040516020818303038152906040528051906020012081565b3480156103e857600080fd5b506101ca6103f7366004611c20565b61137f565b34801561040857600080fd5b506102de610417366004611b91565b60046020526000908152604090205460ff1681565b34801561043857600080fd5b5061022c60015481565b34801561044e57600080fd5b5061022c7f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f000000181565b34801561048257600080fd5b5061022c610491366004611e5d565b611655565b3480156104a257600080fd5b506104ab611746565b6040516101a19190611e7f565b60008781526003602052604090205460609060ff16156105145760405162461bcd60e51b8152602060048201526012602482015271139bdb98d948185b1c9958591e481d5cd95960721b60448201526064015b60405180910390fd5b60015482101561055a5760405162461bcd60e51b81526020600482015260116024820152704e6f7420656e6f7567682070726f6f667360781b604482015260640161050b565b600080546040516105799130918c908c908c908c908c90602001611ec3565b60405160208183030381529060405280519060200120905060005b8381101561075a57600460008686848181106105b2576105b2611f15565b90506020028101906105c49190611f2b565b60209081013582528101919091526040016000205460ff16156106225760405162461bcd60e51b8152602060048201526016602482015275139d5b1b1a599a595c88185b1c9958591e481d5cd95960521b604482015260640161050b565b61064f85858381811061063757610637611f15565b90506020028101906106499190611f2b565b3561179e565b6106925760405162461bcd60e51b81526020600482015260146024820152732737ba10309031bab93932b73a1039b4b3b732b960611b604482015260640161050b565b6106bf828686848181106106a8576106a8611f15565b90506020028101906106ba9190611f2b565b6117f4565b6106fb5760405162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b210383937b7b360991b604482015260640161050b565b60016004600087878581811061071357610713611f15565b90506020028101906107259190611f2b565b6020908101358252810191909152604001600020805460ff19169115159190911790558061075281611f61565b915050610594565b50600089815260036020526040808220805460ff191660011790555181906001600160a01b038b16908a90610792908b908b90611f7a565b60006040518083038185875af1925050503d80600081146107cf576040519150601f19603f3d011682016040523d82523d6000602084013e6107d4565b606091505b5091509150816108125760405162461bcd60e51b8152602060048201526009602482015268151e0819985a5b195960ba1b604482015260640161050b565b8a7f1654479f61781d185c419742a20e599a227ae1840317c8a74ceda5eb6166b8268b8b8b8b8660405161084a959493929190611f8a565b60405180910390a29a9950505050505050505050565b33301461087f5760405162461bcd60e51b815260040161050b90611feb565b6000811161089f5760405162461bcd60e51b815260040161050b9061200d565b6002548111156108c15760405162461bcd60e51b815260040161050b90612044565b600155565b3330146108e55760405162461bcd60e51b815260040161050b90611feb565b6040516001600160a01b0387811660248301526044820187905260009182918a169060640160408051601f198184030181529181526020820180516001600160e01b031663095ea7b360e01b1790525161093f9190612074565b6000604051808303816000865af19150503d806000811461097c576040519150601f19603f3d011682016040523d82523d6000602084013e610981565b606091505b50915091508180156109ab5750805115806109ab5750808060200190518101906109ab9190612086565b6109e85760405162461bcd60e51b815260206004820152600e60248201526d105c1c1c9bdd994819985a5b195960921b604482015260640161050b565b6000866001600160a01b0316868686604051610a05929190611f7a565b60006040518083038185875af1925050503d8060008114610a42576040519150601f19603f3d011682016040523d82523d6000602084013e610a47565b606091505b5050905080610a865760405162461bcd60e51b815260206004820152600b60248201526a10d85b1b0819985a5b195960aa1b604482015260640161050b565b50505050505050505050565b333014610ab15760405162461bcd60e51b815260040161050b90611feb565b81610aec5760405162461bcd60e51b815260206004820152600b60248201526a456d70747920617272617960a81b604482015260640161050b565b6002548210610b3d5760405162461bcd60e51b815260206004820152601960248201527f43616e6e6f742072656d6f766520616c6c207369676e65727300000000000000604482015260640161050b565b60008111610b5d5760405162461bcd60e51b815260040161050b9061200d565b600254610b6b9083906120a8565b811115610b8a5760405162461bcd60e51b815260040161050b90612044565b60005b82811015610d18576000805b600254811015610cc057858584818110610bb557610bb5611f15565b9050602002013560028281548110610bcf57610bcf611f15565b906000526020600020015403610cae5760028054610bef906001906120a8565b81548110610bff57610bff611f15565b906000526020600020015460028281548110610c1d57610c1d611f15565b6000918252602090912001556002805480610c3a57610c3a6120bb565b6001900381819060005260206000200160009055905560019150858584818110610c6657610c66611f15565b905060200201357f33129f9e36e06e860f22ce83276273f4d83d31e9fd4a0d3a4dbac9d2da4f9d0d6000604051610ca1911515815260200190565b60405180910390a2610cc0565b80610cb881611f61565b915050610b99565b5080610d055760405162461bcd60e51b815260206004820152601460248201527310dbdb5b5a5d1b595b9d081b9bdd08199bdd5b9960621b604482015260640161050b565b5080610d1081611f61565b915050610b8d565b506001555050565b60028181548110610d3057600080fd5b600091825260209091200154905081565b60008054604051610d5e91309188908890889088906020016120d1565b604051602081830303815290604052805190602001209050949350505050565b333014610d9d5760405162461bcd60e51b815260040161050b90611feb565b848314610dbc5760405162461bcd60e51b815260040161050b9061212c565b848114610ddb5760405162461bcd60e51b815260040161050b9061212c565b84610e165760405162461bcd60e51b815260206004820152600b60248201526a08adae0e8f240c4c2e8c6d60ab1b604482015260640161050b565b60005b85811015611132576000878783818110610e3557610e35611f15565b9050602002016020810190610e4a9190612155565b6001600160a01b031603610e945760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081c9958da5c1a595b9d607a1b604482015260640161050b565b6000838383818110610ea857610ea8611f15565b9050602002016020810190610ebd9190612155565b6001600160a01b031603610fa8576000878783818110610edf57610edf611f15565b9050602002016020810190610ef49190612155565b6001600160a01b0316868684818110610f0f57610f0f611f15565b9050602002013560405160006040518083038185875af1925050503d8060008114610f56576040519150601f19603f3d011682016040523d82523d6000602084013e610f5b565b606091505b5050905080610fa25760405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b604482015260640161050b565b50611120565b600080848484818110610fbd57610fbd611f15565b9050602002016020810190610fd29190612155565b6001600160a01b0316898985818110610fed57610fed611f15565b90506020020160208101906110029190612155565b88888681811061101457611014611f15565b6040516001600160a01b039094166024850152602002919091013560448301525060640160408051601f198184030181529181526020820180516001600160e01b031663a9059cbb60e01b1790525161106d9190612074565b6000604051808303816000865af19150503d80600081146110aa576040519150601f19603f3d011682016040523d82523d6000602084013e6110af565b606091505b50915091508180156110d95750805115806110d95750808060200190518101906110d99190612086565b61111d5760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b604482015260640161050b565b50505b8061112a81611f61565b915050610e19565b50505050505050565b33301461115a5760405162461bcd60e51b815260040161050b90611feb565b8281146111795760405162461bcd60e51b815260040161050b9061212c565b826111b45760405162461bcd60e51b815260206004820152600b60248201526a08adae0e8f240c4c2e8c6d60ab1b604482015260640161050b565b60005b838110156113185760008585838181106111d3576111d3611f15565b90506020020160208101906111e89190612155565b6001600160a01b0316036112325760405162461bcd60e51b8152602060048201526011602482015270125b9d985b1a59081c9958da5c1a595b9d607a1b604482015260640161050b565b600085858381811061124657611246611f15565b905060200201602081019061125b9190612155565b6001600160a01b031684848481811061127657611276611f15565b9050602002013560405160006040518083038185875af1925050503d80600081146112bd576040519150601f19603f3d011682016040523d82523d6000602084013e6112c2565b606091505b50509050806113055760405162461bcd60e51b815260206004820152600f60248201526e151c985b9cd9995c8819985a5b1959608a1b604482015260640161050b565b508061131081611f61565b9150506111b7565b5050505050565b6040805160008152602081019182905260029161133c9190612074565b602060405180830381855afa158015611359573d6000803e3d6000fd5b5050506040513d601f19601f8201168201806040525081019061137c9190612170565b81565b33301461139e5760405162461bcd60e51b815260040161050b90611feb565b816113d95760405162461bcd60e51b815260206004820152600b60248201526a456d70747920617272617960a81b604482015260640161050b565b600081116113f95760405162461bcd60e51b815260040161050b9061200d565b600254611407908390612189565b8111156114265760405162461bcd60e51b815260040161050b90612044565b60005b82811015610d185783838281811061144357611443611f15565b9050602002013560000361148e5760405162461bcd60e51b8152602060048201526012602482015271125b9d985b1a590818dbdb5b5a5d1b595b9d60721b604482015260640161050b565b60005b600254811015611525578484838181106114ad576114ad611f15565b90506020020135600282815481106114c7576114c7611f15565b9060005260206000200154036115135760405162461bcd60e51b8152602060048201526011602482015270436f6d6d69746d656e742065786973747360781b604482015260640161050b565b8061151d81611f61565b915050611491565b5060005b818110156115b75784848381811061154357611543611f15565b9050602002013585858381811061155c5761155c611f15565b90506020020135036115a55760405162461bcd60e51b8152602060048201526012602482015271111d5c1b1a58d85d19481a5b881a5b9c1d5d60721b604482015260640161050b565b806115af81611f61565b915050611529565b5060028484838181106115cc576115cc611f15565b8354600181018555600094855260209485902091909402929092013591909201555083838281811061160057611600611f15565b905060200201357f33129f9e36e06e860f22ce83276273f4d83d31e9fd4a0d3a4dbac9d2da4f9d0d600160405161163b911515815260200190565b60405180910390a28061164d81611f61565b915050611429565b6000806116827f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018561219c565b905060006116b07f30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f00000018561219c565b604080518082018252848152602081018390529051632b0aac7f60e11b8152919250733333333C0A88F9BE4fd23ed0536F9B6c427e3B939163561558fe916116fa916004016121be565b602060405180830381865af4158015611717573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061173b9190612170565b925050505b92915050565b6060600280548060200260200160405190810160405280929190818152602001828054801561179457602002820191906000526020600020905b815481526020019060010190808311611780575b5050505050905090565b6000805b6002548110156117eb5782600282815481106117c0576117c0611f15565b9060005260206000200154036117d95750600192915050565b806117e381611f61565b9150506117a2565b50600092915050565b600080611802846001611655565b604080516020808201849052863582840152868101356060808401919091528351808403909101815260808301845269756c747261706c6f6e6b60b01b60a08401528351608a81850301815260aa840180865281519190930120600080845260ca909401948590529495509391927f00000000000000000000000000000000000000000000000000000000000000009160029161189f9190612074565b602060405180830381855afa1580156118bc573d6000803e3d6000fd5b5050506040513d601f19601f820116820180604052508101906118df9190612170565b8480519060200120604051602001611910949392919093845260208401929092526040830152606082015260800190565b6040516020818303038152906040528051906020012090507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663a78f9e36866060013587604001358489806080019061197291906121ef565b8b60a001358c60c001356040518863ffffffff1660e01b815260040161199e9796959493929190612239565b602060405180830381865afa1580156119bb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119df9190612086565b9695505050505050565b80356001600160a01b0381168114611a0057600080fd5b919050565b60008083601f840112611a1757600080fd5b50813567ffffffffffffffff811115611a2f57600080fd5b602083019150836020828501011115611a4757600080fd5b9250929050565b60008083601f840112611a6057600080fd5b50813567ffffffffffffffff811115611a7857600080fd5b6020830191508360208260051b8501011115611a4757600080fd5b600080600080600080600060a0888a031215611aae57600080fd5b87359650611abe602089016119e9565b955060408801359450606088013567ffffffffffffffff80821115611ae257600080fd5b611aee8b838c01611a05565b909650945060808a0135915080821115611b0757600080fd5b50611b148a828b01611a4e565b989b979a50959850939692959293505050565b60005b83811015611b42578181015183820152602001611b2a565b50506000910152565b60008151808452611b63816020860160208601611b27565b601f01601f19169290920160200192915050565b602081526000611b8a6020830184611b4b565b9392505050565b600060208284031215611ba357600080fd5b5035919050565b600080600080600080600060c0888a031215611bc557600080fd5b611bce886119e9565b9650611bdc602089016119e9565b955060408801359450611bf1606089016119e9565b93506080880135925060a088013567ffffffffffffffff811115611c1457600080fd5b611b148a828b01611a05565b600080600060408486031215611c3557600080fd5b833567ffffffffffffffff811115611c4c57600080fd5b611c5886828701611a4e565b909790965060209590950135949350505050565b634e487b7160e01b600052604160045260246000fd5b60008060008060808587031215611c9857600080fd5b84359350611ca8602086016119e9565b925060408501359150606085013567ffffffffffffffff80821115611ccc57600080fd5b818701915087601f830112611ce057600080fd5b813581811115611cf257611cf2611c6c565b604051601f8201601f19908116603f01168101908382118183101715611d1a57611d1a611c6c565b816040528281528a6020848701011115611d3357600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b60008060008060008060608789031215611d7057600080fd5b863567ffffffffffffffff80821115611d8857600080fd5b611d948a838b01611a4e565b90985096506020890135915080821115611dad57600080fd5b611db98a838b01611a4e565b90965094506040890135915080821115611dd257600080fd5b50611ddf89828a01611a4e565b979a9699509497509295939492505050565b60008060008060408587031215611e0757600080fd5b843567ffffffffffffffff80821115611e1f57600080fd5b611e2b88838901611a4e565b90965094506020870135915080821115611e4457600080fd5b50611e5187828801611a4e565b95989497509550505050565b60008060408385031215611e7057600080fd5b50508035926020909101359150565b6020808252825182820181905260009190848201906040850190845b81811015611eb757835183529284019291840191600101611e9b565b50909695505050505050565b60006bffffffffffffffffffffffff19808a60601b168352886014840152876034840152808760601b166054840152508460688301528284608884013750600091016088019081529695505050505050565b634e487b7160e01b600052603260045260246000fd5b6000823560de19833603018112611f4157600080fd5b9190910192915050565b634e487b7160e01b600052601160045260246000fd5b600060018201611f7357611f73611f4b565b5060010190565b8183823760009101908152919050565b6001600160a01b0386168152602081018590526080604082018190528101839052828460a0830137600060a084830101526000601f19601f850116820160a0838203016060840152611fdf60a0820185611b4b565b98975050505050505050565b6020808252600890820152672737ba1029b2b63360c11b604082015260600190565b6020808252601e908201527f4d757374206265206e6f6e2d7a65726f20736967732072657175697265640000604082015260600190565b6020808252601690820152750a6d2cee640e4cae2ead2e4cac840e8dede40d0d2ced60531b604082015260600190565b60008251611f41818460208701611b27565b60006020828403121561209857600080fd5b81518015158114611b8a57600080fd5b8181038181111561174057611740611f4b565b634e487b7160e01b600052603160045260246000fd5b60006bffffffffffffffffffffffff19808960601b168352876014840152866034840152808660601b16605484015250836068830152825161211a816088850160208701611b27565b91909101608801979650505050505050565b6020808252600f908201526e098cadccee8d040dad2e6dac2e8c6d608b1b604082015260600190565b60006020828403121561216757600080fd5b611b8a826119e9565b60006020828403121561218257600080fd5b5051919050565b8082018082111561174057611740611f4b565b6000826121b957634e487b7160e01b600052601260045260246000fd5b500690565b60408101818360005b60028110156121e65781518352602092830192909101906001016121c7565b50505092915050565b6000808335601e1984360301811261220657600080fd5b83018035915067ffffffffffffffff82111561222157600080fd5b6020019150600581901b3603821315611a4757600080fd5b87815286602082015285604082015260c060608201528360c0820152600060018060fb1b0385111561226a57600080fd5b8460051b808760e085013760808301949094525060a08101919091520160e0019594505050505056fea2646970667358221220addee73ee726353c6fb61be647551027bfa7622664ea8abd7c75b400a386170364736f6c63430008140033";
diff --git a/packages/shared/src/contracts/bridge-abi.ts b/packages/shared/src/contracts/bridge-abi.ts
new file mode 100644
index 00000000..97bf6b63
--- /dev/null
+++ b/packages/shared/src/contracts/bridge-abi.ts
@@ -0,0 +1,150 @@
+// Minimal ABI for OP Stack L1StandardBridge
+export const L1_STANDARD_BRIDGE_ABI = [
+ {
+ inputs: [
+ { name: "_to", type: "address" },
+ { name: "_minGasLimit", type: "uint32" },
+ { name: "_extraData", type: "bytes" },
+ ],
+ name: "bridgeETHTo",
+ outputs: [],
+ stateMutability: "payable",
+ type: "function",
+ },
+] as const;
+
+// LayerZero OFT / OFT Adapter ABI (send + quoteSend)
+export const OFT_ABI = [
+ {
+ inputs: [
+ {
+ components: [
+ { name: "dstEid", type: "uint32" },
+ { name: "to", type: "bytes32" },
+ { name: "amountLD", type: "uint256" },
+ { name: "minAmountLD", type: "uint256" },
+ { name: "extraOptions", type: "bytes" },
+ { name: "composeMsg", type: "bytes" },
+ { name: "oftCmd", type: "bytes" },
+ ],
+ name: "_sendParam",
+ type: "tuple",
+ },
+ {
+ components: [
+ { name: "nativeFee", type: "uint256" },
+ { name: "lzTokenFee", type: "uint256" },
+ ],
+ name: "_fee",
+ type: "tuple",
+ },
+ { name: "_refundAddress", type: "address" },
+ ],
+ name: "send",
+ outputs: [
+ {
+ components: [
+ { name: "guid", type: "bytes32" },
+ { name: "nonce", type: "uint64" },
+ {
+ components: [
+ { name: "nativeFee", type: "uint256" },
+ { name: "lzTokenFee", type: "uint256" },
+ ],
+ name: "fee",
+ type: "tuple",
+ },
+ ],
+ name: "msgReceipt",
+ type: "tuple",
+ },
+ {
+ components: [
+ { name: "amountSentLD", type: "uint256" },
+ { name: "amountReceivedLD", type: "uint256" },
+ ],
+ name: "oftReceipt",
+ type: "tuple",
+ },
+ ],
+ stateMutability: "payable",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ components: [
+ { name: "dstEid", type: "uint32" },
+ { name: "to", type: "bytes32" },
+ { name: "amountLD", type: "uint256" },
+ { name: "minAmountLD", type: "uint256" },
+ { name: "extraOptions", type: "bytes" },
+ { name: "composeMsg", type: "bytes" },
+ { name: "oftCmd", type: "bytes" },
+ ],
+ name: "_sendParam",
+ type: "tuple",
+ },
+ { name: "_payInLzToken", type: "bool" },
+ ],
+ name: "quoteSend",
+ outputs: [
+ {
+ components: [
+ { name: "nativeFee", type: "uint256" },
+ { name: "lzTokenFee", type: "uint256" },
+ ],
+ name: "msgFee",
+ type: "tuple",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+ {
+ inputs: [
+ {
+ components: [
+ { name: "dstEid", type: "uint32" },
+ { name: "to", type: "bytes32" },
+ { name: "amountLD", type: "uint256" },
+ { name: "minAmountLD", type: "uint256" },
+ { name: "extraOptions", type: "bytes" },
+ { name: "composeMsg", type: "bytes" },
+ { name: "oftCmd", type: "bytes" },
+ ],
+ name: "_sendParam",
+ type: "tuple",
+ },
+ ],
+ name: "quoteOFT",
+ outputs: [
+ {
+ components: [
+ { name: "minAmountLD", type: "uint256" },
+ { name: "maxAmountLD", type: "uint256" },
+ ],
+ name: "oftLimit",
+ type: "tuple",
+ },
+ {
+ components: [
+ { name: "feeAmountLD", type: "int256" },
+ { name: "description", type: "string" },
+ ],
+ name: "oftFeeDetails",
+ type: "tuple[]",
+ },
+ {
+ components: [
+ { name: "amountSentLD", type: "uint256" },
+ { name: "amountReceivedLD", type: "uint256" },
+ ],
+ name: "oftReceipt",
+ type: "tuple",
+ },
+ ],
+ stateMutability: "view",
+ type: "function",
+ },
+] as const;
diff --git a/packages/shared/src/contracts/index.ts b/packages/shared/src/contracts/index.ts
index 1d4aceb4..7b73e146 100644
--- a/packages/shared/src/contracts/index.ts
+++ b/packages/shared/src/contracts/index.ts
@@ -1,2 +1,3 @@
export * from "./MetaMultiSigWallet";
export * from "./contracts-config";
+export * from "./bridge-abi";
diff --git a/packages/shared/src/dto/transaction/create-transaction.dto.ts b/packages/shared/src/dto/transaction/create-transaction.dto.ts
index 3492f433..a897cb41 100644
--- a/packages/shared/src/dto/transaction/create-transaction.dto.ts
+++ b/packages/shared/src/dto/transaction/create-transaction.dto.ts
@@ -49,6 +49,19 @@ export class CreateTransactionDto {
@IsString()
contactId?: string;
+ // Cross-chain bridge
+ @IsOptional()
+ @IsNumber()
+ destChainId?: number;
+
+ @IsOptional()
+ @IsString()
+ bridgeFee?: string;
+
+ @IsOptional()
+ @IsString()
+ bridgeMinAmount?: string;
+
// ADD_SIGNER / REMOVE_SIGNER
@IsOptional()
@IsArray()
diff --git a/packages/shared/src/types/account.ts b/packages/shared/src/types/account.ts
index 3f835aa9..17ab1583 100644
--- a/packages/shared/src/types/account.ts
+++ b/packages/shared/src/types/account.ts
@@ -10,6 +10,7 @@ export interface Account {
name: string;
threshold: number;
chainId: number;
+ contractVersion: number;
createdAt: string;
updatedAt: string;
signers: AccountSigner[];
diff --git a/packages/shared/src/types/transaction.ts b/packages/shared/src/types/transaction.ts
index 56398588..7645c6aa 100644
--- a/packages/shared/src/types/transaction.ts
+++ b/packages/shared/src/types/transaction.ts
@@ -22,6 +22,9 @@ export interface Transaction {
signerData?: SignerData[] | null;
newThreshold?: number;
batchData?: string;
+ destChainId?: number;
+ bridgeFee?: string;
+ bridgeMinAmount?: string;
createdBy: string;
threshold: number;
txHash?: string;
diff --git a/packages/shared/src/utils/encodeData.ts b/packages/shared/src/utils/encodeData.ts
index fffc4098..9331205a 100644
--- a/packages/shared/src/utils/encodeData.ts
+++ b/packages/shared/src/utils/encodeData.ts
@@ -1,4 +1,6 @@
-import { encodeFunctionData, type Hex } from "viem";
+import { encodeFunctionData, pad, type Hex } from "viem";
+import { L1_STANDARD_BRIDGE_ABI, OFT_ABI } from "../contracts/bridge-abi";
+import { OP_BRIDGE_MIN_GAS_LIMIT } from "../constants/bridge";
/**
* Encode addSigners function call
@@ -130,3 +132,112 @@ export function encodeBatchTransferMulti(
],
});
}
+
+// ─── Bridge encode functions ───
+
+/**
+ * Encode OP Stack bridgeETHTo call.
+ * Used for ETH bridge from Base → Horizen.
+ */
+export function encodeBridgeETHTo(recipient: string): Hex {
+ return encodeFunctionData({
+ abi: L1_STANDARD_BRIDGE_ABI,
+ functionName: "bridgeETHTo",
+ args: [recipient as `0x${string}`, OP_BRIDGE_MIN_GAS_LIMIT, "0x" as Hex],
+ });
+}
+
+/**
+ * Convert an address to bytes32 (left-padded) for LayerZero.
+ */
+export function addressToBytes32(addr: string): Hex {
+ return pad(addr as `0x${string}`, { size: 32 });
+}
+
+/**
+ * Strip dust bits that would be lost during LayerZero shared-decimals conversion.
+ * OFT standard uses 6 shared decimals; tokens with more local decimals lose
+ * the lowest (localDecimals - sharedDecimals) digits during transfer.
+ */
+export function removeDust(
+ amountLD: bigint,
+ localDecimals: number,
+ sharedDecimals = 6,
+): bigint {
+ if (localDecimals <= sharedDecimals) return amountLD;
+ const rate = BigInt(10 ** (localDecimals - sharedDecimals));
+ return (amountLD / rate) * rate;
+}
+
+/**
+ * Encode LayerZero OFT send() call.
+ */
+export function encodeLzSend(
+ dstEid: number,
+ recipient: string,
+ amountLD: bigint,
+ minAmountLD: bigint,
+ nativeFee: bigint,
+ refundAddress: string,
+ oftCmd: Hex = "0x",
+): Hex {
+ return encodeFunctionData({
+ abi: OFT_ABI,
+ functionName: "send",
+ args: [
+ {
+ dstEid,
+ to: addressToBytes32(recipient),
+ amountLD,
+ minAmountLD,
+ extraOptions: "0x" as Hex,
+ composeMsg: "0x" as Hex,
+ oftCmd,
+ },
+ {
+ nativeFee,
+ lzTokenFee: 0n,
+ },
+ refundAddress as `0x${string}`,
+ ],
+ });
+}
+
+/**
+ * Encode approveAndCall on MetaMultiSigWallet.
+ * Atomically approves a token and calls a target (e.g. OFT Adapter send).
+ */
+export function encodeApproveAndCall(
+ token: string,
+ spender: string,
+ approveAmount: bigint,
+ callTarget: string,
+ callValue: bigint,
+ callData: Hex,
+): Hex {
+ return encodeFunctionData({
+ abi: [
+ {
+ name: "approveAndCall",
+ type: "function",
+ inputs: [
+ { name: "token", type: "address" },
+ { name: "spender", type: "address" },
+ { name: "approveAmount", type: "uint256" },
+ { name: "callTarget", type: "address" },
+ { name: "callValue", type: "uint256" },
+ { name: "callData", type: "bytes" },
+ ],
+ },
+ ],
+ functionName: "approveAndCall",
+ args: [
+ token as `0x${string}`,
+ spender as `0x${string}`,
+ approveAmount,
+ callTarget as `0x${string}`,
+ callValue,
+ callData,
+ ],
+ });
+}