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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Prediction-market specific helpers built on top of `@ab-org/sdk-core`.
7
7
  - Connection state via `createWalletConnectController()` and `createAccountController()` from `@ab-org/sdk-core`
8
8
  - Unified smart-wallet session metadata with capability policy support
9
9
  - High-level execution helpers via `createWalletExecutionController()`
10
- - Deposit / withdraw controllers with USD1-first defaults
10
+ - Deposit / withdraw controllers with USDT-first defaults
11
11
  - **Dynamic token & chain lists** via `MarketDataProvider` (backend-driven, mock included)
12
12
  - **Quote / slippage API** — fetch real-time pricing before confirming deposit or withdraw
13
13
  - Predicate-market policy adapter for deposit / withdraw / trade flows
@@ -98,7 +98,7 @@ Rules:
98
98
  ### Funding chain & funding token (withdraw / balance)
99
99
 
100
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`**.
101
+ - **`getFundingTokenAddress(chainId?)`** — same optional **`chainId`** as **`getChainInfo`**. If **`NEXT_PUBLIC_FUNDING_TOKEN_ADDRESS`** / **`FUNDING_TOKEN_ADDRESS`** or legacy defaultFundingTokenAddress`**.
102
102
  - **`DEFAULT_FUNDING_TOKEN_ADDRESS`** — shorthand for **`getChainInfo().defaultFundingTokenAddress`** (default funding chain **`3131`**).
103
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
104
  - **`createFundingWithdrawExecutor({ chainId?, rpcUrl?, tokenAddress?, … })`** — uses the same **`chainId`** for **`getChainInfo`**, default token address (**`getFundingTokenAddress(chainId)`**), and order payload (default **`3131`** when **`chainId`** omitted).
@@ -251,7 +251,7 @@ These policies can be passed into your smart-wallet authorization flow so app ac
251
251
  <button
