From 0ed51398181e280c747bfea6381da1e039e774aa Mon Sep 17 00:00:00 2001 From: An Date: Tue, 23 Dec 2025 15:18:48 -0500 Subject: [PATCH 1/4] orderly deposit --- .../sdk/src/anyspend/constants/orderly.ts | 369 ++++++++++++++++++ packages/sdk/src/anyspend/index.ts | 1 + .../react/components/OrderlyDeposit.tsx | 357 +++++++++++++++++ .../src/anyspend/react/components/index.ts | 2 + .../sdk/src/anyspend/react/hooks/index.ts | 1 + .../react/hooks/useOrderlyDepositFee.ts | 234 +++++++++++ 6 files changed, 964 insertions(+) create mode 100644 packages/sdk/src/anyspend/constants/orderly.ts create mode 100644 packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx create mode 100644 packages/sdk/src/anyspend/react/hooks/useOrderlyDepositFee.ts diff --git a/packages/sdk/src/anyspend/constants/orderly.ts b/packages/sdk/src/anyspend/constants/orderly.ts new file mode 100644 index 00000000..084b8a85 --- /dev/null +++ b/packages/sdk/src/anyspend/constants/orderly.ts @@ -0,0 +1,369 @@ +import { keccak256, toBytes, encodeAbiParameters, parseAbiParameters } from "viem"; +import { components } from "../types/api"; + +/** + * Orderly Network Omnichain Configuration + * Supports deposits from any chain where Orderly has a vault + * + * @see https://orderly.network/docs/build-on-omnichain/addresses + */ + +export interface OrderlyChainConfig { + /** Chain ID */ + chainId: number; + /** Display name */ + name: string; + /** Vault contract address */ + vaultAddress: `0x${string}`; + /** USDC token address (or USDC.e for some chains) */ + usdcAddress: `0x${string}`; + /** USDC decimals (usually 6, but some chains may differ) */ + usdcDecimals: number; + /** Token symbol (USDC or USDC.e) */ + usdcSymbol: string; + /** Whether USDT is also supported */ + supportsUsdt?: boolean; + /** USDT address if supported */ + usdtAddress?: `0x${string}`; + /** Public RPC URL */ + rpcUrl: string; + /** Block explorer URL */ + explorerUrl: string; + /** Chain logo URI */ + logoUri?: string; +} + +/** + * All Orderly supported chains with their contract addresses + * Mainnet addresses only + */ +export const ORDERLY_CHAINS: Record = { + // Arbitrum One + 42161: { + chainId: 42161, + name: "Arbitrum", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + usdcDecimals: 6, + usdcSymbol: "USDC", + supportsUsdt: true, + usdtAddress: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", + rpcUrl: "https://arbitrum-one-rpc.publicnode.com", + explorerUrl: "https://arbiscan.io", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_arbitrum.jpg", + }, + + // Optimism + 10: { + chainId: 10, + name: "Optimism", + vaultAddress: "0x816f722424b49cf1275cc86da9840fbd5a6167e9", + usdcAddress: "0x0b2c639c533813f4aa9d7837caf62653d097ff85", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://optimism-rpc.publicnode.com", + explorerUrl: "https://optimistic.etherscan.io", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_optimism.jpg", + }, + + // Base + 8453: { + chainId: 8453, + name: "Base", + vaultAddress: "0x816f722424b49cf1275cc86da9840fbd5a6167e9", + usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://base-rpc.publicnode.com", + explorerUrl: "https://basescan.org", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_base.jpg", + }, + + // Ethereum Mainnet + 1: { + chainId: 1, + name: "Ethereum", + vaultAddress: "0x816f722424b49cf1275cc86da9840fbd5a6167e9", + usdcAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + usdcDecimals: 6, + usdcSymbol: "USDC", + supportsUsdt: true, + usdtAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + rpcUrl: "https://ethereum-rpc.publicnode.com", + explorerUrl: "https://etherscan.io", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_ethereum.jpg", + }, + + // Mantle + 5000: { + chainId: 5000, + name: "Mantle", + vaultAddress: "0x816f722424b49cf1275cc86da9840fbd5a6167e9", + usdcAddress: "0x09bc4e0d864854c6afb6eb9a9cdf58ac190d0df9", + usdcDecimals: 6, + usdcSymbol: "USDC.e", + rpcUrl: "https://rpc.mantle.xyz", + explorerUrl: "https://mantlescan.xyz", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_mantle.jpg", + }, + + // Avalanche C-Chain + 43114: { + chainId: 43114, + name: "Avalanche", + vaultAddress: "0x816f722424b49cf1275cc86da9840fbd5a6167e9", + usdcAddress: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://avalanche-c-chain-rpc.publicnode.com", + explorerUrl: "https://snowtrace.io", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_avalanche.jpg", + }, + + // BNB Smart Chain + 56: { + chainId: 56, + name: "BNB Chain", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + usdcDecimals: 18, // BSC USDC has 18 decimals + usdcSymbol: "USDC", + supportsUsdt: true, + usdtAddress: "0x55d398326f99059fF775485246999027B3197955", + rpcUrl: "https://bsc-rpc.publicnode.com", + explorerUrl: "https://bscscan.com", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_binance.jpg", + }, + + // Polygon PoS + 137: { + chainId: 137, + name: "Polygon", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", // Native USDC on Polygon + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://polygon-bor-rpc.publicnode.com", + explorerUrl: "https://polygonscan.com", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_polygon.jpg", + }, + + // SEI + 1329: { + chainId: 1329, + name: "SEI", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://evm-rpc.sei-apis.com", + explorerUrl: "https://seitrace.com", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_sei.jpg", + }, + + // Mode + 34443: { + chainId: 34443, + name: "Mode", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0xd988097fb8612cc24eeC14542bC03424c656005f", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://mainnet.mode.network", + explorerUrl: "https://modescan.io", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_mode.jpg", + }, + + // Abstract + 2741: { + chainId: 2741, + name: "Abstract", + vaultAddress: "0xE80F2396A266e898FBbD251b89CFE65B3e41fD18", // Different vault address! + usdcAddress: "0x84A71ccD554Cc1b02749b35d22F684CC8ec987e1", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://api.mainnet.abs.xyz", + explorerUrl: "https://abscan.org", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_abstract.jpg", + }, + + // Morph + 2818: { + chainId: 2818, + name: "Morph", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0xe34c91815d7fc18A9e2148bcD4241d0a5848b693", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://rpc.morphl2.io", + explorerUrl: "https://explorer.morphl2.io", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_morph.jpg", + }, + + // Sonic (formerly Fantom Sonic) + 146: { + chainId: 146, + name: "Sonic", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0x29219dd037d542be3f1a41f28cdafee7a38f5894", // Fixed truncated address + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://rpc.soniclabs.com", + explorerUrl: "https://sonicscan.org", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_sonic.jpg", + }, + + // Berachain + 80094: { + chainId: 80094, + name: "Berachain", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0x549943e04f40284185054145c6e4e9568c1d3241", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://rpc.berachain.com", + explorerUrl: "https://berascan.com", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_berachain.jpg", + }, + + // Story + 1516: { + chainId: 1516, + name: "Story", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0xF1815bd50389c46847f0Bda824eC8da914045D14", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://mainnet.storyrpc.io", + explorerUrl: "https://storyscan.xyz", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_story.jpg", + }, + + // Plume + 98865: { + chainId: 98865, + name: "Plume", + vaultAddress: "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9", + usdcAddress: "0x78adD880A697070c1e765Ac44D65323a0DcCE913", + usdcDecimals: 6, + usdcSymbol: "USDC", + rpcUrl: "https://rpc.plume.org", + explorerUrl: "https://explorer.plume.org", + logoUri: "https://icons.llamao.fi/icons/chains/rsz_plume.jpg", + }, +} as const; + +/** + * Get list of all supported Orderly chain IDs + */ +export const ORDERLY_SUPPORTED_CHAIN_IDS = Object.keys(ORDERLY_CHAINS).map(Number); + +/** + * Primary/recommended chains for deposits (most liquid) + */ +export const ORDERLY_PRIMARY_CHAINS = [42161, 10, 8453, 1, 137, 56] as const; + +/** + * Default chain for Orderly deposits + */ +export const ORDERLY_DEFAULT_CHAIN_ID = 42161; // Arbitrum + +// Pre-computed hashes for efficiency +export const ORDERLY_HASHES = { + USDC_TOKEN_HASH: keccak256(toBytes("USDC")), + USDT_TOKEN_HASH: keccak256(toBytes("USDT")), +} as const; + +// Deposit fee buffer (5% like SDK) +export const ORDERLY_DEPOSIT_FEE_BUFFER = 105n; + +// Vault ABI for deposit and getDepositFee functions +export const ORDERLY_VAULT_ABI = [ + { + name: "deposit", + type: "function", + stateMutability: "payable", + inputs: [ + { + name: "depositData", + type: "tuple", + components: [ + { name: "accountId", type: "bytes32" }, + { name: "brokerHash", type: "bytes32" }, + { name: "tokenHash", type: "bytes32" }, + { name: "tokenAmount", type: "uint128" }, + ], + }, + ], + outputs: [], + }, + { + name: "getDepositFee", + type: "function", + stateMutability: "view", + inputs: [ + { name: "user", type: "address" }, + { + name: "depositData", + type: "tuple", + components: [ + { name: "accountId", type: "bytes32" }, + { name: "brokerHash", type: "bytes32" }, + { name: "tokenHash", type: "bytes32" }, + { name: "tokenAmount", type: "uint128" }, + ], + }, + ], + outputs: [{ name: "", type: "uint256" }], + }, +] as const; + +/** + * Get Orderly chain config by chain ID + */ +export function getOrderlyChainConfig(chainId: number): OrderlyChainConfig | undefined { + return ORDERLY_CHAINS[chainId]; +} + +/** + * Check if a chain is supported by Orderly + */ +export function isOrderlyChainSupported(chainId: number): boolean { + return chainId in ORDERLY_CHAINS; +} + +/** + * Get USDC token definition for AnySpend by chain ID + */ +export function getOrderlyUsdcToken(chainId: number): components["schemas"]["Token"] | undefined { + const config = ORDERLY_CHAINS[chainId]; + if (!config) return undefined; + + return { + chainId: config.chainId, + address: config.usdcAddress, + symbol: config.usdcSymbol, + name: config.usdcSymbol === "USDC.e" ? "Bridged USD Coin" : "USD Coin", + decimals: config.usdcDecimals, + metadata: { + logoURI: + "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png", + }, + }; +} + +/** + * Compute Orderly accountId from wallet address and broker ID + * Formula: keccak256(abi.encode(address, keccak256(brokerId))) + */ +export function computeOrderlyAccountId(walletAddress: `0x${string}`, brokerId: string): `0x${string}` { + const brokerIdHash = keccak256(toBytes(brokerId)); + return keccak256(encodeAbiParameters(parseAbiParameters("address, bytes32"), [walletAddress, brokerIdHash])); +} + +/** + * Compute broker hash from broker ID + */ +export function computeBrokerHash(brokerId: string): `0x${string}` { + return keccak256(toBytes(brokerId)); +} diff --git a/packages/sdk/src/anyspend/index.ts b/packages/sdk/src/anyspend/index.ts index 91b8f24e..826666dd 100644 --- a/packages/sdk/src/anyspend/index.ts +++ b/packages/sdk/src/anyspend/index.ts @@ -14,6 +14,7 @@ export * from "./utils/validation"; // Constants export * from "./constants"; +export * from "./constants/orderly"; // Services export * from "./services/gas"; diff --git a/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx b/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx new file mode 100644 index 00000000..47df34e4 --- /dev/null +++ b/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx @@ -0,0 +1,357 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useAccount, useChainId, useSendTransaction, useSwitchChain, useWaitForTransactionReceipt } from "wagmi"; +import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; +import { Loader2, CheckCircle2, AlertCircle } from "lucide-react"; + +import { cn } from "@b3dotfun/sdk/shared/utils/cn"; +import { ShinyButton, StyleRoot, toast } from "@b3dotfun/sdk/global-account/react"; + +import { + ORDERLY_VAULT_ABI, + ORDERLY_HASHES, + computeOrderlyAccountId, + computeBrokerHash, + getOrderlyChainConfig, + isOrderlyChainSupported, +} from "../../constants/orderly"; +import { useOrderlyDepositFee } from "../hooks/useOrderlyDepositFee"; + +export interface OrderlyDepositProps { + /** The broker ID for Orderly Network */ + brokerId: string; + /** The chain ID to deposit on */ + chainId: number; + /** The amount to deposit in USDC (e.g., "100" for $100) */ + amount: string; + /** The beneficiary wallet address (defaults to connected wallet) */ + beneficiaryAddress?: `0x${string}`; + /** Callback when deposit succeeds */ + onSuccess?: (txHash: string) => void; + /** Callback when deposit fails */ + onError?: (error: Error) => void; + /** Custom button text */ + buttonText?: string; + /** Whether to show the fee breakdown */ + showFeeBreakdown?: boolean; + /** Custom class name for the container */ + className?: string; +} + +type DepositState = "idle" | "switching-chain" | "approving" | "depositing" | "success" | "error"; + +/** + * OrderlyDeposit - Single-screen deposit component for Orderly Network + * + * Receives chain and amount as props, fetches deposit fee automatically, + * and executes the deposit in one click. + * + * @example + * ```tsx + * console.log("Deposited!", txHash)} + * /> + * ``` + */ +export function OrderlyDeposit({ + brokerId, + chainId, + amount, + beneficiaryAddress, + onSuccess, + onError, + buttonText, + showFeeBreakdown = true, + className, +}: OrderlyDepositProps) { + const { address: connectedAddress } = useAccount(); + const currentChainId = useChainId(); + const { switchChainAsync } = useSwitchChain(); + + // Use beneficiary address or connected wallet + const effectiveAddress = beneficiaryAddress ?? connectedAddress; + + // State + const [depositState, setDepositState] = useState("idle"); + const [txHash, setTxHash] = useState<`0x${string}` | undefined>(); + const [errorMessage, setErrorMessage] = useState(); + + // Get chain config + const chainConfig = useMemo(() => getOrderlyChainConfig(chainId), [chainId]); + const isChainSupported = isOrderlyChainSupported(chainId); + const isOnCorrectChain = currentChainId === chainId; + + // Compute Orderly identifiers + const accountId = useMemo(() => { + if (!effectiveAddress) return undefined; + return computeOrderlyAccountId(effectiveAddress, brokerId); + }, [effectiveAddress, brokerId]); + + const brokerHash = useMemo(() => computeBrokerHash(brokerId), [brokerId]); + + // Fetch deposit fee + const { + feeWei, + feeWithBufferWei, + feeFormatted, + feeWithBufferFormatted, + isLoading: isFeeLoading, + error: feeError, + } = useOrderlyDepositFee({ + walletAddress: effectiveAddress, + brokerId, + chainId, + amount, + }); + + // Wagmi hooks + const { sendTransactionAsync, isPending: isSendingTx } = useSendTransaction(); + const { isLoading: isWaitingTx, isSuccess: isTxConfirmed } = useWaitForTransactionReceipt({ + hash: txHash, + }); + + // Handle tx confirmation + useEffect(() => { + if (isTxConfirmed && txHash) { + setDepositState("success"); + onSuccess?.(txHash); + } + }, [isTxConfirmed, txHash, onSuccess]); + + // Validation + const isValidAmount = useMemo(() => { + const numAmount = parseFloat(amount); + return !isNaN(numAmount) && numAmount > 0; + }, [amount]); + + const canDeposit = useMemo(() => { + return ( + effectiveAddress && + accountId && + brokerHash && + chainConfig && + isValidAmount && + feeWithBufferWei && + !isFeeLoading && + depositState === "idle" + ); + }, [effectiveAddress, accountId, brokerHash, chainConfig, isValidAmount, feeWithBufferWei, isFeeLoading, depositState]); + + // Execute deposit + const executeDeposit = useCallback(async () => { + if (!effectiveAddress || !accountId || !brokerHash || !chainConfig || !feeWithBufferWei) { + return; + } + + setErrorMessage(undefined); + + try { + // Switch chain if needed + if (!isOnCorrectChain) { + setDepositState("switching-chain"); + await switchChainAsync({ chainId }); + } + + // Approve USDC + setDepositState("approving"); + const amountInUnits = parseUnits(amount, chainConfig.usdcDecimals); + + await sendTransactionAsync({ + to: chainConfig.usdcAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "approve", + args: [chainConfig.vaultAddress, amountInUnits], + }), + }); + + // Small delay to ensure approval is mined + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Deposit to vault + setDepositState("depositing"); + const depositTxHash = await sendTransactionAsync({ + to: chainConfig.vaultAddress, + value: feeWithBufferWei, + data: encodeFunctionData({ + abi: ORDERLY_VAULT_ABI, + functionName: "deposit", + args: [ + { + accountId: accountId, + brokerHash: brokerHash, + tokenHash: ORDERLY_HASHES.USDC_TOKEN_HASH, + tokenAmount: amountInUnits, + }, + ], + }), + }); + + setTxHash(depositTxHash); + } catch (err: any) { + console.error("Deposit error:", err); + setDepositState("error"); + setErrorMessage(err.shortMessage || err.message || "Deposit failed"); + onError?.(err); + } + }, [ + effectiveAddress, + accountId, + brokerHash, + chainConfig, + feeWithBufferWei, + isOnCorrectChain, + switchChainAsync, + chainId, + amount, + sendTransactionAsync, + onError, + ]); + + // Reset state + const reset = useCallback(() => { + setDepositState("idle"); + setTxHash(undefined); + setErrorMessage(undefined); + }, []); + + // Button text based on state + const getButtonText = () => { + if (buttonText && depositState === "idle") return buttonText; + + switch (depositState) { + case "switching-chain": + return `Switching to ${chainConfig?.name}...`; + case "approving": + return "Approving USDC..."; + case "depositing": + return "Depositing..."; + case "success": + return "Deposit Successful!"; + case "error": + return "Try Again"; + default: + if (!isOnCorrectChain) { + return `Switch to ${chainConfig?.name} & Deposit`; + } + return `Deposit ${amount} ${chainConfig?.usdcSymbol || "USDC"}`; + } + }; + + // Error states + if (!isChainSupported) { + return ( +
+ Chain {chainId} is not supported by Orderly +
+ ); + } + + if (!effectiveAddress) { + return ( +
+ Connect wallet to deposit +
+ ); + } + + const isProcessing = depositState !== "idle" && depositState !== "success" && depositState !== "error"; + + return ( + +
+ {/* Fee breakdown */} + {showFeeBreakdown && ( +
+
+
+ Amount + + {amount} {chainConfig?.usdcSymbol} + +
+
+ Network +
+ {chainConfig?.logoUri && ( + + )} + {chainConfig?.name} +
+
+
+ Deposit Fee + + {isFeeLoading ? ( + + ) : feeFormatted ? ( + `~${parseFloat(feeFormatted).toFixed(6)} ETH` + ) : feeError ? ( + Error + ) : ( + "..." + )} + +
+
+
+ )} + + {/* Success state */} + {depositState === "success" && txHash && ( +
+ +
+

Deposit successful!

+ + View transaction + +
+
+ )} + + {/* Error state */} + {depositState === "error" && errorMessage && ( +
+ +

{errorMessage}

+
+ )} + + {/* Deposit button */} + +
+ {isProcessing && } + {depositState === "success" && } + {getButtonText()} +
+
+
+
+ ); +} + +export default OrderlyDeposit; diff --git a/packages/sdk/src/anyspend/react/components/index.ts b/packages/sdk/src/anyspend/react/components/index.ts index 2ebb689c..48dd6dff 100644 --- a/packages/sdk/src/anyspend/react/components/index.ts +++ b/packages/sdk/src/anyspend/react/components/index.ts @@ -8,6 +8,8 @@ export { AnySpendCustomExactIn } from "./AnySpendCustomExactIn"; export { AnySpendDeposit } from "./AnySpendDeposit"; export type { AnySpendDepositProps, ChainConfig, DepositContractConfig } from "./AnySpendDeposit"; export { AnySpendDepositHype, HYPE_TOKEN_DETAILS } from "./AnyspendDepositHype"; +export { OrderlyDeposit } from "./OrderlyDeposit"; +export type { OrderlyDepositProps } from "./OrderlyDeposit"; export * from "./AnySpendFingerprintWrapper"; export { AnySpendNFT } from "./AnySpendNFT"; export { AnyspendSignatureMint } from "./AnyspendSignatureMint"; diff --git a/packages/sdk/src/anyspend/react/hooks/index.ts b/packages/sdk/src/anyspend/react/hooks/index.ts index ff56994a..909c3a9a 100644 --- a/packages/sdk/src/anyspend/react/hooks/index.ts +++ b/packages/sdk/src/anyspend/react/hooks/index.ts @@ -15,3 +15,4 @@ export * from "./useRecipientAddressState"; export * from "./useSigMint"; export * from "./useStripeClientSecret"; export * from "./useStripeSupport"; +export * from "./useOrderlyDepositFee"; diff --git a/packages/sdk/src/anyspend/react/hooks/useOrderlyDepositFee.ts b/packages/sdk/src/anyspend/react/hooks/useOrderlyDepositFee.ts new file mode 100644 index 00000000..d39393db --- /dev/null +++ b/packages/sdk/src/anyspend/react/hooks/useOrderlyDepositFee.ts @@ -0,0 +1,234 @@ +"use client"; + +import { useCallback, useMemo } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { createPublicClient, http, parseUnits, formatUnits } from "viem"; +import { + ORDERLY_CHAINS, + ORDERLY_VAULT_ABI, + ORDERLY_HASHES, + ORDERLY_DEPOSIT_FEE_BUFFER, + ORDERLY_DEFAULT_CHAIN_ID, + computeOrderlyAccountId, + computeBrokerHash, + getOrderlyChainConfig, + isOrderlyChainSupported, + type OrderlyChainConfig, +} from "../../constants/orderly"; + +/** + * Create a public client for a specific chain + */ +function createChainClient(chainConfig: OrderlyChainConfig) { + return createPublicClient({ + transport: http(chainConfig.rpcUrl), + }); +} + +export interface OrderlyDepositFeeParams { + /** The wallet address of the depositor */ + walletAddress?: `0x${string}`; + /** The broker ID for Orderly */ + brokerId: string; + /** The chain ID to deposit on (defaults to Arbitrum) */ + chainId?: number; + /** The amount to deposit (in USDC, e.g., "100" for $100) */ + amount?: string; + /** Whether to apply the 5% buffer (default: true) */ + applyBuffer?: boolean; +} + +export interface OrderlyDepositFeeResult { + /** The raw deposit fee in wei */ + feeWei: bigint | undefined; + /** The deposit fee with buffer applied (in wei) */ + feeWithBufferWei: bigint | undefined; + /** The deposit fee formatted in native token (ETH, AVAX, etc.) */ + feeFormatted: string | undefined; + /** The deposit fee with buffer formatted */ + feeWithBufferFormatted: string | undefined; + /** Whether the fee is loading */ + isLoading: boolean; + /** Error if fee fetch failed */ + error: Error | null; + /** The computed accountId for this wallet/broker combination */ + accountId: `0x${string}` | undefined; + /** The broker hash */ + brokerHash: `0x${string}` | undefined; + /** The chain config being used */ + chainConfig: OrderlyChainConfig | undefined; + /** Whether the chain is supported */ + isChainSupported: boolean; + /** Refetch the deposit fee */ + refetch: () => void; + /** Fetch fee for a specific amount (useful for dynamic calculations) */ + fetchFeeForAmount: ( + amountUsdc: string, + targetChainId?: number, + ) => Promise<{ + feeWei: bigint; + feeWithBufferWei: bigint; + }>; +} + +/** + * Hook to fetch the Orderly vault deposit fee for any supported chain + * + * @example + * ```tsx + * const { feeFormatted, feeWithBufferWei, isLoading, chainConfig } = useOrderlyDepositFee({ + * walletAddress: address, + * brokerId: "my_broker_id", + * chainId: 42161, // Arbitrum + * amount: "100", // $100 USDC + * }); + * + * // Display the fee + * console.log(`Deposit fee: ${feeFormatted} ${chainConfig?.name === 'Avalanche' ? 'AVAX' : 'ETH'}`); + * ``` + */ +export function useOrderlyDepositFee({ + walletAddress, + brokerId, + chainId = ORDERLY_DEFAULT_CHAIN_ID, + amount, + applyBuffer = true, +}: OrderlyDepositFeeParams): OrderlyDepositFeeResult { + // Get chain config + const chainConfig = useMemo(() => getOrderlyChainConfig(chainId), [chainId]); + const isChainSupported = useMemo(() => isOrderlyChainSupported(chainId), [chainId]); + + // Compute accountId and brokerHash + const accountId = useMemo(() => { + if (!walletAddress) return undefined; + return computeOrderlyAccountId(walletAddress, brokerId); + }, [walletAddress, brokerId]); + + const brokerHash = useMemo(() => { + return computeBrokerHash(brokerId); + }, [brokerId]); + + // Convert amount to USDC smallest units (using chain-specific decimals) + const amountInUnits = useMemo(() => { + if (!amount || parseFloat(amount) <= 0 || !chainConfig) return undefined; + try { + return parseUnits(amount, chainConfig.usdcDecimals); + } catch { + return undefined; + } + }, [amount, chainConfig]); + + // Fetch deposit fee from contract + const fetchDepositFee = useCallback(async (): Promise => { + if (!walletAddress || !accountId || !amountInUnits || !chainConfig) { + throw new Error("Missing required parameters for fee calculation"); + } + + const client = createChainClient(chainConfig); + + const depositData = { + accountId: accountId, + brokerHash: brokerHash!, + tokenHash: ORDERLY_HASHES.USDC_TOKEN_HASH, + tokenAmount: amountInUnits, + }; + + const fee = await client.readContract({ + address: chainConfig.vaultAddress, + abi: ORDERLY_VAULT_ABI, + functionName: "getDepositFee", + args: [walletAddress, depositData], + }); + + return fee as bigint; + }, [walletAddress, accountId, brokerHash, amountInUnits, chainConfig]); + + // Use react-query for caching and refetching + const { + data: feeWei, + isLoading, + error, + refetch, + } = useQuery({ + queryKey: ["orderly-deposit-fee", walletAddress, brokerId, chainId, amount], + queryFn: fetchDepositFee, + enabled: !!walletAddress && !!accountId && !!amountInUnits && !!chainConfig, + staleTime: 30_000, // 30 seconds + refetchInterval: 60_000, // Refetch every minute + }); + + // Calculate fee with buffer + const feeWithBufferWei = useMemo(() => { + if (!feeWei) return undefined; + return applyBuffer ? (feeWei * ORDERLY_DEPOSIT_FEE_BUFFER) / 100n : feeWei; + }, [feeWei, applyBuffer]); + + // Format fees for display + const feeFormatted = useMemo(() => { + if (!feeWei) return undefined; + return formatUnits(feeWei, 18); + }, [feeWei]); + + const feeWithBufferFormatted = useMemo(() => { + if (!feeWithBufferWei) return undefined; + return formatUnits(feeWithBufferWei, 18); + }, [feeWithBufferWei]); + + // Function to fetch fee for a specific amount on any chain + const fetchFeeForAmount = useCallback( + async ( + amountUsdc: string, + targetChainId?: number, + ): Promise<{ feeWei: bigint; feeWithBufferWei: bigint }> => { + const effectiveChainId = targetChainId ?? chainId; + const targetChainConfig = getOrderlyChainConfig(effectiveChainId); + + if (!walletAddress || !accountId || !brokerHash || !targetChainConfig) { + throw new Error("Wallet address, broker ID, and valid chain are required"); + } + + const client = createChainClient(targetChainConfig); + const units = parseUnits(amountUsdc, targetChainConfig.usdcDecimals); + + const depositData = { + accountId: accountId, + brokerHash: brokerHash, + tokenHash: ORDERLY_HASHES.USDC_TOKEN_HASH, + tokenAmount: units, + }; + + const fee = await client.readContract({ + address: targetChainConfig.vaultAddress, + abi: ORDERLY_VAULT_ABI, + functionName: "getDepositFee", + args: [walletAddress, depositData], + }); + + const feeValue = fee as bigint; + const feeWithBuffer = (feeValue * ORDERLY_DEPOSIT_FEE_BUFFER) / 100n; + + return { + feeWei: feeValue, + feeWithBufferWei: feeWithBuffer, + }; + }, + [walletAddress, accountId, brokerHash, chainId], + ); + + return { + feeWei, + feeWithBufferWei, + feeFormatted, + feeWithBufferFormatted, + isLoading, + error: error as Error | null, + accountId, + brokerHash, + chainConfig, + isChainSupported, + refetch, + fetchFeeForAmount, + }; +} + +export default useOrderlyDepositFee; From 5d8f20c3031ef81ca8887d43aa947739bcae9347 Mon Sep 17 00:00:00 2001 From: An Date: Tue, 23 Dec 2025 15:38:21 -0500 Subject: [PATCH 2/4] orderly deposit works --- apps/anyspend-demo-nextjs/next.config.js | 2 +- .../app/components/OrderlyDepositButton.tsx | 136 ++++++++++++++++++ apps/anyspend-demo-nextjs/src/app/page.tsx | 2 + .../react/components/OrderlyDeposit.tsx | 51 +++---- .../react/hooks/useOrderlyDepositFee.ts | 10 +- 5 files changed, 162 insertions(+), 39 deletions(-) create mode 100644 apps/anyspend-demo-nextjs/src/app/components/OrderlyDepositButton.tsx diff --git a/apps/anyspend-demo-nextjs/next.config.js b/apps/anyspend-demo-nextjs/next.config.js index 71caa0c4..11fc93d5 100644 --- a/apps/anyspend-demo-nextjs/next.config.js +++ b/apps/anyspend-demo-nextjs/next.config.js @@ -5,7 +5,7 @@ const nextConfig = { env: { NEXT_PUBLIC_THIRDWEB_CLIENT_ID: process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID || "eb17a5ec4314526d42fc567821aeb9a6", NEXT_PUBLIC_GLOBAL_ACCOUNTS_PARTNER_ID: - process.env.NEXT_PUBLIC_GLOBAL_ACCOUNTS_PARTNER_ID || "ceba2f84-45ff-4717-b3e9-0acf0d062abd", + process.env.NEXT_PUBLIC_GLOBAL_ACCOUNTS_PARTNER_ID || "b9aac999-efef-4625-96d6-8043f20ec615", NEXT_PUBLIC_THIRDWEB_ECOSYSTEM_ID: process.env.NEXT_PUBLIC_THIRDWEB_ECOSYSTEM_ID || "ecosystem.b3dotfun", NEXT_PUBLIC_B3_API: process.env.NEXT_PUBLIC_B3_API || "https://b3-api-development.up.railway.app", NEXT_PUBLIC_ANYSPEND_BASE_URL: process.env.NEXT_PUBLIC_ANYSPEND_BASE_URL || "https://mainnet.anyspend.com", diff --git a/apps/anyspend-demo-nextjs/src/app/components/OrderlyDepositButton.tsx b/apps/anyspend-demo-nextjs/src/app/components/OrderlyDepositButton.tsx new file mode 100644 index 00000000..4b166bec --- /dev/null +++ b/apps/anyspend-demo-nextjs/src/app/components/OrderlyDepositButton.tsx @@ -0,0 +1,136 @@ +"use client"; + +import { OrderlyDeposit } from "@b3dotfun/sdk/anyspend/react"; +import { useAccountWallet } from "@b3dotfun/sdk/global-account/react"; +import { useState } from "react"; + +// Demo broker ID - replace with your actual broker ID from Orderly +const DEMO_BROKER_ID = "volt"; + +// Available chains for the demo +const DEMO_CHAINS = [ + { id: 42161, name: "Arbitrum" }, + { id: 10, name: "Optimism" }, + { id: 8453, name: "Base" }, + { id: 1, name: "Ethereum" }, + { id: 137, name: "Polygon" }, +]; + +export function OrderlyDepositButton() { + const { address } = useAccountWallet(); + + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedChainId, setSelectedChainId] = useState(42161); + const [amount, setAmount] = useState("10"); + + const handleOpenModal = () => { + setIsModalOpen(true); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + }; + + const handleSuccess = (txHash: string) => { + console.log("Orderly deposit successful!", txHash); + // Optionally close modal after success + // setIsModalOpen(false); + }; + + const handleError = (error: Error) => { + console.error("Orderly deposit failed:", error); + }; + + return ( + <> + + + {/* Modal */} + {isModalOpen && ( +
+
+ {/* Header */} +
+

Orderly Deposit

+ +
+ + {/* Chain selector */} +
+ + +
+ + {/* Amount input */} +
+ + setAmount(e.target.value)} + placeholder="Enter amount" + className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500" + /> +
+ {["10", "50", "100", "500"].map(val => ( + + ))} +
+
+ + {/* Orderly Deposit Component */} + {address ? ( + + ) : ( +
+ Connect wallet to deposit +
+ )} +
+
+ )} + + ); +} diff --git a/apps/anyspend-demo-nextjs/src/app/page.tsx b/apps/anyspend-demo-nextjs/src/app/page.tsx index 9acb862c..e6edaa85 100644 --- a/apps/anyspend-demo-nextjs/src/app/page.tsx +++ b/apps/anyspend-demo-nextjs/src/app/page.tsx @@ -10,6 +10,7 @@ import { DepositHypeButton } from "./components/DepositHypeButton"; import { GetB3TokenButton } from "./components/GetB3TokenButton"; import { MintNftButton } from "./components/MintNftButton"; import { OrderDetailsButton } from "./components/OrderDetailsButton"; +import { OrderlyDepositButton } from "./components/OrderlyDepositButton"; import { SignInButton } from "./components/SignInButton"; import { SignatureMintButton } from "./components/SignatureMintButton"; import { SignatureMintModal } from "./components/SignatureMintModal"; @@ -59,6 +60,7 @@ export default function Home() { + diff --git a/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx b/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx index 47df34e4..366198ac 100644 --- a/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx +++ b/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx @@ -6,7 +6,7 @@ import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; import { Loader2, CheckCircle2, AlertCircle } from "lucide-react"; import { cn } from "@b3dotfun/sdk/shared/utils/cn"; -import { ShinyButton, StyleRoot, toast } from "@b3dotfun/sdk/global-account/react"; +import { ShinyButton, StyleRoot } from "@b3dotfun/sdk/global-account/react"; import { ORDERLY_VAULT_ABI, @@ -94,14 +94,7 @@ export function OrderlyDeposit({ const brokerHash = useMemo(() => computeBrokerHash(brokerId), [brokerId]); // Fetch deposit fee - const { - feeWei, - feeWithBufferWei, - feeFormatted, - feeWithBufferFormatted, - isLoading: isFeeLoading, - error: feeError, - } = useOrderlyDepositFee({ + const { feeWithBufferWei, feeFormatted, isLoading: isFeeLoading, error: feeError } = useOrderlyDepositFee({ walletAddress: effectiveAddress, brokerId, chainId, @@ -109,8 +102,8 @@ export function OrderlyDeposit({ }); // Wagmi hooks - const { sendTransactionAsync, isPending: isSendingTx } = useSendTransaction(); - const { isLoading: isWaitingTx, isSuccess: isTxConfirmed } = useWaitForTransactionReceipt({ + const { sendTransactionAsync } = useSendTransaction(); + const { isSuccess: isTxConfirmed } = useWaitForTransactionReceipt({ hash: txHash, }); @@ -139,7 +132,16 @@ export function OrderlyDeposit({ !isFeeLoading && depositState === "idle" ); - }, [effectiveAddress, accountId, brokerHash, chainConfig, isValidAmount, feeWithBufferWei, isFeeLoading, depositState]); + }, [ + effectiveAddress, + accountId, + brokerHash, + chainConfig, + isValidAmount, + feeWithBufferWei, + isFeeLoading, + depositState, + ]); // Execute deposit const executeDeposit = useCallback(async () => { @@ -244,19 +246,11 @@ export function OrderlyDeposit({ // Error states if (!isChainSupported) { - return ( -
- Chain {chainId} is not supported by Orderly -
- ); + return
Chain {chainId} is not supported by Orderly
; } if (!effectiveAddress) { - return ( -
- Connect wallet to deposit -
- ); + return
Connect wallet to deposit
; } const isProcessing = depositState !== "idle" && depositState !== "success" && depositState !== "error"; @@ -277,9 +271,7 @@ export function OrderlyDeposit({
Network
- {chainConfig?.logoUri && ( - - )} + {chainConfig?.logoUri && } {chainConfig?.name}
@@ -303,7 +295,7 @@ export function OrderlyDeposit({ {/* Success state */} {depositState === "success" && txHash && ( -
+

Deposit successful!

@@ -321,7 +313,7 @@ export function OrderlyDeposit({ {/* Error state */} {depositState === "error" && errorMessage && ( -
+

{errorMessage}

@@ -331,10 +323,7 @@ export function OrderlyDeposit({ => { - if (!walletAddress || !accountId || !amountInUnits || !chainConfig) { + if (!walletAddress || !accountId || !brokerHash || !amountInUnits || !chainConfig) { throw new Error("Missing required parameters for fee calculation"); } @@ -128,7 +127,7 @@ export function useOrderlyDepositFee({ const depositData = { accountId: accountId, - brokerHash: brokerHash!, + brokerHash: brokerHash, tokenHash: ORDERLY_HASHES.USDC_TOKEN_HASH, tokenAmount: amountInUnits, }; @@ -176,10 +175,7 @@ export function useOrderlyDepositFee({ // Function to fetch fee for a specific amount on any chain const fetchFeeForAmount = useCallback( - async ( - amountUsdc: string, - targetChainId?: number, - ): Promise<{ feeWei: bigint; feeWithBufferWei: bigint }> => { + async (amountUsdc: string, targetChainId?: number): Promise<{ feeWei: bigint; feeWithBufferWei: bigint }> => { const effectiveChainId = targetChainId ?? chainId; const targetChainConfig = getOrderlyChainConfig(effectiveChainId); From 518323ebe51287805ec91abdbafc772b44384f3d Mon Sep 17 00:00:00 2001 From: An Date: Tue, 23 Dec 2025 16:36:36 -0500 Subject: [PATCH 3/4] orderly deposit --- apps/anyspend-demo-nextjs/next.config.js | 2 + .../AnySpendOrderlyDepositButton.tsx | 100 ++++++++ apps/anyspend-demo-nextjs/src/app/page.tsx | 2 + .../components/AnySpendOrderlyDeposit.tsx | 217 ++++++++++++++++++ .../src/anyspend/react/components/index.ts | 2 + 5 files changed, 323 insertions(+) create mode 100644 apps/anyspend-demo-nextjs/src/app/components/AnySpendOrderlyDepositButton.tsx create mode 100644 packages/sdk/src/anyspend/react/components/AnySpendOrderlyDeposit.tsx diff --git a/apps/anyspend-demo-nextjs/next.config.js b/apps/anyspend-demo-nextjs/next.config.js index 11fc93d5..a906ba73 100644 --- a/apps/anyspend-demo-nextjs/next.config.js +++ b/apps/anyspend-demo-nextjs/next.config.js @@ -9,6 +9,8 @@ const nextConfig = { NEXT_PUBLIC_THIRDWEB_ECOSYSTEM_ID: process.env.NEXT_PUBLIC_THIRDWEB_ECOSYSTEM_ID || "ecosystem.b3dotfun", NEXT_PUBLIC_B3_API: process.env.NEXT_PUBLIC_B3_API || "https://b3-api-development.up.railway.app", NEXT_PUBLIC_ANYSPEND_BASE_URL: process.env.NEXT_PUBLIC_ANYSPEND_BASE_URL || "https://mainnet.anyspend.com", + NEXT_PUBLIC_DEVMODE_SHARED_SECRET: + process.env.NEXT_PUBLIC_DEVMODE_SHARED_SECRET || "k1c4Ep6agmoejiBinKE70B6bzb8vSdm8", }, }; diff --git a/apps/anyspend-demo-nextjs/src/app/components/AnySpendOrderlyDepositButton.tsx b/apps/anyspend-demo-nextjs/src/app/components/AnySpendOrderlyDepositButton.tsx new file mode 100644 index 00000000..73296b3e --- /dev/null +++ b/apps/anyspend-demo-nextjs/src/app/components/AnySpendOrderlyDepositButton.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { AnySpendOrderlyDeposit } from "@b3dotfun/sdk/anyspend/react"; +import { useAccountWallet } from "@b3dotfun/sdk/global-account/react"; +import { useState } from "react"; + +// Demo broker ID - replace with your actual broker ID from Orderly +const DEMO_BROKER_ID = "volt"; + +// Available Orderly chains for the demo +const DEMO_CHAINS = [ + { id: 42161, name: "Arbitrum" }, + { id: 10, name: "Optimism" }, + { id: 8453, name: "Base" }, + { id: 1, name: "Ethereum" }, + { id: 137, name: "Polygon" }, +]; + +export function AnySpendOrderlyDepositButton() { + const { address } = useAccountWallet(); + + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedChainId, setSelectedChainId] = useState(42161); + + const handleOpenModal = () => { + setIsModalOpen(true); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + }; + + const handleSuccess = (amount: string) => { + console.log("Orderly deposit successful! Amount:", amount); + }; + + return ( + <> + + + {/* Modal */} + {isModalOpen && ( +
+
+ {/* Chain selector */} +
+ + +

This is where your USDC will be deposited in Orderly

+
+ + {/* AnySpend Orderly Deposit Component */} + {address ? ( + + ) : ( +
+

Connect wallet to deposit

+ +
+ )} +
+
+ )} + + ); +} diff --git a/apps/anyspend-demo-nextjs/src/app/page.tsx b/apps/anyspend-demo-nextjs/src/app/page.tsx index e6edaa85..a2473b75 100644 --- a/apps/anyspend-demo-nextjs/src/app/page.tsx +++ b/apps/anyspend-demo-nextjs/src/app/page.tsx @@ -11,6 +11,7 @@ import { GetB3TokenButton } from "./components/GetB3TokenButton"; import { MintNftButton } from "./components/MintNftButton"; import { OrderDetailsButton } from "./components/OrderDetailsButton"; import { OrderlyDepositButton } from "./components/OrderlyDepositButton"; +import { AnySpendOrderlyDepositButton } from "./components/AnySpendOrderlyDepositButton"; import { SignInButton } from "./components/SignInButton"; import { SignatureMintButton } from "./components/SignatureMintButton"; import { SignatureMintModal } from "./components/SignatureMintModal"; @@ -61,6 +62,7 @@ export default function Home() { +
diff --git a/packages/sdk/src/anyspend/react/components/AnySpendOrderlyDeposit.tsx b/packages/sdk/src/anyspend/react/components/AnySpendOrderlyDeposit.tsx new file mode 100644 index 00000000..244b00be --- /dev/null +++ b/packages/sdk/src/anyspend/react/components/AnySpendOrderlyDeposit.tsx @@ -0,0 +1,217 @@ +"use client"; + +import { useMemo } from "react"; +import { useAccount } from "wagmi"; + +import { cn } from "@b3dotfun/sdk/shared/utils/cn"; + +import { + ORDERLY_CHAINS, + ORDERLY_SUPPORTED_CHAIN_IDS, + ORDERLY_HASHES, + getOrderlyChainConfig, + getOrderlyUsdcToken, + computeOrderlyAccountId, + computeBrokerHash, +} from "../../constants/orderly"; +import { AnySpendDeposit, type DepositContractConfig, type ChainConfig } from "./AnySpendDeposit"; + +// Orderly vault deposit function ABI - must be an array of ABI items +const ORDERLY_DEPOSIT_ABI = JSON.stringify([ + { + name: "deposit", + type: "function", + stateMutability: "payable", + inputs: [ + { + name: "depositData", + type: "tuple", + components: [ + { name: "accountId", type: "bytes32" }, + { name: "brokerHash", type: "bytes32" }, + { name: "tokenHash", type: "bytes32" }, + { name: "tokenAmount", type: "uint128" }, + ], + }, + ], + outputs: [], + }, +]); + +export interface AnySpendOrderlyDepositProps { + /** The broker ID for Orderly Network */ + brokerId: string; + /** The target chain ID to deposit on (must be Orderly-supported) */ + chainId: number; + /** The beneficiary wallet address (defaults to connected wallet) */ + beneficiaryAddress?: `0x${string}`; + /** Callback when the deposit succeeds */ + onSuccess?: (amount: string) => void; + /** Callback when user closes the modal */ + onClose?: () => void; + /** Display mode */ + mode?: "modal" | "page"; + /** Custom class name for the container */ + className?: string; + /** Minimum destination amount in USDC */ + minAmount?: number; + /** Source chain ID to pre-select */ + sourceChainId?: number; + /** Payment type - crypto or fiat */ + paymentType?: "crypto" | "fiat"; + /** Custom header component */ + header?: React.ReactNode; +} + +/** + * AnySpendOrderlyDeposit - Deposit any token from any chain to Orderly Network + * + * Uses AnySpend to swap any token to USDC and deposit directly to Orderly vault + * in a single transaction flow. + * + * @example + * ```tsx + * console.log("Deposited!", amount)} + * /> + * ``` + */ +export function AnySpendOrderlyDeposit({ + brokerId, + chainId, + beneficiaryAddress, + onSuccess, + onClose, + mode = "modal", + className, + minAmount = 1, + sourceChainId, + paymentType, + header, +}: AnySpendOrderlyDepositProps) { + const { address: connectedAddress } = useAccount(); + + // Use beneficiary address or connected wallet + const effectiveAddress = beneficiaryAddress ?? connectedAddress; + + // Validate chain is Orderly-supported + const chainConfig = useMemo(() => getOrderlyChainConfig(chainId), [chainId]); + const isChainSupported = ORDERLY_SUPPORTED_CHAIN_IDS.includes(chainId); + + // Get USDC token for the target chain + const destinationToken = useMemo(() => getOrderlyUsdcToken(chainId), [chainId]); + + // Build supported chains list (only Orderly-supported chains) + const supportedChains: ChainConfig[] = useMemo(() => { + return ORDERLY_SUPPORTED_CHAIN_IDS.map(id => { + const config = ORDERLY_CHAINS[id]; + return { + id, + name: config.name, + iconUrl: config.logoUri, + }; + }); + }, []); + + // Compute Orderly identifiers for the deposit + const accountId = useMemo(() => { + if (!effectiveAddress) return undefined; + return computeOrderlyAccountId(effectiveAddress, brokerId); + }, [effectiveAddress, brokerId]); + + const brokerHash = useMemo(() => computeBrokerHash(brokerId), [brokerId]); + + // Build the deposit contract config for AnySpend + const depositContractConfig: DepositContractConfig | undefined = useMemo(() => { + if (!chainConfig || !accountId || !brokerHash) return undefined; + + // For tuple arguments, pass as a JSON array representing the tuple fields + // The tuple is: (bytes32 accountId, bytes32 brokerHash, bytes32 tokenHash, uint128 tokenAmount) + const tupleArg = JSON.stringify({ + accountId: accountId, + brokerHash: brokerHash, + tokenHash: ORDERLY_HASHES.USDC_TOKEN_HASH, + tokenAmount: "{{amount_out}}", // Will be replaced with actual swapped amount + }); + + return { + functionAbi: ORDERLY_DEPOSIT_ABI, + functionName: "deposit", + functionArgs: [tupleArg], + to: chainConfig.vaultAddress, + spenderAddress: chainConfig.vaultAddress, // USDC approval goes to vault + action: "Deposit to Orderly", + }; + }, [chainConfig, accountId, brokerHash]); + + // Validation + if (!isChainSupported || !chainConfig) { + return ( +
+ Chain {chainId} is not supported by Orderly Network +
+ ); + } + + if (!destinationToken) { + return ( +
+ Could not find USDC token for chain {chainId} +
+ ); + } + + if (!effectiveAddress) { + return ( +
+ Connect wallet to deposit +
+ ); + } + + if (!depositContractConfig) { + return ( +
+ Loading deposit configuration... +
+ ); + } + + // Custom header for Orderly deposit + const defaultHeader = () => ( +
+
+

Deposit to Orderly

+

+ Swap any token and deposit to {chainConfig.name} +

+
+
+ ); + + return ( +
+ <>{header} : defaultHeader} + /> +
+ ); +} + +export default AnySpendOrderlyDeposit; diff --git a/packages/sdk/src/anyspend/react/components/index.ts b/packages/sdk/src/anyspend/react/components/index.ts index 48dd6dff..02aa9c9f 100644 --- a/packages/sdk/src/anyspend/react/components/index.ts +++ b/packages/sdk/src/anyspend/react/components/index.ts @@ -10,6 +10,8 @@ export type { AnySpendDepositProps, ChainConfig, DepositContractConfig } from ". export { AnySpendDepositHype, HYPE_TOKEN_DETAILS } from "./AnyspendDepositHype"; export { OrderlyDeposit } from "./OrderlyDeposit"; export type { OrderlyDepositProps } from "./OrderlyDeposit"; +export { AnySpendOrderlyDeposit } from "./AnySpendOrderlyDeposit"; +export type { AnySpendOrderlyDepositProps } from "./AnySpendOrderlyDeposit"; export * from "./AnySpendFingerprintWrapper"; export { AnySpendNFT } from "./AnySpendNFT"; export { AnyspendSignatureMint } from "./AnyspendSignatureMint"; From b43b794a443c39d38cd540170d5c5a7c141511f4 Mon Sep 17 00:00:00 2001 From: An Date: Wed, 31 Dec 2025 00:30:37 -0500 Subject: [PATCH 4/4] orderly deposit --- .../AnySpendOrderlyDepositButton.tsx | 33 +----- .../components/AnySpendCustomExactIn.tsx | 3 + .../react/components/AnySpendDeposit.tsx | 2 + .../components/AnySpendOrderlyDeposit.tsx | 109 +++++++----------- .../react/components/OrderlyDeposit.tsx | 7 +- .../react/hooks/useCreateDepositFirstOrder.ts | 1 + .../react/hooks/useOrderlyDepositFee.ts | 1 + packages/sdk/src/anyspend/types/api.ts | 5 + 8 files changed, 60 insertions(+), 101 deletions(-) diff --git a/apps/anyspend-demo-nextjs/src/app/components/AnySpendOrderlyDepositButton.tsx b/apps/anyspend-demo-nextjs/src/app/components/AnySpendOrderlyDepositButton.tsx index 73296b3e..b5d4685d 100644 --- a/apps/anyspend-demo-nextjs/src/app/components/AnySpendOrderlyDepositButton.tsx +++ b/apps/anyspend-demo-nextjs/src/app/components/AnySpendOrderlyDepositButton.tsx @@ -7,20 +7,9 @@ import { useState } from "react"; // Demo broker ID - replace with your actual broker ID from Orderly const DEMO_BROKER_ID = "volt"; -// Available Orderly chains for the demo -const DEMO_CHAINS = [ - { id: 42161, name: "Arbitrum" }, - { id: 10, name: "Optimism" }, - { id: 8453, name: "Base" }, - { id: 1, name: "Ethereum" }, - { id: 137, name: "Polygon" }, -]; - export function AnySpendOrderlyDepositButton() { const { address } = useAccountWallet(); - const [isModalOpen, setIsModalOpen] = useState(false); - const [selectedChainId, setSelectedChainId] = useState(42161); const handleOpenModal = () => { setIsModalOpen(true); @@ -42,11 +31,11 @@ export function AnySpendOrderlyDepositButton() { >

AnySpend + Orderly

-

Deposit any token from any chain to Orderly

+

Deposit any token to Orderly on Arbitrum

Any token - Cross-chain + Arbitrum
@@ -54,28 +43,10 @@ export function AnySpendOrderlyDepositButton() { {isModalOpen && (
- {/* Chain selector */} -
- - -

This is where your USDC will be deposited in Orderly

-
- {/* AnySpend Orderly Deposit Component */} {address ? ( console.log("Deposited!", amount)} * /> * ``` */ export function AnySpendOrderlyDeposit({ brokerId, - chainId, beneficiaryAddress, onSuccess, onClose, mode = "modal", className, minAmount = 1, - sourceChainId, paymentType, header, }: AnySpendOrderlyDepositProps) { @@ -96,24 +93,11 @@ export function AnySpendOrderlyDeposit({ // Use beneficiary address or connected wallet const effectiveAddress = beneficiaryAddress ?? connectedAddress; - // Validate chain is Orderly-supported - const chainConfig = useMemo(() => getOrderlyChainConfig(chainId), [chainId]); - const isChainSupported = ORDERLY_SUPPORTED_CHAIN_IDS.includes(chainId); - - // Get USDC token for the target chain - const destinationToken = useMemo(() => getOrderlyUsdcToken(chainId), [chainId]); - - // Build supported chains list (only Orderly-supported chains) - const supportedChains: ChainConfig[] = useMemo(() => { - return ORDERLY_SUPPORTED_CHAIN_IDS.map(id => { - const config = ORDERLY_CHAINS[id]; - return { - id, - name: config.name, - iconUrl: config.logoUri, - }; - }); - }, []); + // Get Arbitrum chain config + const chainConfig = useMemo(() => getOrderlyChainConfig(ORDERLY_CHAIN_ID), []); + + // Get USDC token for Arbitrum + const destinationToken = useMemo(() => getOrderlyUsdcToken(ORDERLY_CHAIN_ID), []); // Compute Orderly identifiers for the deposit const accountId = useMemo(() => { @@ -123,11 +107,19 @@ export function AnySpendOrderlyDeposit({ const brokerHash = useMemo(() => computeBrokerHash(brokerId), [brokerId]); + // Fetch deposit fee from Orderly vault contract using minAmount as estimate + const { feeWithBufferWei } = useOrderlyDepositFee({ + walletAddress: effectiveAddress, + brokerId, + chainId: ORDERLY_CHAIN_ID, + amount: minAmount.toString(), // Use minAmount for fee estimation + }); + // Build the deposit contract config for AnySpend const depositContractConfig: DepositContractConfig | undefined = useMemo(() => { - if (!chainConfig || !accountId || !brokerHash) return undefined; + if (!chainConfig || !effectiveAddress || !accountId || !brokerHash || !feeWithBufferWei) return undefined; - // For tuple arguments, pass as a JSON array representing the tuple fields + // For tuple arguments, pass as a JSON object representing the tuple fields // The tuple is: (bytes32 accountId, bytes32 brokerHash, bytes32 tokenHash, uint128 tokenAmount) const tupleArg = JSON.stringify({ accountId: accountId, @@ -137,46 +129,31 @@ export function AnySpendOrderlyDeposit({ }); return { - functionAbi: ORDERLY_DEPOSIT_ABI, - functionName: "deposit", - functionArgs: [tupleArg], + functionAbi: ORDERLY_DEPOSIT_TO_ABI, + functionName: "depositTo", + functionArgs: [effectiveAddress, tupleArg], // receiver address first, then tuple data to: chainConfig.vaultAddress, spenderAddress: chainConfig.vaultAddress, // USDC approval goes to vault action: "Deposit to Orderly", + value: feeWithBufferWei.toString(), // Native token fee for LayerZero cross-chain messaging }; - }, [chainConfig, accountId, brokerHash]); + }, [chainConfig, effectiveAddress, accountId, brokerHash, feeWithBufferWei]); // Validation - if (!isChainSupported || !chainConfig) { - return ( -
- Chain {chainId} is not supported by Orderly Network -
- ); + if (!chainConfig) { + return
Failed to load Arbitrum chain configuration
; } if (!destinationToken) { - return ( -
- Could not find USDC token for chain {chainId} -
- ); + return
Could not find USDC token for Arbitrum
; } if (!effectiveAddress) { - return ( -
- Connect wallet to deposit -
- ); + return
Connect wallet to deposit
; } if (!depositContractConfig) { - return ( -
- Loading deposit configuration... -
- ); + return
Loading deposit configuration...
; } // Custom header for Orderly deposit @@ -184,9 +161,7 @@ export function AnySpendOrderlyDeposit({

Deposit to Orderly

-

- Swap any token and deposit to {chainConfig.name} -

+

Swap any token and deposit to Arbitrum

); @@ -197,17 +172,13 @@ export function AnySpendOrderlyDeposit({ mode={mode} recipientAddress={effectiveAddress} destinationToken={destinationToken} - destinationChainId={chainId} + destinationChainId={ORDERLY_CHAIN_ID} depositContractConfig={depositContractConfig} - supportedChains={supportedChains} minDestinationAmount={minAmount} - sourceTokenChainId={sourceChainId} paymentType={paymentType} onSuccess={onSuccess} onClose={onClose} actionLabel="Deposit to Orderly" - chainSelectionTitle="Pay from" - chainSelectionDescription="Select source chain for your deposit" header={header ? () => <>{header} : defaultHeader} />
diff --git a/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx b/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx index 366198ac..f233392f 100644 --- a/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx +++ b/packages/sdk/src/anyspend/react/components/OrderlyDeposit.tsx @@ -94,7 +94,12 @@ export function OrderlyDeposit({ const brokerHash = useMemo(() => computeBrokerHash(brokerId), [brokerId]); // Fetch deposit fee - const { feeWithBufferWei, feeFormatted, isLoading: isFeeLoading, error: feeError } = useOrderlyDepositFee({ + const { + feeWithBufferWei, + feeFormatted, + isLoading: isFeeLoading, + error: feeError, + } = useOrderlyDepositFee({ walletAddress: effectiveAddress, brokerId, chainId, diff --git a/packages/sdk/src/anyspend/react/hooks/useCreateDepositFirstOrder.ts b/packages/sdk/src/anyspend/react/hooks/useCreateDepositFirstOrder.ts index 73091c6e..68b3d8d9 100644 --- a/packages/sdk/src/anyspend/react/hooks/useCreateDepositFirstOrder.ts +++ b/packages/sdk/src/anyspend/react/hooks/useCreateDepositFirstOrder.ts @@ -50,6 +50,7 @@ export function useCreateDepositFirstOrder({ onSuccess, onError }: UseCreateDepo to: normalizeAddress(contractConfig.to), spenderAddress: contractConfig.spenderAddress ? normalizeAddress(contractConfig.spenderAddress) : undefined, action: contractConfig.action, + value: contractConfig.value, } : {}; diff --git a/packages/sdk/src/anyspend/react/hooks/useOrderlyDepositFee.ts b/packages/sdk/src/anyspend/react/hooks/useOrderlyDepositFee.ts index 758a35f0..0974f520 100644 --- a/packages/sdk/src/anyspend/react/hooks/useOrderlyDepositFee.ts +++ b/packages/sdk/src/anyspend/react/hooks/useOrderlyDepositFee.ts @@ -154,6 +154,7 @@ export function useOrderlyDepositFee({ enabled: !!walletAddress && !!accountId && !!amountInUnits && !!chainConfig, staleTime: 30_000, // 30 seconds refetchInterval: 60_000, // Refetch every minute + structuralSharing: false, // BigInt can't be serialized, disable structural sharing }); // Calculate fee with buffer diff --git a/packages/sdk/src/anyspend/types/api.ts b/packages/sdk/src/anyspend/types/api.ts index 79bb98d0..e6f51ea5 100644 --- a/packages/sdk/src/anyspend/types/api.ts +++ b/packages/sdk/src/anyspend/types/api.ts @@ -1905,6 +1905,11 @@ export interface components { * @example 0x58241893EF1f86C9fBd8109Cd44Ea961fDb474e1 */ spenderAddress?: string; + /** + * @description Native token value to send with the transaction (in wei) + * @example 1000000000000000000 + */ + value?: string; /** * @description Optional action identifier used for display purposes * @example stake B3