@ab-org/predicate-market-sdk 0.0.2 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Prediction-market specific helpers built on top of `@ab-org/sdk-core`.
7
7
  - Connection state via `createWalletConnectController()` and `createAccountController()` from `@ab-org/sdk-core`
8
8
  - Unified smart-wallet session metadata with capability policy support
9
9
  - High-level execution helpers via `createWalletExecutionController()`
10
- - Deposit / withdraw controllers with USD1-first defaults
10
+ - Deposit / withdraw controllers with USDT-first defaults
11
11
  - **Dynamic token & chain lists** via `MarketDataProvider` (backend-driven, mock included)
12
12
  - **Quote / slippage API** — fetch real-time pricing before confirming deposit or withdraw
13
13
  - Predicate-market policy adapter for deposit / withdraw / trade flows
@@ -94,6 +94,16 @@ initSDK({
94
94
 
95
95
  Rules:
96
96
  - **`NEXT_PUBLIC_RELAY_ORIGIN`** (or `RELAY_ORIGIN`) must match where you serve **`/relay/google`** and **`/relay/x`**
97
+
98
+ ### Funding chain & funding token (withdraw / balance)
99
+
100
+ - **`getChainInfo(chainId?)`** — resolves RPC, explorer metadata, native currency fields, and **`defaultFundingTokenAddress`** (per-chain default ERC-20 for the funding leg). If **`chainId`** is omitted or empty, defaults to **`3131`** (Tenderly BSC vnet). Built-in ids are **`3131`** and **`56`** (BSC mainnet); unknown ids throw.
101
+ - **`getFundingTokenAddress(chainId?)`** — same optional **`chainId`** as **`getChainInfo`**. If **`NEXT_PUBLIC_FUNDING_TOKEN_ADDRESS`** / **`FUNDING_TOKEN_ADDRESS`** or legacy defaultFundingTokenAddress`**.
102
+ - **`DEFAULT_FUNDING_TOKEN_ADDRESS`** — shorthand for **`getChainInfo().defaultFundingTokenAddress`** (default funding chain **`3131`**).
103
+ - **`fetchFundingTokenBalance(address, { chainId, rpcUrl?, tokenAddress?, decimals?, displaySymbol? })`** — uses **`getChainInfo(chainId)`** for RPC when **`rpcUrl`** is omitted, **`getFundingTokenAddress(chainId)`** when **`tokenAddress`** is omitted, and **`decimals`** default **`chain.nativeCurrencyDecimals`** when omitted (override if your funding token uses different decimals).
104
+ - **`createFundingWithdrawExecutor({ chainId?, rpcUrl?, tokenAddress?, … })`** — uses the same **`chainId`** for **`getChainInfo`**, default token address (**`getFundingTokenAddress(chainId)`**), and order payload (default **`3131`** when **`chainId`** omitted).
105
+
106
+ **Type note:** **`EvmChainInfo`** includes **`defaultFundingTokenAddress`**. If you construct chain objects manually in TypeScript, add that field or use **`getChainInfo`** instead of literals.
97
107
  - `socialProviders: undefined` uses built-in defaults (`google`, `x`)
98
108
  - `socialProviders: []` hides all social buttons
99
109
  - known social ids like `google` and `x` automatically reuse built-in icons unless you override `icon`
@@ -241,7 +251,7 @@ These policies can be passed into your smart-wallet authorization flow so app ac
241
251
  <button
242
252
  onClick={() =>
243
253
  withdraw.open({
244
- defaultToken: "USD1",
254
+ defaultToken: "USDT",
245
255
  defaultChain: "AB_CORE",
246
256
  targetAddress: "0x...",
247
257
  defaultAmount: "100",
@@ -1,22 +1,2 @@
1
- export declare const testBsc: {
2
- readonly chainId: "3131";
3
- readonly name: "BSC_TENDERLY";
4
- readonly chainName: "BSC_TENDERLY";
5
- readonly nativeCurrencyName: "BSC";
6
- readonly nativeCurrencySymbol: "BSC";
7
- readonly nativeCurrencyDecimals: 18;
8
- readonly rpcUrls: readonly ["https://virtual.binance.eu.rpc.tenderly.co/e643ea28-32eb-4fb9-8116-90be24f7defa"];
9
- readonly blockExplorerUrl: "https://dashboard.tenderly.co/explorer/vnet/6593bc72-f548-497d-bff9-5be061436a48";
10
- readonly platformType: "EVM";
11
- readonly icon: "https://static.tomo.inc/token/bsc_new.svg";
12
- readonly supportSwap: true;
13
- readonly supportGift: true;
14
- readonly supportHistory: true;
15
- };
16
- export declare const clientIds: {
17
- readonly google: string;
18
- readonly x: string;
19
- readonly sdk: "MfLzAb2ItdDR3RZfE33ueqUhRf3mo1w0tBzLfExvLG72oUzvI6mgACtq19v6Vz7cmHVsfMBpbmr1fFU09sn30wbD";
20
- };
21
- export declare const BSC_USD1_ADDRESS = "0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d";
22
- export declare const BSC_USD1_PLATFORM_CONTRACT_ADDRESS = "0x1234567890123456789012345678901234567890";
1
+ export { ClientIds, DEFAULT_FUNDING_CHAIN_ID, DEFAULT_FUNDING_TOKEN_ADDRESS, getFundingTokenAddress, getChainInfo, type EvmChainInfo, } from "@ab-org/wallet-utils";
2
+ /** Demo / docs placeholder platform contract; replace in production. */
@@ -1,23 +1,3 @@
1
- import { getEnv } from "../utils/env.js";
2
- export const testBsc = {
3
- chainId: "3131",
4
- name: "BSC_TENDERLY",
5
- chainName: "BSC_TENDERLY",
6
- nativeCurrencyName: "BSC",
7
- nativeCurrencySymbol: "BSC",
8
- nativeCurrencyDecimals: 18,
9
- rpcUrls: ["https://virtual.binance.eu.rpc.tenderly.co/e643ea28-32eb-4fb9-8116-90be24f7defa"],
10
- blockExplorerUrl: "https://dashboard.tenderly.co/explorer/vnet/6593bc72-f548-497d-bff9-5be061436a48",
11
- platformType: "EVM",
12
- icon: "https://static.tomo.inc/token/bsc_new.svg",
13
- supportSwap: true,
14
- supportGift: true,
15
- supportHistory: true,
16
- };
17
- export const clientIds = {
18
- google: getEnv("GOOGLE_CLIENT_ID"),
19
- x: getEnv("X_CLIENT_ID"),
20
- sdk: "MfLzAb2ItdDR3RZfE33ueqUhRf3mo1w0tBzLfExvLG72oUzvI6mgACtq19v6Vz7cmHVsfMBpbmr1fFU09sn30wbD",
21
- };
22
- export const BSC_USD1_ADDRESS = "0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d";
23
- export const BSC_USD1_PLATFORM_CONTRACT_ADDRESS = "0x1234567890123456789012345678901234567890";
1
+ export { ClientIds, DEFAULT_FUNDING_CHAIN_ID, DEFAULT_FUNDING_TOKEN_ADDRESS, getFundingTokenAddress, getChainInfo, } from "@ab-org/wallet-utils";
2
+ /** Demo / docs placeholder platform contract; replace in production. */
3
+ // export const FUNDING_TOKEN_PLATFORM_CONTRACT_ADDRESS ="0x1234567890123456789012345678901234567890";
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ export * from "./modules/withdraw.js";
8
8
  export * from "./modules/marketData.js";
9
9
  export * from "./modules/balanceQuery.js";
10
10
  export * from "./modules/withdrawExecutor.js";
11
+ export * from "./modules/api.js";
12
+ export * from "./modules/withdrawDirect.js";
11
13
  export * from "./policyAdapter.js";
12
14
  export * from "./ui/SignInModal.js";
13
15
  export * from "./ui/signInTypes.js";
@@ -17,3 +19,4 @@ export * from "./ui/WithdrawModal.js";
17
19
  export * from "./ui/components/DropdownField.js";
18
20
  export * from "./ui/components/DepositDetailsPanel.js";
19
21
  export * from "./walletUtils.js";
22
+ export * from "./constants/chains.js";
package/dist/index.js CHANGED
@@ -8,6 +8,8 @@ export * from "./modules/withdraw.js";
8
8
  export * from "./modules/marketData.js";
9
9
  export * from "./modules/balanceQuery.js";
10
10
  export * from "./modules/withdrawExecutor.js";
11
+ export * from "./modules/api.js";
12
+ export * from "./modules/withdrawDirect.js";
11
13
  export * from "./policyAdapter.js";
12
14
  export * from "./ui/SignInModal.js";
13
15
  export * from "./ui/signInTypes.js";
@@ -17,3 +19,5 @@ export * from "./ui/WithdrawModal.js";
17
19
  export * from "./ui/components/DropdownField.js";
18
20
  export * from "./ui/components/DepositDetailsPanel.js";
19
21
  export * from "./walletUtils.js";
22
+ // Re-export funding chain helpers so apps can import from package root
23
+ export * from "./constants/chains.js";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Merchant API client (BaseUrl: https://merchant.tomo.services)
2
+ * Merchant API client
3
3
  * 基于 axios 封装,类型与文档一致。
4
4
  */
5
5
  import { type AxiosInstance } from "axios";
@@ -14,6 +14,10 @@ export interface TokenData {
14
14
  symbol: string;
15
15
  address: string;
16
16
  decimals: number;
17
+ /** 最小充值量(最小单位字符串,如 `"15000000"`) */
18
+ minimum_deposit?: string;
19
+ /** 是否为 USD 类稳定币 */
20
+ is_usd_stable?: boolean;
17
21
  }
18
22
  export interface ChainData {
19
23
  chain_id: string;
@@ -33,12 +37,12 @@ export interface PlatformRegisterResponseData {
33
37
  chain_id: string;
34
38
  }
35
39
  export type QuoteDirection = "deposit" | "withdraw";
36
- export interface QuoteRequest {
40
+ interface QuoteRequest {
37
41
  direction: QuoteDirection;
38
42
  chain_id: string;
39
43
  token_address: string;
40
44
  token_amount?: string;
41
- usd1_amount?: string;
45
+ dst_token_amount?: string;
42
46
  }
43
47
  export interface QuoteResponseData {
44
48
  token_address: string;
@@ -47,7 +51,7 @@ export interface QuoteResponseData {
47
51
  rate: string;
48
52
  chain_id: number;
49
53
  deposit_address?: string;
50
- usd1_amount?: string;
54
+ dst_token_amount?: string;
51
55
  token_amount?: string;
52
56
  expires_at?: string;
53
57
  }
@@ -58,7 +62,7 @@ export interface DepositOrderResponseData {
58
62
  source_chain_id: string;
59
63
  token_address: string;
60
64
  token_amount?: string;
61
- usd1_amount?: string;
65
+ dst_token_amount?: string;
62
66
  deposit_address?: string;
63
67
  source_tx_hash?: string;
64
68
  created_at?: string;
@@ -70,8 +74,8 @@ export interface WithdrawOrderResponseData {
70
74
  status: WithdrawOrderStatus;
71
75
  one_time_address?: string;
72
76
  chain_id: string;
73
- usd1_amount: string;
74
- /** Fee (e.g. "0.01 USD1"); only present if backend includes it in GET /api/v1/orders/withdraw/:id. UI falls back to feeDisplay prop or "—". */
77
+ dst_token_amount: string;
78
+ /** Fee (e.g. "0.01 USDT"); only present if backend includes it in GET /api/v1/orders/withdraw/:id. UI falls back to feeDisplay prop or "—". */
75
79
  fee?: string;
76
80
  target_chain_id: string;
77
81
  target_address: string;
@@ -123,7 +127,7 @@ export interface CreateOrderResponseData {
123
127
  payment_sessions: PaymentSessionResponseData[];
124
128
  }
125
129
  /**
126
- * 配置 Merchant API 的 baseURL(可选,默认 https://merchant.tomo.services)
130
+ * 配置 Merchant API 的 baseURL
127
131
  */
128
132
  export declare function configureMerchantApi(): void;
129
133
  /**
@@ -142,3 +146,4 @@ export declare function getDepositOrder(orderId: string): Promise<DepositOrderRe
142
146
  export declare function getWithdrawOrder(orderId: string): Promise<WithdrawOrderResponseData>;
143
147
  /** POST /order - 创建 NATIVE_SWAP 提现订单 */
144
148
  export declare function createOrder(body: CreateOrderRequest): Promise<CreateOrderResponseData>;
149
+ export {};
@@ -1,13 +1,15 @@
1
1
  /**
2
- * Merchant API client (BaseUrl: https://merchant.tomo.services)
2
+ * Merchant API client
3
3
  * 基于 axios 封装,类型与文档一致。
4
4
  */
5
5
  import { getEnv } from "../utils/env.js";
6
6
  import axios from "axios";
7
- const DEFAULT_MERCHANT_BASE_URL = "https://merchant.tomo.services";
8
7
  // ─── Axios 实例与请求封装 ─────────────────────────────────────────────────────
9
8
  function createClient() {
10
- const BASE_URL = getEnv("MERCHANT_BASE_URL") || DEFAULT_MERCHANT_BASE_URL;
9
+ const BASE_URL = getEnv("MERCHANT_BASE_URL");
10
+ if (!BASE_URL) {
11
+ throw new Error('MERCHANT_BASE_URL is not set');
12
+ }
11
13
  return axios.create({
12
14
  baseURL: BASE_URL,
13
15
  timeout: 30000,
@@ -24,7 +26,7 @@ function unwrap(res) {
24
26
  // 单例 client,可通过 configureMerchantApi 修改 baseURL
25
27
  let apiClient = createClient();
26
28
  /**
27
- * 配置 Merchant API 的 baseURL(可选,默认 https://merchant.tomo.services)
29
+ * 配置 Merchant API 的 baseURL
28
30
  */
29
31
  export function configureMerchantApi() {
30
32
  apiClient = createClient();
@@ -8,13 +8,20 @@ export interface Erc20BalanceResult {
8
8
  * No external library dependency – uses the global `fetch`.
9
9
  */
10
10
  export declare function fetchErc20Balance(rpcUrl: string, tokenAddress: string, walletAddress: string): Promise<bigint>;
11
- export interface BscUsd1BalanceOptions {
11
+ export interface FundingTokenBalanceOptions {
12
12
  rpcUrl?: string;
13
13
  tokenAddress?: string;
14
14
  decimals?: number;
15
+ /**
16
+ * Funding chain id (e.g. `"3131"` Tenderly BSC, `"56"` mainnet).
17
+ * When omitted, defaults to `3131` via {@link getChainInfo}.
18
+ */
19
+ chainId?: string | number | null;
20
+ /** Label for {@link Erc20BalanceResult.symbol} (default `"Funding"`). */
21
+ displaySymbol?: string;
15
22
  }
16
23
  /**
17
- * Convenience wrapper fetches the USD1 balance on BSC for a given wallet.
18
- * All parameters have sensible defaults and can be overridden.
24
+ * Fetches the configured funding ERC-20 balance on the selected funding chain.
25
+ * Defaults: chain `3131`, RPC from {@link getChainInfo}, token from {@link getFundingTokenAddress} / env.
19
26
  */
20
- export declare function fetchBscUsd1Balance(walletAddress: string, options?: BscUsd1BalanceOptions): Promise<Erc20BalanceResult>;
27
+ export declare function fetchFundingTokenBalance(walletAddress: string, options?: FundingTokenBalanceOptions): Promise<Erc20BalanceResult>;
@@ -1,7 +1,6 @@
1
- import { testBsc, BSC_USD1_ADDRESS } from "../constants/chains.js";
1
+ import { getEnv } from "../utils/env";
2
+ import { getFundingTokenAddress, getChainInfo } from "../constants/chains.js";
2
3
  const ERC20_BALANCE_OF_SELECTOR = "0x70a08231";
3
- const USD1_DECIMALS = testBsc.nativeCurrencyDecimals;
4
- const BSC_RPC_URL = testBsc.rpcUrls[0];
5
4
  function padAddress(address) {
6
5
  return address.toLowerCase().replace("0x", "").padStart(64, "0");
7
6
  }
@@ -42,17 +41,20 @@ export async function fetchErc20Balance(rpcUrl, tokenAddress, walletAddress) {
42
41
  return BigInt(json.result ?? "0x0");
43
42
  }
44
43
  /**
45
- * Convenience wrapper fetches the USD1 balance on BSC for a given wallet.
46
- * All parameters have sensible defaults and can be overridden.
44
+ * Fetches the configured funding ERC-20 balance on the selected funding chain.
45
+ * Defaults: chain `3131`, RPC from {@link getChainInfo}, token from {@link getFundingTokenAddress} / env.
47
46
  */
48
- export async function fetchBscUsd1Balance(walletAddress, options) {
49
- const rpcUrl = options?.rpcUrl ?? BSC_RPC_URL;
50
- const tokenAddress = options?.tokenAddress ?? BSC_USD1_ADDRESS;
51
- const decimals = options?.decimals ?? USD1_DECIMALS;
47
+ export async function fetchFundingTokenBalance(walletAddress, options) {
48
+ const chain = getChainInfo(options?.chainId);
49
+ const rpcUrl = options?.rpcUrl ?? chain.rpcUrls[0];
50
+ const tokenAddress = options?.tokenAddress ?? getFundingTokenAddress(options?.chainId);
51
+ const decimals = options?.decimals ?? chain.nativeCurrencyDecimals;
52
+ const FUNDING_TOKEN_SYMBOL = getEnv("FUNDING_TOKEN_SYMBOL");
53
+ const displaySymbol = options?.displaySymbol ?? (FUNDING_TOKEN_SYMBOL || "Funding");
52
54
  const raw = await fetchErc20Balance(rpcUrl, tokenAddress, walletAddress);
53
55
  return {
54
56
  raw,
55
57
  formatted: formatBalanceDisplay(raw, decimals),
56
- symbol: "USD1",
58
+ symbol: displaySymbol,
57
59
  };
58
60
  }
@@ -1,4 +1,5 @@
1
1
  import { sessionStore } from "@ab-org/sdk-core";
2
+ import { getEnv } from "../utils/env";
2
3
  function requireSession() {
3
4
  const session = sessionStore.getState().session;
4
5
  if (!session)
@@ -17,7 +18,7 @@ export const createDepositController = (custody, marketData) => {
17
18
  session.chainContext?.settlementChain ??
18
19
  session.chain ??
19
20
  "AB_CORE";
20
- const token = config?.preferredToken ?? "USD1";
21
+ const token = config?.preferredToken || getEnv("FUNDING_TOKEN_SYMBOL") || "USDT";
21
22
  try {
22
23
  const { address } = await marketData.getDepositAddress(token, chain);
23
24
  const depositId = `${chain}:${token}:${Date.now()}`;
@@ -3,6 +3,6 @@ import type { MarketDataProvider } from "../types.js";
3
3
  * Default `MarketDataProvider` backed by the merchant chains API.
4
4
  * - getSupportedTokens / getSupportedChains: from GET `{merchantBase}/chains`
5
5
  * - getQuote: local estimate until a dedicated quote API exists
6
- * - getDepositAddress: session wallet address + fixed minimum (no deposit-address HTTP API here)
6
+ * - getDepositAddress: 当前 session 钱包地址 + `token`/`chain` 匹配的 `GET /chains` `minimum_deposit`(按 decimals 格式化)
7
7
  */
8
8
  export declare function createMarketDataProvider(): MarketDataProvider;
@@ -1,45 +1,20 @@
1
1
  import { sessionStore } from "@ab-org/sdk-core";
2
- import { getEnv } from "../utils/env.js";
3
- const DEFAULT_MERCHANT_BASE_URL = "https://merchant.tomo.services";
2
+ import { formatUnits } from "viem";
3
+ import { getChains } from "./api.js";
4
4
  let cachedChains = null;
5
- /** Fallback when chains API fails (network error, 4xx/5xx, parse error, missing base URL). */
6
- const DEFAULT_CHAINS_FALLBACK = [
7
- {
8
- chain_id: "56",
9
- network: "BSC",
10
- tokens: [
11
- { symbol: "USD1", address: "0x", decimals: 18 },
12
- { symbol: "USDT", address: "0x", decimals: 6 },
13
- { symbol: "USDC", address: "0x", decimals: 6 },
14
- ],
15
- },
16
- {
17
- chain_id: "ETH",
18
- network: "Ethereum",
19
- tokens: [
20
- { symbol: "USDT", address: "0x", decimals: 6 },
21
- { symbol: "USDC", address: "0x", decimals: 6 },
22
- ],
23
- },
24
- ];
25
5
  async function fetchChainsFromApi() {
26
6
  if (cachedChains)
27
7
  return cachedChains;
28
8
  try {
29
- const CHAINS_API_BASE = getEnv("MERCHANT_BASE_URL") || DEFAULT_MERCHANT_BASE_URL;
30
- const res = await fetch(`${CHAINS_API_BASE}/chains`);
31
- if (!res.ok)
32
- throw new Error(`Chains API error: ${res.status} ${res.statusText}`);
33
- const json = (await res.json());
34
- const chains = json.data?.chains ?? [];
9
+ const { chains } = await getChains();
35
10
  if (chains.length > 0) {
36
11
  cachedChains = chains;
37
12
  return chains;
38
13
  }
39
- return DEFAULT_CHAINS_FALLBACK;
14
+ return [];
40
15
  }
41
16
  catch {
42
- return DEFAULT_CHAINS_FALLBACK;
17
+ return [];
43
18
  }
44
19
  }
45
20
  function chainToChainInfo(chain) {
@@ -48,6 +23,22 @@ function chainToChainInfo(chain) {
48
23
  name: chain.network,
49
24
  };
50
25
  }
26
+ function findTokenInChains(chains, chainId, tokenSymbol) {
27
+ const chain = chains.find((c) => c.chain_id === chainId);
28
+ return chain?.tokens.find((t) => t.symbol === tokenSymbol);
29
+ }
30
+ /** `minimum_deposit` 为链上最小单位整数字符串,按 `decimals` 转成可读金额并拼 token 符号 */
31
+ function formatMinimumDepositDisplay(token, tokenSymbol) {
32
+ const raw = token.minimum_deposit?.trim();
33
+ if (raw == null || raw === "")
34
+ return undefined;
35
+ try {
36
+ return `${formatUnits(BigInt(raw), token.decimals)} ${tokenSymbol}`;
37
+ }
38
+ catch {
39
+ return `${raw} ${tokenSymbol}`;
40
+ }
41
+ }
51
42
  function deriveTokensFromChains(chains) {
52
43
  const bySymbol = new Map();
53
44
  for (const chain of chains) {
@@ -84,7 +75,7 @@ function computeDefaultQuote(request) {
84
75
  * Default `MarketDataProvider` backed by the merchant chains API.
85
76
  * - getSupportedTokens / getSupportedChains: from GET `{merchantBase}/chains`
86
77
  * - getQuote: local estimate until a dedicated quote API exists
87
- * - getDepositAddress: session wallet address + fixed minimum (no deposit-address HTTP API here)
78
+ * - getDepositAddress: 当前 session 钱包地址 + `token`/`chain` 匹配的 `GET /chains` `minimum_deposit`(按 decimals 格式化)
88
79
  */
89
80
  export function createMarketDataProvider() {
90
81
  return {
@@ -102,11 +93,14 @@ export function createMarketDataProvider() {
102
93
  async getQuote(request) {
103
94
  return computeDefaultQuote(request);
104
95
  },
105
- async getDepositAddress(_token, _chain) {
96
+ async getDepositAddress(token, chain) {
106
97
  const session = sessionStore.getState().session;
98
+ const chains = await fetchChainsFromApi();
99
+ const meta = findTokenInChains(chains, chain, token);
100
+ const minimumDeposit = meta != null ? formatMinimumDepositDisplay(meta, token) : undefined;
107
101
  return {
108
102
  address: session?.address ?? "",
109
- minimumDeposit: "15.00",
103
+ minimumDeposit,
110
104
  };
111
105
  },
112
106
  };
@@ -1,4 +1,5 @@
1
1
  import { sessionStore } from "@ab-org/sdk-core";
2
+ import { getEnv } from "../utils/env";
2
3
  function requireSession() {
3
4
  const session = sessionStore.getState().session;
4
5
  if (!session)
@@ -13,7 +14,7 @@ export const createWithdrawController = (custody, marketData) => {
13
14
  };
14
15
  const open = async (config) => {
15
16
  const session = requireSession();
16
- const token = config?.defaultToken ?? "USD1";
17
+ const token = config?.defaultToken || getEnv("FUNDING_TOKEN_SYMBOL") || "USDT";
17
18
  const chain = config?.defaultChain ??
18
19
  session.chainContext?.settlementChain ??
19
20
  session.chain ??
@@ -0,0 +1,14 @@
1
+ import type { ChainData, TokenData } from "./api.js";
2
+ /**
3
+ * funding 链(与 {@link DEFAULT_FUNDING_CHAIN_ID} 一致)上,且 `tokenAddress` 在 Merchant `GET /chains`
4
+ * 中对应条目的 `is_usd_stable === true` 时,由业务层自行完成提现并构造 `withdrawDirectResult`;
5
+ * 其余情况走默认 funding 提现执行器与订单轮询。
6
+ */
7
+ export declare function isUsdtWithdrawDirect(chainId: string, tokenAddress: string, chains: ChainData[]): boolean;
8
+ /**
9
+ * 从 `getChains()` 返回的 `chains` 中,按当前选择的链 id + token symbol 或合约地址解析 `TokenData`。
10
+ */
11
+ export declare function findTokenDataFromChains(chains: ChainData[], chainId: string, opts: {
12
+ symbol: string;
13
+ tokenAddress?: string;
14
+ }): TokenData | undefined;
@@ -0,0 +1,33 @@
1
+ import { DEFAULT_FUNDING_CHAIN_ID } from "../constants/chains.js";
2
+ /**
3
+ * funding 链(与 {@link DEFAULT_FUNDING_CHAIN_ID} 一致)上,且 `tokenAddress` 在 Merchant `GET /chains`
4
+ * 中对应条目的 `is_usd_stable === true` 时,由业务层自行完成提现并构造 `withdrawDirectResult`;
5
+ * 其余情况走默认 funding 提现执行器与订单轮询。
6
+ */
7
+ export function isUsdtWithdrawDirect(chainId, tokenAddress, chains) {
8
+ if (chainId !== String(DEFAULT_FUNDING_CHAIN_ID))
9
+ return false;
10
+ const addr = tokenAddress.trim();
11
+ if (!addr)
12
+ return false;
13
+ const chain = chains.find((c) => c.chain_id === chainId);
14
+ const token = chain?.tokens.find((t) => t.address.toLowerCase() === addr.toLowerCase());
15
+ return token?.is_usd_stable === true;
16
+ }
17
+ /**
18
+ * 从 `getChains()` 返回的 `chains` 中,按当前选择的链 id + token symbol 或合约地址解析 `TokenData`。
19
+ */
20
+ export function findTokenDataFromChains(chains, chainId, opts) {
21
+ const chain = chains.find((c) => c.chain_id === chainId);
22
+ if (!chain?.tokens.length)
23
+ return undefined;
24
+ const sym = opts.symbol.trim();
25
+ const addr = opts.tokenAddress?.trim().toLowerCase();
26
+ return chain.tokens.find((t) => {
27
+ if (addr && t.address.toLowerCase() === addr)
28
+ return true;
29
+ if (sym.length > 0 && t.symbol === sym)
30
+ return true;
31
+ return false;
32
+ });
33
+ }
@@ -9,7 +9,7 @@ export interface WithdrawRequest {
9
9
  chain: string;
10
10
  }
11
11
  export interface WithdrawResult {
12
- /** BSC 上转入一次性地址的 funding tx hash */
12
+ /** Funding 链上转入一次性地址的 tx hash */
13
13
  txHash: string;
14
14
  /** 提现订单 ID,用于轮询 getWithdrawOrder(orderId) */
15
15
  orderId: string;
@@ -30,18 +30,27 @@ export type WithdrawExecutor = (request: WithdrawRequest) => Promise<WithdrawRes
30
30
  * "100.1" → 100100000000000000000n
31
31
  */
32
32
  export declare function parseUnits(value: string, decimals: number): bigint;
33
- export interface BscUsd1WithdrawExecutorOptions {
34
- /** 源代币 USD1 合约地址(提现场景下为 BSC 上的 USD1) */
33
+ export interface FundingWithdrawExecutorOptions {
34
+ /** 源链 funding ERC-20 合约地址;默认 {@link getFundingTokenAddress} / env */
35
35
  tokenAddress?: string;
36
36
  decimals?: number;
37
- chainId?: number;
37
+ /**
38
+ * Funding EVM chain id(如 `3131` Tenderly、`56` 主网)。未传时默认 `3131`。
39
+ */
40
+ chainId?: number | string;
41
+ /** 覆盖 {@link getChainInfo} 提供的 JSON-RPC URL */
42
+ rpcUrl?: string;
38
43
  /** 系统配置的单笔限额(wei 字符串),若提供则校验 request.amount 不得超过此值 */
39
44
  maxAmountWei?: string;
45
+ /**
46
+ * 与商户订单接口约定的 funding 侧 token symbol(默认 `USDT`,按后端协议)。
47
+ */
48
+ fundingLegTokenSymbol?: string;
40
49
  }
41
50
  /**
42
51
  * Factory that returns a `WithdrawExecutor` implementing the flow in withdraw.md:
43
52
  * 1) Create NATIVE_SWAP order → get one-time wallet address (OTW);
44
- * 2) Sign ERC-20 transfer of USD1 on BSC to the OTW (not to user);
53
+ * 2) Sign ERC-20 transfer of the funding token on the funding chain to the OTW (not to user);
45
54
  * 3) Broadcast tx and return { txHash, orderId } for the client to poll getWithdrawOrder(orderId).
46
55
  */
47
- export declare function createBscUsd1WithdrawExecutor(options?: BscUsd1WithdrawExecutorOptions): WithdrawExecutor;
56
+ export declare function createFundingWithdrawExecutor(options?: FundingWithdrawExecutorOptions): WithdrawExecutor;
@@ -2,12 +2,11 @@ import { sessionStore } from "@ab-org/sdk-core";
2
2
  import { tryAutoReconnect } from "../auth/autoReconnect.js";
3
3
  import { getChains, createOrder } from "./api.js";
4
4
  import { fromHex, parseGwei, toHex } from "viem";
5
- import { testBsc, BSC_USD1_ADDRESS } from "../constants/chains.js";
5
+ import { getFundingTokenAddress, getChainInfo } from "../constants/chains.js";
6
+ import { getEnv } from "../utils/env";
6
7
  const MAX_WITHDRAW_GAS_LIMIT = 500000n;
7
- const BSC_RPC_URL = testBsc.rpcUrls[0];
8
8
  /* ─── Internal helpers ────────────────────────── */
9
9
  const ERC20_TRANSFER_SELECTOR = "0xa9059cbb";
10
- const USD1_DECIMALS = testBsc.nativeCurrencyDecimals;
11
10
  function padHex256(value) {
12
11
  return value.toString(16).padStart(64, "0");
13
12
  }
@@ -82,13 +81,13 @@ async function requestHexQuantity(provider, rpcUrl, method, params) {
82
81
  return toHexQuantity(await callRpc(rpcUrl, method, params));
83
82
  }
84
83
  }
85
- async function ensureBscChain(provider, chainId) {
84
+ async function ensureFundingEvmChain(provider, chainId) {
86
85
  const hex = `0x${chainId.toString(16)}`;
87
86
  try {
88
87
  await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: hex }] });
89
88
  }
90
89
  catch {
91
- // Ignored – the wallet may already be on BSC or may not support switching.
90
+ // Ignored – the wallet may already be on the funding chain or may not support switching.
92
91
  }
93
92
  }
94
93
  function getDstTokenAddress(chains, chainId, tokenSymbol) {
@@ -98,14 +97,17 @@ function getDstTokenAddress(chains, chainId, tokenSymbol) {
98
97
  /**
99
98
  * Factory that returns a `WithdrawExecutor` implementing the flow in withdraw.md:
100
99
  * 1) Create NATIVE_SWAP order → get one-time wallet address (OTW);
101
- * 2) Sign ERC-20 transfer of USD1 on BSC to the OTW (not to user);
100
+ * 2) Sign ERC-20 transfer of the funding token on the funding chain to the OTW (not to user);
102
101
  * 3) Broadcast tx and return { txHash, orderId } for the client to poll getWithdrawOrder(orderId).
103
102
  */
104
- export function createBscUsd1WithdrawExecutor(options) {
105
- const tokenAddress = options?.tokenAddress ?? BSC_USD1_ADDRESS;
106
- const decimals = options?.decimals ?? USD1_DECIMALS;
103
+ export function createFundingWithdrawExecutor(options) {
104
+ const fundingChain = getChainInfo(options?.chainId);
105
+ const chainIdNum = Number(fundingChain.chainId);
106
+ const rpcUrl = options?.rpcUrl ?? fundingChain.rpcUrls[0];
107
+ const tokenAddress = options?.tokenAddress ?? getFundingTokenAddress(options?.chainId);
108
+ const decimals = options?.decimals ?? fundingChain.nativeCurrencyDecimals;
107
109
  const maxAmountWei = options?.maxAmountWei;
108
- const chainId = options?.chainId ?? Number(testBsc.chainId);
110
+ const fundingLegTokenSymbol = options?.fundingLegTokenSymbol || getEnv("FUNDING_TOKEN_SYMBOL") || "USDT";
109
111
  return async (request) => {
110
112
  const amountWei = parseUnits(request.amount, decimals);
111
113
  const amountWeiStr = amountWei.toString();
@@ -139,13 +141,13 @@ export function createBscUsd1WithdrawExecutor(options) {
139
141
  if (!dstTokenAddress) {
140
142
  throw new Error(`Unsupported token ${request.token} on chain ${request.chain}`);
141
143
  }
142
- // 校验规则:源代币或目标代币中必须有一个是 USD1。提现场景下源代币(token_address)为 USD1;token_amount 已按 maxAmountWei 做单笔限额校验
143
- const sourceTokenSymbol = "USD1";
144
+ // 校验规则:源/目标代币需满足商户约定;提现场景下源链为 funding token;token_amount 已按 maxAmountWei 做单笔限额校验
145
+ const sourceTokenSymbol = fundingLegTokenSymbol;
144
146
  const orderRes = await createOrder({
145
147
  intent_id: `withdraw-${Date.now()}`,
146
148
  order_type: "NATIVE_SWAP",
147
149
  order_payload: {
148
- chain_id: String(chainId),
150
+ chain_id: fundingChain.chainId,
149
151
  token_address: tokenAddress,
150
152
  token_amount: amountWeiStr,
151
153
  dst_chain_id: request.chain,
@@ -158,7 +160,7 @@ export function createBscUsd1WithdrawExecutor(options) {
158
160
  token_amount: amountWeiStr,
159
161
  token_address: tokenAddress,
160
162
  user_address: session.address,
161
- chain_id: String(chainId),
163
+ chain_id: fundingChain.chainId,
162
164
  },
163
165
  ],
164
166
  });
@@ -166,18 +168,18 @@ export function createBscUsd1WithdrawExecutor(options) {
166
168
  if (!oneTimeAddress) {
167
169
  throw new Error("Order created but no one-time wallet address returned");
168
170
  }
169
- await ensureBscChain(provider, chainId);
171
+ await ensureFundingEvmChain(provider, chainIdNum);
170
172
  const data = encodeTransferData(oneTimeAddress, amountWei);
171
- const nonce = await requestHexQuantity(provider, BSC_RPC_URL, "eth_getTransactionCount", [session.address, "latest"]);
173
+ const nonce = await requestHexQuantity(provider, rpcUrl, "eth_getTransactionCount", [session.address, "latest"]);
172
174
  const tx = {
173
175
  from: session.address,
174
176
  to: tokenAddress,
175
177
  value: "0x0",
176
178
  nonce,
177
179
  data,
178
- chainId: toHex(chainId),
180
+ chainId: toHex(chainIdNum),
179
181
  };
180
- const estimatedGasHex = await requestHexQuantity(provider, BSC_RPC_URL, "eth_estimateGas", [tx]);
182
+ const estimatedGasHex = await requestHexQuantity(provider, rpcUrl, "eth_estimateGas", [tx]);
181
183
  const estimatedGas = fromHex(estimatedGasHex, "bigint");
182
184
  const gas = toHex(estimatedGas > MAX_WITHDRAW_GAS_LIMIT ? MAX_WITHDRAW_GAS_LIMIT : estimatedGas);
183
185
  const transaction = {
@@ -201,8 +203,8 @@ export function createBscUsd1WithdrawExecutor(options) {
201
203
  method: "eth_signTransaction",
202
204
  params: [transaction],
203
205
  });
204
- txHash = await callRpc(BSC_RPC_URL, "eth_sendRawTransaction", [signedTx]);
206
+ txHash = await callRpc(rpcUrl, "eth_sendRawTransaction", [signedTx]);
205
207
  }
206
- return { txHash, orderId: orderRes.order_id, fundingChainId: String(chainId) };
208
+ return { txHash, orderId: orderRes.order_id, fundingChainId: fundingChain.chainId };
207
209
  };
208
210
  }
@@ -27,7 +27,7 @@ export interface DepositModalProps {
27
27
  explorerTxUrl?: (chainId: string, txHash: string) => string;
28
28
  onTokenSelect?: (id: string) => void;
29
29
  onChainSelect?: (id: string) => void;
30
- onCopyAddress?: () => void;
30
+ onCopyAddress?: (address: string) => void;
31
31
  onBuyCrypto?: () => void;
32
32
  onSignIn?: () => void;
33
33
  onBack?: () => void;