252
252
  onClick={() =>
253
253
  withdraw.open({
254
- defaultToken: "USD1",
254
+ defaultToken: "USDT",
255
255
  defaultChain: "AB_CORE",
256
256
  targetAddress: "0x...",
257
257
  defaultAmount: "100",
@@ -75,7 +75,7 @@ export interface WithdrawOrderResponseData {
75
75
  one_time_address?: string;
76
76
  chain_id: string;
77
77
  dst_token_amount: string;
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 "—". */
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 "—". */
79
79
  fee?: string;
80
80
  target_chain_id: string;
81
81
  target_address: string;
@@ -1,4 +1,5 @@
1
1
  import { sessionStore } from "@ab-org/sdk-core";
2
+ import { getEnv } from "../utils/env";
2
3
  function requireSession() {
3
4
  const session = sessionStore.getState().session;
4
5
  if (!session)
@@ -17,7 +18,7 @@ export const createDepositController = (custody, marketData) => {
17
18
  session.chainContext?.settlementChain ??
18
19
  session.chain ??
19
20
  "AB_CORE";
20
- const token = config?.preferredToken ?? "USD1";
21
+ const token = config?.preferredToken || getEnv("FUNDING_TOKEN_SYMBOL") || "USDT";
21
22
  try {
22
23
  const { address } = await marketData.getDepositAddress(token, chain);
23
24
  const depositId = `${chain}:${token}:${Date.now()}`;
@@ -1,4 +1,5 @@
1
1
  import { sessionStore } from "@ab-org/sdk-core";
2
+ import { getEnv } from "../utils/env";
2
3
  function requireSession() {
3
4
  const session = sessionStore.getState().session;
4
5
  if (!session)
@@ -13,7 +14,7 @@ export const createWithdrawController = (custody, marketData) => {
13
14
  };
14
15
  const open = async (config) => {
15
16
  const session = requireSession();
16
- const token = config?.defaultToken ?? "USD1";
17
+ const token = config?.defaultToken || getEnv("FUNDING_TOKEN_SYMBOL") || "USDT";
17
18
  const chain = config?.defaultChain ??
18
19
  session.chainContext?.settlementChain ??
19
20
  session.chain ??
@@ -43,7 +43,7 @@ export interface FundingWithdrawExecutorOptions {
43
43
  /** 系统配置的单笔限额(wei 字符串),若提供则校验 request.amount 不得超过此值 */
44
44
  maxAmountWei?: string;
45
45
  /**
46
- * 与商户订单接口约定的 funding 侧 token symbol(默认 `USD1`,按后端协议)。
46
+ * 与商户订单接口约定的 funding 侧 token symbol(默认 `USDT`,按后端协议)。
47
47
  */
48
48
  fundingLegTokenSymbol?: string;
49
49
  }
@@ -3,6 +3,7 @@ import { tryAutoReconnect } from "../auth/autoReconnect.js";
3
3
  import { getChains, createOrder } from "./api.js";
4
4
  import { fromHex, parseGwei, toHex } from "viem";
5
5
  import { getFundingTokenAddress, getChainInfo } from "../constants/chains.js";
6
+ import { getEnv } from "../utils/env";
6
7
  const MAX_WITHDRAW_GAS_LIMIT = 500000n;
7
8
  /* ─── Internal helpers ────────────────────────── */
8
9
  const ERC20_TRANSFER_SELECTOR = "0xa9059cbb";
@@ -106,7 +107,7 @@ export function createFundingWithdrawExecutor(options) {
106
107
  const tokenAddress = options?.tokenAddress ?? getFundingTokenAddress(options?.chainId);
107
108
  const decimals = options?.decimals ?? fundingChain.nativeCurrencyDecimals;
108
109
  const maxAmountWei = options?.maxAmountWei;
109
- const fundingLegTokenSymbol = options?.fundingLegTokenSymbol ?? "USD1";
110
+ const fundingLegTokenSymbol = options?.fundingLegTokenSymbol || getEnv("FUNDING_TOKEN_SYMBOL") || "USDT";
110
111
  return async (request) => {
111
112
  const amountWei = parseUnits(request.amount, decimals);
112
113
  const amountWeiStr = amountWei.toString();
@@ -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;
@@ -8,7 +8,7 @@ import { DepositDetailsPanel } from "./components/DepositDetailsPanel.js";
8
8
  import { LoginRequiredOverlay } from "./components/LoginRequiredOverlay.js";
9
9
  import { useSession } from "./hooks/useSession.js";
10
10
  import { colors, fonts, radii } from "./theme.js";
11
- import { getChains, quote, } from "../modules/api.js";
11
+ import { getChains, quote, registerPlatform, } from "../modules/api.js";
12
12
  import { getEnv } from "../utils/env";
13
13
  /** 校验是否为合法的充值地址(传入值):非空且长度满足常见链地址格式 */
14
14
  function isValidDepositAddress(v) {
@@ -97,6 +97,9 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
97
97
  const [loadingChains, setLoadingChains] = useState(false);
98
98
  const [loadingQuote, setLoadingQuote] = useState(false);
99
99
  const [quoteRefreshKey, setQuoteRefreshKey] = useState(0);
100
+ const [internalDepositAddress, setInternalDepositAddress] = useState(undefined);
101
+ const lastEmittedAddressRef = useRef(undefined);
102
+ const lastEmittedChainRef = useRef(undefined);
100
103
  const tokenOptions = useMemo(() => {
101
104
  if (tokenOptionsProp?.length)
102
105
  return tokenOptionsProp;
@@ -128,6 +131,25 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
128
131
  .then((res) => setApiChains(res?.chains ?? {}))
129
132
  .finally(() => setLoadingChains(false));
130
133
  }, [view]);
134
+ // Fetch depositAddress internally when user is signed in and a chain has been selected.
135
+ useEffect(() => {
136
+ if (view !== "transfer")
137
+ return;
138
+ if (!session?.address || !chain)
139
+ return;
140
+ registerPlatform({
141
+ platform_contract_address: session.address,
142
+ chain_id: chain,
143
+ })
144
+ .then((res) => {
145
+ if (res?.deposit_address) {
146
+ setInternalDepositAddress(res.deposit_address);
147
+ }
148
+ })
149
+ .catch(() => {
150
+ // silent failure; UI will keep placeholder if any
151
+ });
152
+ }, [view, chain, session?.address]);
131
153
  useEffect(() => {
132
154
  if (!apiChains?.length || !token || !chain) {
133
155
  setApiQuote(null);
@@ -165,6 +187,19 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
165
187
  })
166
188
  .finally(() => setLoadingQuote(false));
167
189
  }, [apiChains, token, chain, depositAmount, quoteRefreshKey, onShowToast]);
190
+ // Emit resolved address when external prop is provided/changes
191
+ useEffect(() => {
192
+ if (!chain)
193
+ return;
194
+ if (!depositAddress)
195
+ return;
196
+ if (lastEmittedAddressRef.current === depositAddress &&
197
+ lastEmittedChainRef.current === chain) {
198
+ return;
199
+ }
200
+ lastEmittedAddressRef.current = depositAddress;
201
+ lastEmittedChainRef.current = chain;
202
+ }, [depositAddress, chain]);
168
203
  const handleQuoteExpired = useCallback(() => {
169
204
  setQuoteRefreshKey((k) => k + 1);
170
205
  }, []);
@@ -180,8 +215,8 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
180
215
  setView("entry");
181
216
  onBack?.();
182
217
  };
183
- const handleCopyAddress = useCallback(() => {
184
- onCopyAddress?.();
218
+ const handleCopyAddress = useCallback((address) => {
219
+ onCopyAddress?.(address);
185
220
  onShowToast?.("Address copied");
186
221
  setCopySuccessMessage("Address copied");
187
222
  }, [onCopyAddress, onShowToast]);
@@ -204,7 +239,7 @@ export const DepositModal = ({ token, chain, tokenOptions: tokenOptionsProp, cha
204
239
  fontWeight: 500,
205
240
  boxShadow: "0 4px 12px rgba(0,0,0,.25)",
206
241
  zIndex: 10,
207
- }, children: copySuccessMessage })), view === "entry" ? (_jsx(EntryView, { cryptoIcons: cryptoIcons, onTransferCrypto: () => setView("transfer"), onBuyCrypto: onBuyCrypto })) : (_jsx(TransferView, { token: token, chain: chain, tokenOptions: tokenOptions, chainOptions: chainOptions, depositAddress: depositAddress, minimumDeposit: minimumDeposit, qrCenterIcon: qrCenterIcon, quote: apiQuote, quoteLoading: loadingQuote, txHash: txHash, chainIdForExplorer: chain, explorerTxUrl: explorerTxUrl, loadingChains: loadingChains, onTokenSelect: onTokenSelect, onChainSelect: onChainSelect, onCopyAddress: handleCopyAddress, onQuoteExpired: handleQuoteExpired, onRefreshQuote: () => {
242
+ }, children: copySuccessMessage })), view === "entry" ? (_jsx(EntryView, { cryptoIcons: cryptoIcons, onTransferCrypto: () => setView("transfer"), onBuyCrypto: onBuyCrypto })) : (_jsx(TransferView, { token: token, chain: chain, tokenOptions: tokenOptions, chainOptions: chainOptions, depositAddress: depositAddress ?? internalDepositAddress, minimumDeposit: minimumDeposit, qrCenterIcon: qrCenterIcon, quote: apiQuote, quoteLoading: loadingQuote, txHash: txHash, chainIdForExplorer: chain, explorerTxUrl: explorerTxUrl, loadingChains: loadingChains, onTokenSelect: onTokenSelect, onChainSelect: onChainSelect, onCopyAddress: handleCopyAddress, onQuoteExpired: handleQuoteExpired, onRefreshQuote: () => {
208
243
  if (!apiChains?.length || !token || !chain)
209
244
  return;
210
245
  const tokenAddress = getTokenAddressForChain(apiChains, chain, token);
@@ -3,6 +3,6 @@ export interface DepositDetailsPanelProps {
3
3
  address: string;
4
4
  tokenIcon?: ReactNode;
5
5
  minimumDeposit?: string;
6
- onCopyAddress?: () => void;
6
+ onCopyAddress?: (address: string) => void;
7
7
  }
8
8
  export declare const DepositDetailsPanel: ({ address, tokenIcon, minimumDeposit, onCopyAddress, }: DepositDetailsPanelProps) => import("react/jsx-runtime.js").JSX.Element;
@@ -56,9 +56,32 @@ const StyledQR = ({ data }) => {
56
56
  const CopyIcon = () => (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", children: [_jsx("rect", { x: "4.5", y: "4.5", width: "7", height: "7", rx: "1.5", stroke: colors.textPrimary, strokeWidth: "1.2" }), _jsx("path", { d: "M9.5 4.5V3.5C9.5 2.67 8.83 2 8 2H3.5C2.67 2 2 2.67 2 3.5V8C2 8.83 2.67 9.5 3.5 9.5H4.5", stroke: colors.textPrimary, strokeWidth: "1.2" })] }));
57
57
  /* ─── Main ──────────────────────────────────── */
58
58
  export const DepositDetailsPanel = ({ address, tokenIcon, minimumDeposit, onCopyAddress, }) => {
59
- const handleCopy = () => {
60
- navigator.clipboard?.writeText(address);
61
- onCopyAddress?.();
59
+ const canCopy = typeof address === "string" && address.trim().length > 0;
60
+ const handleCopy = async () => {
61
+ if (!canCopy)
62
+ return;
63
+ try {
64
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
65
+ await navigator.clipboard.writeText(address);
66
+ }
67
+ else {
68
+ const el = document.createElement("textarea");
69
+ el.value = address;
70
+ el.setAttribute("readonly", "");
71
+ el.style.position = "absolute";
72
+ el.style.left = "-9999px";
73
+ document.body.appendChild(el);
74
+ el.select();
75
+ try {
76
+ document.execCommand("copy");
77
+ }
78
+ catch { }
79
+ document.body.removeChild(el);
80
+ }
81
+ }
82
+ finally {
83
+ onCopyAddress?.(address);
84
+ }
62
85
  };
63
86
  return (_jsxs("div", { style: {
64
87
  display: "flex",
@@ -95,7 +118,7 @@ export const DepositDetailsPanel = ({ address, tokenIcon, minimumDeposit, onCopy
95
118
  display: "flex",
96
119
  justifyContent: "space-between",
97
120
  fontSize: 13,
98
- }, children: [_jsx("span", { style: { color: colors.textSecondary }, children: "Minimum deposit" }), _jsx("span", { style: { color: colors.textPrimary, fontWeight: 500 }, children: minimumDeposit })] })), _jsxs("button", { type: "button", onClick: handleCopy, style: {
121
+ }, children: [_jsx("span", { style: { color: colors.textSecondary }, children: "Minimum deposit" }), _jsx("span", { style: { color: colors.textPrimary, fontWeight: 500 }, children: minimumDeposit })] })), _jsxs("button", { type: "button", onClick: handleCopy, disabled: !canCopy, style: {
99
122
  display: "flex",
100
123
  alignItems: "center",
101
124
  gap: 6,
@@ -106,10 +129,13 @@ export const DepositDetailsPanel = ({ address, tokenIcon, minimumDeposit, onCopy
106
129
  color: colors.textPrimary,
107
130
  fontSize: 14,
108
131
  fontWeight: 500,
109
- cursor: "pointer",
132
+ cursor: canCopy ? "pointer" : "not-allowed",
133
+ opacity: canCopy ? 1 : 0.6,
110
134
  fontFamily: fonts.family,
111
135
  transition: "background .15s",
112
136
  }, onMouseEnter: (e) => {
137
+ if (!canCopy)
138
+ return;
113
139
  e.currentTarget.style.background = "rgba(255,255,255,0.06)";
114
140
  }, onMouseLeave: (e) => {
115
141
  e.currentTarget.style.background = "transparent";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ab-org/predicate-market-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*",
@@ -23,8 +23,8 @@
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",
27
- "@ab-org/sdk-core": "0.0.1"
26
+ "@ab-org/sdk-core": "0.0.1",
27
+ "@ab-org/wallet-utils": "0.0.7"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": ">=18",