@ab-org/predicate-market-sdk 0.0.2 → 0.1.0
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 +10 -0
- 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 +12 -7
- 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/marketData.d.ts +1 -1
- package/dist/modules/marketData.js +27 -33
- 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 +21 -20
- package/dist/ui/DepositModal.js +7 -14
- package/dist/ui/WithdrawModal.d.ts +12 -2
- package/dist/ui/WithdrawModal.js +101 -55
- 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 +2 -1
package/README.md
CHANGED
|
@@ -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 **`NEXT_PUBLIC_BSC_USD1_ADDRESS`** / **`BSC_USD1_ADDRESS`** is set (checksummed `0x` + 40 hex), that value overrides defaults for **all** chains; otherwise returns **`getChainInfo(chainId).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`
|
|
@@ -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,7 +74,7 @@ export interface WithdrawOrderResponseData {
|
|
|
70
74
|
status: WithdrawOrderStatus;
|
|
71
75
|
one_time_address?: string;
|
|
72
76
|
chain_id: string;
|
|
73
|
-
|
|
77
|
+
dst_token_amount: string;
|
|
74
78
|
/** 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 "—". */
|
|
75
79
|
fee?: string;
|
|
76
80
|
target_chain_id: 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
|
}
|
|
@@ -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
|
};
|
|
@@ -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(默认 `USD1`,按后端协议)。
|
|
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,10 @@ 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
6
|
const MAX_WITHDRAW_GAS_LIMIT = 500000n;
|
|
7
|
-
const BSC_RPC_URL = testBsc.rpcUrls[0];
|
|
8
7
|
/* ─── Internal helpers ────────────────────────── */
|
|
9
8
|
const ERC20_TRANSFER_SELECTOR = "0xa9059cbb";
|
|
10
|
-
const USD1_DECIMALS = testBsc.nativeCurrencyDecimals;
|
|
11
9
|
function padHex256(value) {
|
|
12
10
|
return value.toString(16).padStart(64, "0");
|
|
13
11
|
}
|
|
@@ -82,13 +80,13 @@ async function requestHexQuantity(provider, rpcUrl, method, params) {
|
|
|
82
80
|
return toHexQuantity(await callRpc(rpcUrl, method, params));
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
|
-
async function
|
|
83
|
+
async function ensureFundingEvmChain(provider, chainId) {
|
|
86
84
|
const hex = `0x${chainId.toString(16)}`;
|
|
87
85
|
try {
|
|
88
86
|
await provider.request({ method: "wallet_switchEthereumChain", params: [{ chainId: hex }] });
|
|
89
87
|
}
|
|
90
88
|
catch {
|
|
91
|
-
// Ignored – the wallet may already be on
|
|
89
|
+
// Ignored – the wallet may already be on the funding chain or may not support switching.
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
function getDstTokenAddress(chains, chainId, tokenSymbol) {
|
|
@@ -98,14 +96,17 @@ function getDstTokenAddress(chains, chainId, tokenSymbol) {
|
|
|
98
96
|
/**
|
|
99
97
|
* Factory that returns a `WithdrawExecutor` implementing the flow in withdraw.md:
|
|
100
98
|
* 1) Create NATIVE_SWAP order → get one-time wallet address (OTW);
|
|
101
|
-
* 2) Sign ERC-20 transfer of
|
|
99
|
+
* 2) Sign ERC-20 transfer of the funding token on the funding chain to the OTW (not to user);
|
|
102
100
|
* 3) Broadcast tx and return { txHash, orderId } for the client to poll getWithdrawOrder(orderId).
|
|
103
101
|
*/
|
|
104
|
-
export function
|
|
105
|
-
const
|
|
106
|
-
const
|
|
102
|
+
export function createFundingWithdrawExecutor(options) {
|
|
103
|
+
const fundingChain = getChainInfo(options?.chainId);
|
|
104
|
+
const chainIdNum = Number(fundingChain.chainId);
|
|
105
|
+
const rpcUrl = options?.rpcUrl ?? fundingChain.rpcUrls[0];
|
|
106
|
+
const tokenAddress = options?.tokenAddress ?? getFundingTokenAddress(options?.chainId);
|
|
107
|
+
const decimals = options?.decimals ?? fundingChain.nativeCurrencyDecimals;
|
|
107
108
|
const maxAmountWei = options?.maxAmountWei;
|
|
108
|
-
const
|
|
109
|
+
const fundingLegTokenSymbol = options?.fundingLegTokenSymbol ?? "USD1";
|
|
109
110
|
return async (request) => {
|
|
110
111
|
const amountWei = parseUnits(request.amount, decimals);
|
|
111
112
|
const amountWeiStr = amountWei.toString();
|
|
@@ -139,13 +140,13 @@ export function createBscUsd1WithdrawExecutor(options) {
|
|
|
139
140
|
if (!dstTokenAddress) {
|
|
140
141
|
throw new Error(`Unsupported token ${request.token} on chain ${request.chain}`);
|
|
141
142
|
}
|
|
142
|
-
//
|
|
143
|
-
const sourceTokenSymbol =
|
|
143
|
+
// 校验规则:源/目标代币需满足商户约定;提现场景下源链为 funding token;token_amount 已按 maxAmountWei 做单笔限额校验
|
|
144
|
+
const sourceTokenSymbol = fundingLegTokenSymbol;
|
|
144
145
|
const orderRes = await createOrder({
|
|
145
146
|
intent_id: `withdraw-${Date.now()}`,
|
|
146
147
|
order_type: "NATIVE_SWAP",
|
|
147
148
|
order_payload: {
|
|
148
|
-
chain_id:
|
|
149
|
+
chain_id: fundingChain.chainId,
|
|
149
150
|
token_address: tokenAddress,
|
|
150
151
|
token_amount: amountWeiStr,
|
|
151
152
|
dst_chain_id: request.chain,
|
|
@@ -158,7 +159,7 @@ export function createBscUsd1WithdrawExecutor(options) {
|
|
|
158
159
|
token_amount: amountWeiStr,
|
|
159
160
|
token_address: tokenAddress,
|
|
160
161
|
user_address: session.address,
|
|
161
|
-
chain_id:
|
|
162
|
+
chain_id: fundingChain.chainId,
|
|
162
163
|
},
|
|
163
164
|
],
|
|
164
165
|
});
|
|
@@ -166,18 +167,18 @@ export function createBscUsd1WithdrawExecutor(options) {
|
|
|
166
167
|
if (!oneTimeAddress) {
|
|
167
168
|
throw new Error("Order created but no one-time wallet address returned");
|
|
168
169
|
}
|
|
169
|
-
await
|
|
170
|
+
await ensureFundingEvmChain(provider, chainIdNum);
|
|
170
171
|
const data = encodeTransferData(oneTimeAddress, amountWei);
|
|
171
|
-
const nonce = await requestHexQuantity(provider,
|
|
172
|
+
const nonce = await requestHexQuantity(provider, rpcUrl, "eth_getTransactionCount", [session.address, "latest"]);
|
|
172
173
|
const tx = {
|
|
173
174
|
from: session.address,
|
|
174
175
|
to: tokenAddress,
|
|
175
176
|
value: "0x0",
|
|
176
177
|
nonce,
|
|
177
178
|
data,
|
|
178
|
-
chainId: toHex(
|
|
179
|
+
chainId: toHex(chainIdNum),
|
|
179
180
|
};
|
|
180
|
-
const estimatedGasHex = await requestHexQuantity(provider,
|
|
181
|
+
const estimatedGasHex = await requestHexQuantity(provider, rpcUrl, "eth_estimateGas", [tx]);
|
|
181
182
|
const estimatedGas = fromHex(estimatedGasHex, "bigint");
|
|
182
183
|
const gas = toHex(estimatedGas > MAX_WITHDRAW_GAS_LIMIT ? MAX_WITHDRAW_GAS_LIMIT : estimatedGas);
|
|
183
184
|
const transaction = {
|
|
@@ -201,8 +202,8 @@ export function createBscUsd1WithdrawExecutor(options) {
|
|
|
201
202
|
method: "eth_signTransaction",
|
|
202
203
|
params: [transaction],
|
|
203
204
|
});
|
|
204
|
-
txHash = await callRpc(
|
|
205
|
+
txHash = await callRpc(rpcUrl, "eth_sendRawTransaction", [signedTx]);
|
|
205
206
|
}
|
|
206
|
-
return { txHash, orderId: orderRes.order_id, fundingChainId:
|
|
207
|
+
return { txHash, orderId: orderRes.order_id, fundingChainId: fundingChain.chainId };
|
|
207
208
|
};
|
|
208
209
|
}
|
package/dist/ui/DepositModal.js
CHANGED
|
@@ -9,6 +9,7 @@ import { LoginRequiredOverlay } from "./components/LoginRequiredOverlay.js";
|
|
|
9
9
|
import { useSession } from "./hooks/useSession.js";
|
|
10
10
|
import { colors, fonts, radii } from "./theme.js";
|
|
11
11
|
import { getChains, quote, } from "../modules/api.js";
|
|
12
|
+
import { getEnv } from "../utils/env";
|
|
12
13
|
/** 校验是否为合法的充值地址(传入值):非空且长度满足常见链地址格式 */
|
|
13
14
|
function isValidDepositAddress(v) {
|
|
14
15
|
return typeof v === "string" && v.trim().length >= 20;
|
|
@@ -43,8 +44,6 @@ const DefaultCryptoIcons = () => {
|
|
|
43
44
|
}, children: t.label }, i))) }));
|
|
44
45
|
};
|
|
45
46
|
/* ─── Helpers: derive options from getChains ─── */
|
|
46
|
-
/** Deposit 时不可选 USD1(充值是转入 USD1,源代币为 USDT/USDC 等) */
|
|
47
|
-
const DEPOSIT_EXCLUDED_TOKEN = "USD1";
|
|
48
47
|
function chainsToTokenOptions(chains) {
|
|
49
48
|
const bySymbol = new Map();
|
|
50
49
|
for (const c of chains) {
|
|
@@ -54,7 +53,6 @@ function chainsToTokenOptions(chains) {
|
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
return Array.from(bySymbol.entries())
|
|
57
|
-
.filter(([id]) => id !== DEPOSIT_EXCLUDED_TOKEN)
|
|
58
56
|
.map(([id, { symbol }]) => ({
|
|
59
57
|
id,
|
|
60
58
|
label: symbol,
|
|
@@ -88,6 +86,8 @@ function getTokenAddressForChain(chains, chainId, tokenSymbol) {
|
|
|
88
86
|
const chain = chains.find((c) => c.chain_id === chainId);
|
|
89
87
|
return chain?.tokens.find((t) => t.symbol === tokenSymbol)?.address;
|
|
90
88
|
}
|
|
89
|
+
/* ─── Main Component ─────────────────────────── */
|
|
90
|
+
const FUNDING_TOKEN_SYMBOL = getEnv("FUNDING_TOKEN_SYMBOL");
|
|
91
91
|
export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, chainOptions: chainOptionsProp, depositAddress, minimumDeposit, qrCenterIcon, cryptoIcons, depositAmount, onShowToast, txHash, explorerTxUrl, onTokenSelect, onChainSelect, onCopyAddress, onBuyCrypto, onSignIn, onBack, onClose, }) => {
|
|
92
92
|
const session = useSession();
|
|
93
93
|
const [view, setView] = useState("entry");
|
|
@@ -98,9 +98,8 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
|
|
|
98
98
|
const [loadingQuote, setLoadingQuote] = useState(false);
|
|
99
99
|
const [quoteRefreshKey, setQuoteRefreshKey] = useState(0);
|
|
100
100
|
const tokenOptions = useMemo(() => {
|
|
101
|
-
const excludeUsd1 = (opts) => opts.filter((o) => o.id !== DEPOSIT_EXCLUDED_TOKEN && o.label !== DEPOSIT_EXCLUDED_TOKEN);
|
|
102
101
|
if (tokenOptionsProp?.length)
|
|
103
|
-
return
|
|
102
|
+
return tokenOptionsProp;
|
|
104
103
|
if (!apiChains?.length)
|
|
105
104
|
return undefined;
|
|
106
105
|
return chainsToTokenOptions(apiChains);
|
|
@@ -112,12 +111,6 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
|
|
|
112
111
|
return undefined;
|
|
113
112
|
return chainsToChainOptionsForToken(apiChains, token);
|
|
114
113
|
}, [chainOptionsProp, apiChains, token]);
|
|
115
|
-
// 当前选中的是 USD1 时自动切到第一个可选 token(deposit 不允许选 USD1)
|
|
116
|
-
useEffect(() => {
|
|
117
|
-
if (token !== DEPOSIT_EXCLUDED_TOKEN || !tokenOptions?.length || !onTokenSelect)
|
|
118
|
-
return;
|
|
119
|
-
onTokenSelect(tokenOptions[0].id);
|
|
120
|
-
}, [token, tokenOptions, onTokenSelect]);
|
|
121
114
|
// 仅有一个 chain 选项时默认选中
|
|
122
115
|
useEffect(() => {
|
|
123
116
|
if (chainOptions?.length !== 1 || !onChainSelect)
|
|
@@ -166,7 +159,7 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
|
|
|
166
159
|
rate: "1",
|
|
167
160
|
chain_id: Number(chain) || 56,
|
|
168
161
|
deposit_address: "0x" + "0".repeat(39) + "1",
|
|
169
|
-
|
|
162
|
+
dst_token_amount: depositAmount ?? "0",
|
|
170
163
|
expires_at: new Date(Date.now() + 60000).toISOString(),
|
|
171
164
|
});
|
|
172
165
|
})
|
|
@@ -227,7 +220,7 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
|
|
|
227
220
|
rate: "1",
|
|
228
221
|
chain_id: Number(chain) || 56,
|
|
229
222
|
deposit_address: "0x" + "0".repeat(39) + "1",
|
|
230
|
-
|
|
223
|
+
dst_token_amount: depositAmount ?? "0",
|
|
231
224
|
expires_at: new Date(Date.now() + 60000).toISOString(),
|
|
232
225
|
});
|
|
233
226
|
})
|
|
@@ -302,7 +295,7 @@ const TransferView = ({ token, chain, tokenOptions, chainOptions, depositAddress
|
|
|
302
295
|
display: "flex",
|
|
303
296
|
flexDirection: "column",
|
|
304
297
|
gap: 8,
|
|
305
|
-
}, children: quoteLoading ? (_jsx("span", { style: { fontSize: 13, color: colors.textSecondary }, children: "Loading\u2026" })) : (_jsxs(_Fragment, { children: [_jsxs("span", { style: { fontSize: 13, color: colors.textSecondary }, children: ["1 ", quoteData.token_symbol, " = ", quoteData.rate, "
|
|
298
|
+
}, children: quoteLoading ? (_jsx("span", { style: { fontSize: 13, color: colors.textSecondary }, children: "Loading\u2026" })) : (_jsxs(_Fragment, { children: [_jsxs("span", { style: { fontSize: 13, color: colors.textSecondary }, children: ["1 ", quoteData.token_symbol, " = ", quoteData.rate, " ", FUNDING_TOKEN_SYMBOL] }), quoteData.expires_at && (_jsx(Countdown, { expiresAt: quoteData.expires_at, isExpired: quoteExpired, onExpired: onQuoteExpired })), quoteExpired && (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: { fontSize: 13, color: "#f59e0b" }, children: "Quote expired, please refresh" }), onRefreshQuote && (_jsx("button", { type: "button", onClick: onRefreshQuote, style: {
|
|
306
299
|
padding: "4px 12px",
|
|
307
300
|
fontSize: 12,
|
|
308
301
|
borderRadius: radii.pill,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ChangeEventHandler } from "react";
|
|
2
2
|
import { type SelectOption } from "./components/DropdownField.js";
|
|
3
|
+
import { type WithdrawOrderResponseData } from "../modules/api.js";
|
|
3
4
|
export type WithdrawUiStatus = "idle" | "pending" | "success" | "manual_review";
|
|
4
5
|
export interface WithdrawModalProps {
|
|
5
6
|
address?: string;
|
|
@@ -18,6 +19,14 @@ export interface WithdrawModalProps {
|
|
|
18
19
|
useMerchantApi?: boolean;
|
|
19
20
|
/** 创建订单后传入,用于轮询提现订单状态 */
|
|
20
21
|
orderId?: string;
|
|
22
|
+
/** 提现模式:direct 直接提现,cross_chain 跨链提现 */
|
|
23
|
+
withdrawMode?: "direct" | "cross_chain";
|
|
24
|
+
/**
|
|
25
|
+
* 直接提现模式(无 orderId)下的结果数据;
|
|
26
|
+
* 将用来构造一个与 getWithdrawOrder 返回值兼容的对象,从而复用“有 orderId”时的全部交互与展示。
|
|
27
|
+
* 最少建议提供:status、dst_token_amount、target_chain_id、target_address(必要时包含 dst_tx_hash / out_tx_hash、fee)。
|
|
28
|
+
*/
|
|
29
|
+
withdrawDirectResult?: Partial<WithdrawOrderResponseData>;
|
|
21
30
|
/** 成功页 Fee 展示:优先用订单接口返回的 fee,若后端未返回则用此值(如询价时的 fee),均无则显示 "—" */
|
|
22
31
|
feeDisplay?: string;
|
|
23
32
|
/** 广播后的 funding tx 所在链 id,与 txHash 一起用于展示「查看交易」链接 */
|
|
@@ -30,11 +39,12 @@ export interface WithdrawModalProps {
|
|
|
30
39
|
onChainSelect?: (id: string) => void;
|
|
31
40
|
onAmountChange?: ChangeEventHandler<HTMLInputElement>;
|
|
32
41
|
onMaxClick?: () => void;
|
|
33
|
-
/** 提交时传入当前表单值(收款地址、金额、所选 token
|
|
42
|
+
/** 提交时传入当前表单值(收款地址、金额、所选 token、链上合约地址、所选 chain),由调用方执行提现 */
|
|
34
43
|
onSubmit?: (payload: {
|
|
35
44
|
toAddress: string;
|
|
36
45
|
amount: string;
|
|
37
46
|
token: string;
|
|
47
|
+
tokenAddress: string;
|
|
38
48
|
chain: string;
|
|
39
49
|
}) => void;
|
|
40
50
|
/** 提现订单状态变为 completed 时调用,用于调用方刷新余额等 */
|
|
@@ -44,4 +54,4 @@ export interface WithdrawModalProps {
|
|
|
44
54
|
onSignIn?: () => void;
|
|
45
55
|
onClose?: () => void;
|
|
46
56
|
}
|
|
47
|
-
export declare const WithdrawModal: ({ address, token, tokenSymbol, chain, amount, balance, status, receiveAmount: receiveAmountProp, txHash, tokenOptions: tokenOptionsProp, chainOptions: chainOptionsProp, useMerchantApi, orderId, feeDisplay, fundingChainId,
|
|
57
|
+
export declare const WithdrawModal: ({ address, token, tokenSymbol, chain, amount, balance, status, receiveAmount: receiveAmountProp, txHash, tokenOptions: tokenOptionsProp, chainOptions: chainOptionsProp, useMerchantApi, orderId, withdrawMode, withdrawDirectResult, feeDisplay, fundingChainId, onShowToast, onAddressChange, onTokenSelect, onChainSelect, onAmountChange, onMaxClick, onSubmit, onWithdrawCompleted, onStartAnotherWithdrawal, onSignIn, onClose, }: WithdrawModalProps) => import("react/jsx-runtime.js").JSX.Element;
|
package/dist/ui/WithdrawModal.js
CHANGED
|
@@ -8,11 +8,13 @@ import { LoginRequiredOverlay } from "./components/LoginRequiredOverlay.js";
|
|
|
8
8
|
import { useSession } from "./hooks/useSession.js";
|
|
9
9
|
import { colors, fonts, radii } from "./theme.js";
|
|
10
10
|
import { getChains, quote, getWithdrawOrder, } from "../modules/api.js";
|
|
11
|
+
import { getEnv } from "../utils/env";
|
|
11
12
|
import { getExplorerUrl } from "../utils/explorer.js";
|
|
12
13
|
import { SuccessIcon } from "./components/Success";
|
|
13
14
|
function CopyIcon() {
|
|
14
15
|
return (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: [_jsx("rect", { x: "5", y: "5", width: "9", height: "9", rx: "1", stroke: "currentColor", strokeWidth: "1.2", fill: "none" }), _jsx("path", { d: "M3 11V3a1 1 0 0 1 1-1h8", stroke: "currentColor", strokeWidth: "1.2", fill: "none" })] }));
|
|
15
16
|
}
|
|
17
|
+
const FUNDING_TOKEN_SYMBOL = getEnv("FUNDING_TOKEN_SYMBOL");
|
|
16
18
|
function chainsToTokenOptions(chains) {
|
|
17
19
|
const bySymbol = new Map();
|
|
18
20
|
for (const c of chains) {
|
|
@@ -36,7 +38,7 @@ function getTokenAddressForChain(chains, chainId, tokenSymbol) {
|
|
|
36
38
|
const chain = chains.find((c) => c.chain_id === chainId);
|
|
37
39
|
return chain?.tokens.find((t) => t.symbol === tokenSymbol)?.address;
|
|
38
40
|
}
|
|
39
|
-
/** 从 balance 字符串解析出数值部分(如 "123.46
|
|
41
|
+
/** 从 balance 字符串解析出数值部分(如 "123.46 xxx" → 123.46) */
|
|
40
42
|
function parseBalanceNumber(balance) {
|
|
41
43
|
const match = balance.trim().match(/^(\d*\.?\d*)/);
|
|
42
44
|
if (!match)
|
|
@@ -44,7 +46,7 @@ function parseBalanceNumber(balance) {
|
|
|
44
46
|
const n = Number(match[1]);
|
|
45
47
|
return Number.isNaN(n) ? null : n;
|
|
46
48
|
}
|
|
47
|
-
/** 将 balance 字符串格式化为小数点后 2 位(如 "123.456789012
|
|
49
|
+
/** 将 balance 字符串格式化为小数点后 2 位(如 "123.456789012 xxx" → "123.46 xxx") */
|
|
48
50
|
function formatBalanceTo2Decimals(balance) {
|
|
49
51
|
const match = balance.trim().match(/^(\d*\.?\d*)(.*)$/);
|
|
50
52
|
if (!match)
|
|
@@ -57,7 +59,6 @@ function formatBalanceTo2Decimals(balance) {
|
|
|
57
59
|
const formatted = n.toFixed(2);
|
|
58
60
|
return formatted + suffix;
|
|
59
61
|
}
|
|
60
|
-
const WEI_PER_ETHER = 1e18;
|
|
61
62
|
/** Wei (string) to ether display string; keeps up to 6 decimal places, strips trailing zeros */
|
|
62
63
|
function weiToEtherDisplay(wei) {
|
|
63
64
|
const s = (wei || "0").trim().replace(/^0+/, "") || "0";
|
|
@@ -74,7 +75,7 @@ function weiToEtherDisplay(wei) {
|
|
|
74
75
|
return fixed;
|
|
75
76
|
}
|
|
76
77
|
const POLL_INTERVAL_MS = 4000;
|
|
77
|
-
export const WithdrawModal = ({ address = "", token, tokenSymbol
|
|
78
|
+
export const WithdrawModal = ({ address = "", token, tokenSymbol, chain, amount = "", balance, status = "idle", receiveAmount: receiveAmountProp, txHash, tokenOptions: tokenOptionsProp, chainOptions: chainOptionsProp, useMerchantApi = false, orderId, withdrawMode, withdrawDirectResult, feeDisplay, fundingChainId, onShowToast, onAddressChange, onTokenSelect, onChainSelect, onAmountChange, onMaxClick, onSubmit, onWithdrawCompleted, onStartAnotherWithdrawal, onSignIn, onClose, }) => {
|
|
78
79
|
const session = useSession();
|
|
79
80
|
const addressInputRef = useRef(null);
|
|
80
81
|
const [addressInputFocused, setAddressInputFocused] = useState(false);
|
|
@@ -100,7 +101,7 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
100
101
|
: undefined;
|
|
101
102
|
if (!raw?.length)
|
|
102
103
|
return undefined;
|
|
103
|
-
return raw
|
|
104
|
+
return raw;
|
|
104
105
|
}, [tokenOptionsProp, apiChains]);
|
|
105
106
|
const chainOptions = useMemo(() => {
|
|
106
107
|
if (chainOptionsProp?.length)
|
|
@@ -109,7 +110,19 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
109
110
|
return undefined;
|
|
110
111
|
return chainsToChainOptionsForToken(apiChains, token);
|
|
111
112
|
}, [chainOptionsProp, apiChains, token]);
|
|
112
|
-
|
|
113
|
+
/** 当前选中的 token + chain 在 getChains 数据中的合约地址(与询价 / quote 使用同一解析方式) */
|
|
114
|
+
const resolvedTokenAddress = useMemo(() => {
|
|
115
|
+
if (!apiChains?.length || !token || !chain)
|
|
116
|
+
return undefined;
|
|
117
|
+
return getTokenAddressForChain(apiChains, chain, token);
|
|
118
|
+
}, [apiChains, token, chain]);
|
|
119
|
+
// direct 模式的内部激活标记(便于“Start another withdrawal”时重置为表单态)
|
|
120
|
+
const [directActive, setDirectActive] = useState(false);
|
|
121
|
+
// 当外部把 withdrawMode 设为 direct 且提供 withdrawDirectResut 时,进入“追踪提现”态
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
setDirectActive(withdrawMode === "direct" && Boolean(withdrawDirectResult));
|
|
124
|
+
}, [withdrawMode, withdrawDirectResult]);
|
|
125
|
+
const trackingWithdraw = useMemo(() => Boolean(orderId) || directActive, [orderId, directActive]);
|
|
113
126
|
// Default focus on recipient address when form is visible; white border while focused, revert on blur
|
|
114
127
|
useEffect(() => {
|
|
115
128
|
if (trackingWithdraw)
|
|
@@ -126,10 +139,10 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
126
139
|
return;
|
|
127
140
|
setLoadingChains(true);
|
|
128
141
|
getChains()
|
|
129
|
-
.then((res) => setApiChains(res?.chains ??
|
|
142
|
+
.then((res) => setApiChains(res?.chains ?? []))
|
|
130
143
|
.finally(() => setLoadingChains(false));
|
|
131
144
|
}, [shouldLoadChains]);
|
|
132
|
-
// 选中 token + chain 后即询价;未填 amount 时用 1
|
|
145
|
+
// 选中 token + chain 后即询价;未填 amount 时用 1 xxx 获取 rate
|
|
133
146
|
useEffect(() => {
|
|
134
147
|
if (!apiChains?.length || !token || !chain) {
|
|
135
148
|
setApiQuote(null);
|
|
@@ -147,7 +160,7 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
147
160
|
direction: "withdraw",
|
|
148
161
|
chain_id: chain,
|
|
149
162
|
token_address: tokenAddress,
|
|
150
|
-
|
|
163
|
+
dst_token_amount: amountWei,
|
|
151
164
|
})
|
|
152
165
|
.then((q) => setApiQuote(q ?? null))
|
|
153
166
|
.catch(() => {
|
|
@@ -183,41 +196,73 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
183
196
|
order_id: orderId ?? "",
|
|
184
197
|
status: "pending",
|
|
185
198
|
chain_id: "56",
|
|
186
|
-
|
|
199
|
+
dst_token_amount: "0",
|
|
187
200
|
target_chain_id: chain ?? "1",
|
|
188
201
|
target_address: address ?? "0x",
|
|
189
202
|
created_at: new Date().toISOString(),
|
|
190
203
|
updated_at: new Date().toISOString(),
|
|
191
204
|
}), [orderId, chain, address]);
|
|
192
205
|
useEffect(() => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
// 1) 有 orderId:按原逻辑轮询
|
|
207
|
+
if (orderId) {
|
|
208
|
+
const t = setInterval(() => {
|
|
209
|
+
getWithdrawOrder(orderId)
|
|
210
|
+
.then((order) => {
|
|
211
|
+
setWithdrawOrder(order);
|
|
212
|
+
if (order.status === "completed" && onShowToast) {
|
|
213
|
+
onShowToast("Withdrawal completed");
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
.catch(() => setWithdrawOrder(mockWithdrawOrderState));
|
|
217
|
+
}, POLL_INTERVAL_MS);
|
|
198
218
|
getWithdrawOrder(orderId)
|
|
199
|
-
.then(
|
|
200
|
-
setWithdrawOrder(order);
|
|
201
|
-
if (order.status === "completed" && onShowToast) {
|
|
202
|
-
onShowToast("Withdrawal completed");
|
|
203
|
-
}
|
|
204
|
-
})
|
|
219
|
+
.then(setWithdrawOrder)
|
|
205
220
|
.catch(() => setWithdrawOrder(mockWithdrawOrderState));
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
221
|
+
return () => clearInterval(t);
|
|
222
|
+
}
|
|
223
|
+
// 2) 无 orderId 且 direct 模式:用传入的结果构造一个“伪订单”,以复用已提交/成功页
|
|
224
|
+
if (!orderId && directActive) {
|
|
225
|
+
const now = new Date().toISOString();
|
|
226
|
+
const inferredAmountWei = (() => {
|
|
227
|
+
if (withdrawDirectResult?.dst_token_amount)
|
|
228
|
+
return withdrawDirectResult.dst_token_amount;
|
|
229
|
+
const n = Number(amount || "0");
|
|
230
|
+
return String(n > 0 ? BigInt(Math.floor(n * 1e18)) : 0n);
|
|
231
|
+
})();
|
|
232
|
+
const synthetic = {
|
|
233
|
+
order_id: withdrawDirectResult?.order_id ?? `direct-${Date.now()}`,
|
|
234
|
+
status: withdrawDirectResult?.status ?? "pending",
|
|
235
|
+
chain_id: withdrawDirectResult?.chain_id ?? String(fundingChainId ?? "3131"),
|
|
236
|
+
dst_token_amount: inferredAmountWei,
|
|
237
|
+
fee: withdrawDirectResult?.fee ?? feeDisplay,
|
|
238
|
+
target_chain_id: withdrawDirectResult?.target_chain_id ?? (chain ?? ""),
|
|
239
|
+
target_address: withdrawDirectResult?.target_address ?? (address ?? ""),
|
|
240
|
+
funding_tx_hash: withdrawDirectResult?.funding_tx_hash ?? txHash,
|
|
241
|
+
dst_tx_hash: withdrawDirectResult?.dst_tx_hash,
|
|
242
|
+
out_tx_hash: withdrawDirectResult?.out_tx_hash,
|
|
243
|
+
created_at: withdrawDirectResult?.created_at ?? now,
|
|
244
|
+
updated_at: withdrawDirectResult?.updated_at ?? now,
|
|
245
|
+
one_time_address: withdrawDirectResult?.one_time_address,
|
|
246
|
+
};
|
|
247
|
+
setWithdrawOrder(synthetic);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// 3) 默认:清空订单,显示表单
|
|
251
|
+
setWithdrawOrder(null);
|
|
252
|
+
}, [orderId, directActive, withdrawDirectResult, amount, feeDisplay, chain, address, txHash, fundingChainId, onShowToast, mockWithdrawOrderState]);
|
|
253
|
+
// 订单完成回调:支持 orderId 或 direct 模式
|
|
254
|
+
const completedKeyRef = useRef(null);
|
|
213
255
|
useEffect(() => {
|
|
214
|
-
if (withdrawOrder?.status
|
|
215
|
-
|
|
256
|
+
if (withdrawOrder?.status !== "completed")
|
|
257
|
+
return;
|
|
258
|
+
const key = orderId ?? withdrawOrder.order_id;
|
|
259
|
+
if (key && completedKeyRef.current !== key) {
|
|
260
|
+
completedKeyRef.current = key;
|
|
216
261
|
onWithdrawCompleted?.();
|
|
217
262
|
}
|
|
218
|
-
if (!
|
|
219
|
-
|
|
220
|
-
}, [withdrawOrder?.status, orderId, onWithdrawCompleted]);
|
|
263
|
+
if (!trackingWithdraw)
|
|
264
|
+
completedKeyRef.current = null;
|
|
265
|
+
}, [withdrawOrder?.status, orderId, trackingWithdraw, onWithdrawCompleted]);
|
|
221
266
|
const receiveAmount = useMemo(() => {
|
|
222
267
|
if (receiveAmountProp) {
|
|
223
268
|
const n = Number(receiveAmountProp);
|
|
@@ -254,7 +299,7 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
254
299
|
direction: "withdraw",
|
|
255
300
|
chain_id: chain,
|
|
256
301
|
token_address: tokenAddress,
|
|
257
|
-
|
|
302
|
+
dst_token_amount: amountWei,
|
|
258
303
|
})
|
|
259
304
|
.then((q) => setApiQuote(q ?? null))
|
|
260
305
|
.catch(() => {
|
|
@@ -379,7 +424,7 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
379
424
|
flexDirection: "column",
|
|
380
425
|
gap: 4,
|
|
381
426
|
width: "100%",
|
|
382
|
-
}, children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", height: 36 }, children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textSecondary }, children: "Amount" }), _jsxs("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textPrimary }, children: [weiToEtherDisplay(withdrawOrder.
|
|
427
|
+
}, children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", height: 36 }, children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textSecondary }, children: "Amount" }), _jsxs("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textPrimary }, children: [weiToEtherDisplay(withdrawOrder.dst_token_amount), " ", tokenSymbol] })] }), _jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", height: 36 }, children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textSecondary }, children: "Fee" }), _jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textPrimary }, children: withdrawOrder.fee ?? feeDisplay ?? "—" })] }), _jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", height: 36 }, children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textSecondary }, children: "Network" }), _jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textPrimary }, children: targetChainName })] }), destTxHash && (_jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "8px 0", minHeight: 38 }, children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textSecondary }, children: "Transaction Hash" }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [explorerUrl ? (_jsxs("a", { href: explorerUrl, target: "_blank", rel: "noopener noreferrer", style: { fontSize: 14, color: colors.textPrimary, textDecoration: "underline" }, children: [destTxHash.slice(0, 10), "...", destTxHash.slice(-8)] })) : (_jsxs("span", { style: { fontSize: 14, color: colors.textPrimary }, children: [destTxHash.slice(0, 10), "...", destTxHash.slice(-8)] })), _jsx("button", { type: "button", onClick: copyHash, "aria-label": "Copy hash", style: {
|
|
383
428
|
padding: 0,
|
|
384
429
|
border: "none",
|
|
385
430
|
background: "transparent",
|
|
@@ -390,7 +435,12 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
390
435
|
justifyContent: "center",
|
|
391
436
|
width: 16,
|
|
392
437
|
height: 16,
|
|
393
|
-
}, children: _jsx(CopyIcon, {}) })] })] }))] }), _jsx("div", { style: { paddingTop: 24, width: "100%", display: "flex", justifyContent: "center" }, children: _jsx("button", { type: "button", onClick: () =>
|
|
438
|
+
}, children: _jsx(CopyIcon, {}) })] })] }))] }), _jsx("div", { style: { paddingTop: 24, width: "100%", display: "flex", justifyContent: "center" }, children: _jsx("button", { type: "button", onClick: () => {
|
|
439
|
+
// 重置 direct 模式追踪与本地订单,恢复到表单态
|
|
440
|
+
setDirectActive(false);
|
|
441
|
+
setWithdrawOrder(null);
|
|
442
|
+
(onStartAnotherWithdrawal ?? onClose)?.();
|
|
443
|
+
}, style: {
|
|
394
444
|
width: 274,
|
|
395
445
|
padding: "12px 0",
|
|
396
446
|
borderRadius: radii.full,
|
|
@@ -435,7 +485,7 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
435
485
|
display: "flex",
|
|
436
486
|
flexDirection: "column",
|
|
437
487
|
gap: 8,
|
|
438
|
-
}, children: loadingQuote ? (_jsx("span", { style: { fontSize: 13, color: colors.textSecondary }, children: "Loading\u2026" })) : (_jsxs(_Fragment, { children: [_jsxs("span", { style: { fontSize: 13, color: colors.textSecondary }, children: ["Rate: 1
|
|
488
|
+
}, children: loadingQuote ? (_jsx("span", { style: { fontSize: 13, color: colors.textSecondary }, children: "Loading\u2026" })) : (_jsxs(_Fragment, { children: [_jsxs("span", { style: { fontSize: 13, color: colors.textSecondary }, children: ["Rate: 1 ", FUNDING_TOKEN_SYMBOL, " = ", apiQuote.rate, " ", apiQuote.token_symbol] }), apiQuote.expires_at && (_jsx(Countdown, { expiresAt: apiQuote.expires_at, isExpired: quoteExpired, onExpired: handleRefreshQuote })), quoteExpired && (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: { fontSize: 13, color: "#f59e0b" }, children: "Quote expired, please refresh" }), _jsx("button", { type: "button", onClick: handleRefreshQuote, style: {
|
|
439
489
|
padding: "4px 12px",
|
|
440
490
|
fontSize: 12,
|
|
441
491
|
borderRadius: radii.pill,
|
|
@@ -444,7 +494,7 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
444
494
|
color: colors.textPrimary,
|
|
445
495
|
cursor: "pointer",
|
|
446
496
|
fontFamily: fonts.family,
|
|
447
|
-
}, children: "Refresh" })] }))] })) })), _jsx("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textPrimary }, children: "Amount" }), balance && (_jsxs("span", { style: { fontSize: 13, lineHeight: 1.2, color: colors.textSecondary }, children: ["Balance: \u00A0", formatBalanceTo2Decimals(balance)] }))] }), _jsxs("div", { style: {
|
|
497
|
+
}, children: "Refresh" })] }))] })) })), _jsx("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [_jsx("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textPrimary }, children: "Amount" }), balance && (_jsxs("span", { style: { fontSize: 13, lineHeight: 1.2, color: colors.textSecondary }, children: ["Balance: \u00A0", formatBalanceTo2Decimals(balance), " ", FUNDING_TOKEN_SYMBOL] }))] }), _jsxs("div", { style: {
|
|
448
498
|
display: "flex",
|
|
449
499
|
alignItems: "center",
|
|
450
500
|
justifyContent: "space-between",
|
|
@@ -463,23 +513,18 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
463
513
|
fontFamily: fonts.family,
|
|
464
514
|
lineHeight: 1.4,
|
|
465
515
|
padding: 0,
|
|
466
|
-
} }),
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
fontWeight: 400,
|
|
479
|
-
color: "#15181D",
|
|
480
|
-
fontFamily: fonts.family,
|
|
481
|
-
cursor: "pointer",
|
|
482
|
-
}, children: "Max" })] })] })] }) })] }) })] })] })] }), _jsxs("div", { style: { padding: "20px 20px 0", display: "flex", flexDirection: "column", gap: 10, alignItems: "center" }, id: "WithdrawModalReceiveAmount", children: [!trackingWithdraw && amount && Number(amount) > 0 && (_jsxs("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textPrimary }, children: ["You will receive : ", receiveAmount] })), orderInProgress && withdrawOrder && (() => {
|
|
516
|
+
} }), _jsx("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: _jsx("button", { type: "button", onClick: onMaxClick, style: {
|
|
517
|
+
background: colors.textPrimary,
|
|
518
|
+
border: "none",
|
|
519
|
+
borderRadius: radii.pill,
|
|
520
|
+
padding: "2px 8px",
|
|
521
|
+
fontSize: 12,
|
|
522
|
+
lineHeight: 1.4,
|
|
523
|
+
fontWeight: 400,
|
|
524
|
+
color: "#15181D",
|
|
525
|
+
fontFamily: fonts.family,
|
|
526
|
+
cursor: "pointer",
|
|
527
|
+
}, children: "Max" }) })] })] }) })] }) })] })] })] }), _jsxs("div", { style: { padding: "20px 20px 0", display: "flex", flexDirection: "column", gap: 10, alignItems: "center" }, id: "WithdrawModalReceiveAmount", children: [!trackingWithdraw && amount && Number(amount) > 0 && (_jsxs("span", { style: { fontSize: 14, lineHeight: 1.4, color: colors.textPrimary }, children: ["You will receive : ", receiveAmount] })), orderInProgress && withdrawOrder && (() => {
|
|
483
528
|
const fundingHash = txHash ?? withdrawOrder?.funding_tx_hash;
|
|
484
529
|
const showFundingLink = fundingHash && fundingChainId;
|
|
485
530
|
try {
|
|
@@ -500,6 +545,7 @@ export const WithdrawModal = ({ address = "", token, tokenSymbol = "USDT", chain
|
|
|
500
545
|
toAddress: address,
|
|
501
546
|
amount: amount ?? "",
|
|
502
547
|
token: token ?? "",
|
|
548
|
+
tokenAddress: resolvedTokenAddress ?? apiQuote?.token_address ?? "",
|
|
503
549
|
chain: chain ?? "",
|
|
504
550
|
}) }))] })] }));
|
|
505
551
|
};
|
package/dist/ui/signInTypes.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export interface SignInUiConfig {
|
|
|
16
16
|
socialProviders?: SocialProvider[];
|
|
17
17
|
wallets?: WalletItem[];
|
|
18
18
|
initialVisibleCount?: number;
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Ignored by `SignInModal` / `useSignInModalController`: social login creates the session without `window.confirm`.
|
|
21
|
+
*/
|
|
19
22
|
sessionConfirmation?: {
|
|
20
23
|
enabled?: boolean;
|
|
21
24
|
title?: string;
|
|
@@ -3,7 +3,7 @@ import { getSDKConfig } from "../auth/config.js";
|
|
|
3
3
|
import { createOidcRelayAuth } from "../auth/oidcRelay.js";
|
|
4
4
|
import WalletAccount from "../auth/walletAccount.js";
|
|
5
5
|
import { getEnv } from "../utils/env.js";
|
|
6
|
-
import {
|
|
6
|
+
import { WalletConnector, createDefaultInjectedWalletRegistry, } from "@ab-org/sdk-core";
|
|
7
7
|
import { resolveWalletItems } from "./SignInModal.shared.js";
|
|
8
8
|
function resolveOidcStage() {
|
|
9
9
|
return getEnv("STAGE").toLowerCase() === "prod" ? "prod" : "dev";
|
|
@@ -13,7 +13,6 @@ export const useSignInModalController = ({ wallets, initialVisibleCount, onGoogl
|
|
|
13
13
|
const [loadingProvider, setLoadingProvider] = useState(null);
|
|
14
14
|
const [loadingWalletId, setLoadingWalletId] = useState(null);
|
|
15
15
|
const defaultWalletRegistry = useMemo(() => createDefaultInjectedWalletRegistry(), []);
|
|
16
|
-
const sdkConfig = getSDKConfig();
|
|
17
16
|
const defaultWalletConnector = useMemo(() => new WalletConnector([
|
|
18
17
|
...defaultWalletRegistry.map((item) => item.provider),
|
|
19
18
|
]), [defaultWalletRegistry]);
|
|
@@ -21,30 +20,6 @@ export const useSignInModalController = ({ wallets, initialVisibleCount, onGoogl
|
|
|
21
20
|
const showExpandToggle = resolvedWallets.length > initialVisibleCount;
|
|
22
21
|
const visibleWallets = expanded ? resolvedWallets : resolvedWallets.slice(0, initialVisibleCount);
|
|
23
22
|
const isBusy = !!loadingProvider || !!loadingWalletId;
|
|
24
|
-
const confirmCapabilitySession = async (providerId, cubeSignerSession) => {
|
|
25
|
-
const confirmation = sdkConfig.signIn?.sessionConfirmation;
|
|
26
|
-
const policy = cubeSignerSession && sdkConfig.cubeSigner
|
|
27
|
-
? {
|
|
28
|
-
id: "preview",
|
|
29
|
-
...(sdkConfig.signIn?.sessionPolicy ?? sdkConfig.cubeSigner.defaultSessionPolicy),
|
|
30
|
-
}
|
|
31
|
-
: undefined;
|
|
32
|
-
if (confirmation?.enabled === false || !policy) {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
if (typeof window === "undefined" || typeof window.confirm !== "function") {
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
const lines = describeSessionCapabilityPolicy(policy);
|
|
39
|
-
const message = [
|
|
40
|
-
confirmation?.title ?? "Authorize smart-wallet session",
|
|
41
|
-
confirmation?.message ?? `Continue with ${providerId} and create a capability session?`,
|
|
42
|
-
...lines,
|
|
43
|
-
]
|
|
44
|
-
.filter(Boolean)
|
|
45
|
-
.join("\n");
|
|
46
|
-
return window.confirm(message);
|
|
47
|
-
};
|
|
48
23
|
const handleSocialClick = async (providerId) => {
|
|
49
24
|
setLoadingProvider(providerId);
|
|
50
25
|
try {
|
|
@@ -67,11 +42,6 @@ export const useSignInModalController = ({ wallets, initialVisibleCount, onGoogl
|
|
|
67
42
|
await WalletAccount.getInstance(oidcToken, authSource, config.cubeSigner);
|
|
68
43
|
const session = WalletAccount.getWalletSession();
|
|
69
44
|
const cubeSignerSession = WalletAccount.getCubeSignerSession();
|
|
70
|
-
const confirmed = await confirmCapabilitySession(providerId, cubeSignerSession);
|
|
71
|
-
if (!confirmed) {
|
|
72
|
-
WalletAccount.clearInstance();
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
45
|
onGoogleLogin?.({ idToken: oidcToken });
|
|
76
46
|
if (cubeSignerSession)
|
|
77
47
|
onCubeSignerSession?.(cubeSignerSession);
|
|
@@ -91,11 +61,6 @@ export const useSignInModalController = ({ wallets, initialVisibleCount, onGoogl
|
|
|
91
61
|
await WalletAccount.getInstance(oidcToken, authSource, config.cubeSigner);
|
|
92
62
|
const session = WalletAccount.getWalletSession();
|
|
93
63
|
const cubeSignerSession = WalletAccount.getCubeSignerSession();
|
|
94
|
-
const confirmed = await confirmCapabilitySession(providerId, cubeSignerSession);
|
|
95
|
-
if (!confirmed) {
|
|
96
|
-
WalletAccount.clearInstance();
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
64
|
onTwitterLogin?.({ code: "", codeVerifier: "", state: "" });
|
|
100
65
|
if (cubeSignerSession)
|
|
101
66
|
onCubeSignerSession?.(cubeSignerSession);
|
package/dist/utils/env.js
CHANGED
|
@@ -28,6 +28,8 @@ function readProcessEnvStatic(key) {
|
|
|
28
28
|
return pick(process.env.NEXT_PUBLIC_CUBE_REG, process.env.CUBE_REG);
|
|
29
29
|
case "RELAY_ORIGIN":
|
|
30
30
|
return pick(process.env.NEXT_PUBLIC_RELAY_ORIGIN, process.env.RELAY_ORIGIN);
|
|
31
|
+
case "FUNDING_TOKEN_SYMBOL":
|
|
32
|
+
return pick(process.env.NEXT_PUBLIC_FUNDING_TOKEN_SYMBOL, process.env.FUNDING_TOKEN_SYMBOL) || "USDT";
|
|
31
33
|
default:
|
|
32
34
|
return undefined;
|
|
33
35
|
}
|
package/dist/walletUtils.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { getEnv } from "./utils/env.js";
|
|
2
2
|
export { getExplorerUrl } from "./utils/explorer.js";
|
|
3
|
-
export {
|
|
3
|
+
export { ClientIds, DEFAULT_FUNDING_TOKEN_ADDRESS, DEFAULT_FUNDING_CHAIN_ID, getFundingTokenAddress, getChainInfo, type EvmChainInfo, } from "./constants/chains.js";
|
package/dist/walletUtils.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { getEnv } from "./utils/env.js";
|
|
2
2
|
export { getExplorerUrl } from "./utils/explorer.js";
|
|
3
|
-
export {
|
|
3
|
+
export { ClientIds, DEFAULT_FUNDING_TOKEN_ADDRESS, DEFAULT_FUNDING_CHAIN_ID, getFundingTokenAddress, getChainInfo, } from "./constants/chains.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ab-org/predicate-market-sdk",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/**/*",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@cubist-labs/cubesigner-sdk": "^0.4.219",
|
|
24
24
|
"axios": "^1.13.6",
|
|
25
25
|
"qrcode-generator": "^2.0.4",
|
|
26
|
+
"@ab-org/wallet-utils": "0.0.7",
|
|
26
27
|
"@ab-org/sdk-core": "0.0.1"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|