@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 +12 -2
- package/dist/constants/chains.d.ts +2 -22
- package/dist/constants/chains.js +3 -23
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/modules/api.d.ts +13 -8
- package/dist/modules/api.js +6 -4
- package/dist/modules/balanceQuery.d.ts +11 -4
- package/dist/modules/balanceQuery.js +12 -10
- package/dist/modules/deposit.js +2 -1
- package/dist/modules/marketData.d.ts +1 -1
- package/dist/modules/marketData.js +27 -33
- package/dist/modules/withdraw.js +2 -1
- package/dist/modules/withdrawDirect.d.ts +14 -0
- package/dist/modules/withdrawDirect.js +33 -0
- package/dist/modules/withdrawExecutor.d.ts +15 -6
- package/dist/modules/withdrawExecutor.js +22 -20
- package/dist/ui/DepositModal.d.ts +1 -1
- package/dist/ui/DepositModal.js +46 -18
- package/dist/ui/WithdrawModal.d.ts +12 -2
- package/dist/ui/WithdrawModal.js +101 -55
- package/dist/ui/components/DepositDetailsPanel.d.ts +1 -1
- package/dist/ui/components/DepositDetailsPanel.js +31 -5
- package/dist/ui/signInTypes.d.ts +3 -0
- package/dist/ui/useSignInModalController.js +1 -36
- package/dist/utils/env.js +2 -0
- package/dist/walletUtils.d.ts +1 -1
- package/dist/walletUtils.js +1 -1
- package/package.json +3 -2
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
|
|
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: "
|
|
254
|
+
defaultToken: "USDT",
|
|
245
255
|
defaultChain: "AB_CORE",
|
|
246
256
|
targetAddress: "0x...",
|
|
247
257
|
defaultAmount: "100",
|
|
@@ -1,22 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
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. */
|
package/dist/constants/chains.js
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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";
|
package/dist/modules/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Merchant API client
|
|
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
|
-
|
|
40
|
+
interface QuoteRequest {
|
|
37
41
|
direction: QuoteDirection;
|
|
38
42
|
chain_id: string;
|
|
39
43
|
token_address: string;
|
|
40
44
|
token_amount?: string;
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
/** Fee (e.g. "0.01
|
|
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
|
|
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 {};
|
package/dist/modules/api.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Merchant API client
|
|
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")
|
|
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
|
|
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
|
|
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
|
-
*
|
|
18
|
-
*
|
|
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
|
|
27
|
+
export declare function fetchFundingTokenBalance(walletAddress: string, options?: FundingTokenBalanceOptions): Promise<Erc20BalanceResult>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
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
|
-
*
|
|
46
|
-
*
|
|
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
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
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:
|
|
58
|
+
symbol: displaySymbol,
|
|
57
59
|
};
|
|
58
60
|
}
|
package/dist/modules/deposit.js
CHANGED
|
@@ -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
|
|
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
|
|
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 {
|
|
3
|
-
|
|
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
|
|
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
|
|
14
|
+
return [];
|
|
40
15
|
}
|
|
41
16
|
catch {
|
|
42
|
-
return
|
|
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
|
|
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(
|
|
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
|
|
103
|
+
minimumDeposit,
|
|
110
104
|
};
|
|
111
105
|
},
|
|
112
106
|
};
|
package/dist/modules/withdraw.js
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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
|
|
34
|
-
/**
|
|
33
|
+
export interface FundingWithdrawExecutorOptions {
|
|
34
|
+
/** 源链 funding ERC-20 合约地址;默认 {@link getFundingTokenAddress} / env */
|
|
35
35
|
tokenAddress?: string;
|
|
36
36
|
decimals?: number;
|
|
37
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
105
|
-
const
|
|
106
|
-
const
|
|
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
|
|
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
|
-
//
|
|
143
|
-
const sourceTokenSymbol =
|
|
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:
|
|
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:
|
|
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
|
|
171
|
+
await ensureFundingEvmChain(provider, chainIdNum);
|
|
170
172
|
const data = encodeTransferData(oneTimeAddress, amountWei);
|
|
171
|
-
const nonce = await requestHexQuantity(provider,
|
|
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(
|
|
180
|
+
chainId: toHex(chainIdNum),
|
|
179
181
|
};
|
|
180
|
-
const estimatedGasHex = await requestHexQuantity(provider,
|
|
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(
|
|
206
|
+
txHash = await callRpc(rpcUrl, "eth_sendRawTransaction", [signedTx]);
|
|
205
207
|
}
|
|
206
|
-
return { txHash, orderId: orderRes.order_id, fundingChainId:
|
|
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;
|