@ab-org/predicate-market-sdk 0.0.1 → 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.
@@ -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、所选 chain),由调用方执行提现 */
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, explorerTxUrl, onShowToast, onAddressChange, onTokenSelect, onChainSelect, onAmountChange, onMaxClick, onSubmit, onWithdrawCompleted, onStartAnotherWithdrawal, onSignIn, onClose, }: WithdrawModalProps) => import("react/jsx-runtime.js").JSX.Element;
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;
@@ -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 USD1" → 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 USD1" → "123.46 USD1") */
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 = "USDT", chain, amount = "", balance, status = "idle", receiveAmount: receiveAmountProp, txHash, tokenOptions: tokenOptionsProp, chainOptions: chainOptionsProp, useMerchantApi = false, orderId, feeDisplay, fundingChainId, explorerTxUrl, onShowToast, onAddressChange, onTokenSelect, onChainSelect, onAmountChange, onMaxClick, onSubmit, onWithdrawCompleted, onStartAnotherWithdrawal, onSignIn, onClose, }) => {
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.filter((o) => o.id !== "USD1" && o.label !== "USD1");
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
- const trackingWithdraw = useMemo(() => Boolean(orderId), [orderId]);
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 USD1 获取 rate
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
- usd1_amount: amountWei,
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
- usd1_amount: "0",
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
- if (!orderId) {
194
- setWithdrawOrder(null);
195
- return;
196
- }
197
- const t = setInterval(() => {
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((order) => {
200
- setWithdrawOrder(order);
201
- if (order.status === "completed" && onShowToast) {
202
- onShowToast("Withdrawal completed");
203
- }
204
- })
219
+ .then(setWithdrawOrder)
205
220
  .catch(() => setWithdrawOrder(mockWithdrawOrderState));
206
- }, POLL_INTERVAL_MS);
207
- getWithdrawOrder(orderId)
208
- .then(setWithdrawOrder)
209
- .catch(() => setWithdrawOrder(mockWithdrawOrderState));
210
- return () => clearInterval(t);
211
- }, [orderId, onShowToast, mockWithdrawOrderState]);
212
- const completedOrderIdRef = useRef(null);
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 === "completed" && orderId && completedOrderIdRef.current !== orderId) {
215
- completedOrderIdRef.current = orderId;
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 (!orderId)
219
- completedOrderIdRef.current = null;
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
- usd1_amount: amountWei,
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.usd1_amount), " ", tokenSymbol ?? "USD1"] })] }), _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: {
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: () => (onStartAnotherWithdrawal ?? onClose)?.(), style: {
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 USD1 = ", 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: {
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
- } }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [_jsx("span", { style: {
467
- fontSize: 14,
468
- lineHeight: 1.4,
469
- color: colors.textSecondary,
470
- fontFamily: fonts.family,
471
- }, children: "USD1" }), _jsx("button", { type: "button", onClick: onMaxClick, style: {
472
- background: colors.textPrimary,
473
- border: "none",
474
- borderRadius: radii.pill,
475
- padding: "2px 8px",
476
- fontSize: 12,
477
- lineHeight: 1.4,
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
  };
@@ -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;
@@ -1,5 +1,5 @@
1
- import { type GoogleCredential } from "../auth/google.js";
2
- import { type TwitterAuthResult } from "../auth/twitter.js";
1
+ import type { GoogleCredential } from "../auth/google.js";
2
+ import type { TwitterAuthResult } from "../auth/twitter.js";
3
3
  import { type CubeSignerSession, type WalletSession } from "@ab-org/sdk-core";
4
4
  import type { WalletItem } from "./signInTypes.js";
5
5
  export interface SignInModalControllerOptions {
@@ -1,71 +1,25 @@
1
1
  import { useMemo, useState } from "react";
2
2
  import { getSDKConfig } from "../auth/config.js";
3
- import { signInWithGoogle } from "../auth/google.js";
4
- import { signInWithTwitter } from "../auth/twitter.js";
5
- import { CubistSocialProvider, describeSessionCapabilityPolicy, WalletConnector, createDefaultInjectedWalletRegistry, } from "@ab-org/sdk-core";
3
+ import { createOidcRelayAuth } from "../auth/oidcRelay.js";
4
+ import WalletAccount from "../auth/walletAccount.js";
5
+ import { getEnv } from "../utils/env.js";
6
+ import { WalletConnector, createDefaultInjectedWalletRegistry, } from "@ab-org/sdk-core";
6
7
  import { resolveWalletItems } from "./SignInModal.shared.js";
8
+ function resolveOidcStage() {
9
+ return getEnv("STAGE").toLowerCase() === "prod" ? "prod" : "dev";
10
+ }
7
11
  export const useSignInModalController = ({ wallets, initialVisibleCount, onGoogleLogin, onTwitterLogin, onCubeSignerSession, onWalletConnected, onSocialLogin, onWalletSelect, }) => {
8
12
  const [expanded, setExpanded] = useState(false);
9
13
  const [loadingProvider, setLoadingProvider] = useState(null);
10
14
  const [loadingWalletId, setLoadingWalletId] = useState(null);
11
15
  const defaultWalletRegistry = useMemo(() => createDefaultInjectedWalletRegistry(), []);
12
- const sdkConfig = getSDKConfig();
13
- const cubistProvider = useMemo(() => {
14
- if (!sdkConfig.cubeSigner)
15
- return null;
16
- return new CubistSocialProvider({
17
- ...sdkConfig.cubeSigner,
18
- defaultSessionPolicy: sdkConfig.signIn?.sessionPolicy ?? sdkConfig.cubeSigner.defaultSessionPolicy,
19
- });
20
- }, [sdkConfig.cubeSigner, sdkConfig.signIn?.sessionPolicy]);
21
16
  const defaultWalletConnector = useMemo(() => new WalletConnector([
22
17
  ...defaultWalletRegistry.map((item) => item.provider),
23
- ...(cubistProvider ? [cubistProvider] : []),
24
- ]), [cubistProvider, defaultWalletRegistry]);
18
+ ]), [defaultWalletRegistry]);
25
19
  const resolvedWallets = useMemo(() => resolveWalletItems(wallets, defaultWalletRegistry), [defaultWalletRegistry, wallets]);
26
20
  const showExpandToggle = resolvedWallets.length > initialVisibleCount;
27
21
  const visibleWallets = expanded ? resolvedWallets : resolvedWallets.slice(0, initialVisibleCount);
28
22
  const isBusy = !!loadingProvider || !!loadingWalletId;
29
- const confirmCapabilitySession = async (providerId, cubistForPolicy) => {
30
- const cubist = cubistForPolicy ?? cubistProvider;
31
- const confirmation = sdkConfig.signIn?.sessionConfirmation;
32
- const policy = cubist?.cubeSignerAuth.defaultSessionPolicy;
33
- if (confirmation?.enabled === false || !policy) {
34
- return true;
35
- }
36
- if (typeof window === "undefined" || typeof window.confirm !== "function") {
37
- return true;
38
- }
39
- const lines = describeSessionCapabilityPolicy(cubist?.cubeSignerAuth.defaultSessionPolicy
40
- ? {
41
- id: "preview",
42
- ...cubist.cubeSignerAuth.defaultSessionPolicy,
43
- }
44
- : undefined);
45
- const message = [
46
- confirmation?.title ?? "Authorize smart-wallet session",
47
- confirmation?.message ?? `Continue with ${providerId} and create a capability session?`,
48
- ...lines,
49
- ]
50
- .filter(Boolean)
51
- .join("\n");
52
- return window.confirm(message);
53
- };
54
- const getCubistAndConnector = (config) => {
55
- if (!config.cubeSigner)
56
- return { cubist: null, connector: defaultWalletConnector };
57
- if (cubistProvider)
58
- return { cubist: cubistProvider, connector: defaultWalletConnector };
59
- const cubist = new CubistSocialProvider({
60
- ...config.cubeSigner,
61
- defaultSessionPolicy: config.signIn?.sessionPolicy ?? config.cubeSigner.defaultSessionPolicy,
62
- });
63
- const connector = new WalletConnector([
64
- ...defaultWalletRegistry.map((item) => item.provider),
65
- cubist,
66
- ]);
67
- return { cubist, connector };
68
- };
69
23
  const handleSocialClick = async (providerId) => {
70
24
  setLoadingProvider(providerId);
71
25
  try {
@@ -73,55 +27,47 @@ export const useSignInModalController = ({ wallets, initialVisibleCount, onGoogl
73
27
  if (!config.cubeSigner) {
74
28
  throw new Error("cubeSigner config is required for social login");
75
29
  }
76
- const { cubist, connector } = getCubistAndConnector(config);
77
- if (!cubist) {
78
- throw new Error("Cubist provider is unavailable");
79
- }
80
- const confirmed = await confirmCapabilitySession(providerId, cubist);
81
- if (!confirmed)
82
- return null;
30
+ const auth = createOidcRelayAuth({
31
+ stage: resolveOidcStage(),
32
+ googleClientId: config.googleClientId ?? "",
33
+ xClientId: config.twitterClientId ?? "",
34
+ });
83
35
  if (providerId === "google") {
84
36
  if (!config.googleClientId) {
85
37
  throw new Error("googleClientId is required for Google sign-in");
86
38
  }
87
- const credential = await signInWithGoogle(config.googleClientId);
88
- onGoogleLogin?.(credential);
89
- const session = await connector.connect(cubist.id, {
90
- payload: { type: "google", idToken: credential.idToken },
91
- });
92
- const cubeSignerSession = cubist.cubeSignerSession;
39
+ const oidcToken = await auth.loginByGoogle();
40
+ const authSource = "google";
41
+ WalletAccount.clearInstance();
42
+ await WalletAccount.getInstance(oidcToken, authSource, config.cubeSigner);
43
+ const session = WalletAccount.getWalletSession();
44
+ const cubeSignerSession = WalletAccount.getCubeSignerSession();
45
+ onGoogleLogin?.({ idToken: oidcToken });
93
46
  if (cubeSignerSession)
94
47
  onCubeSignerSession?.(cubeSignerSession);
95
48
  onSocialLogin?.(providerId);
49
+ if (!session) {
50
+ throw new Error("Failed to build wallet session from Google login");
51
+ }
96
52
  return session;
97
53
  }
98
54
  if (providerId === "x") {
99
55
  if (!config.twitterClientId) {
100
56
  throw new Error("twitterClientId is required for X sign-in");
101
57
  }
102
- const redirectUri = config.twitterRedirectUri ??
103
- (typeof window !== "undefined"
104
- ? `${window.location.origin}/auth/twitter-callback`
105
- : undefined);
106
- if (!redirectUri) {
107
- throw new Error("twitterRedirectUri is required for X sign-in");
108
- }
109
- const result = await signInWithTwitter(config.twitterClientId, redirectUri);
110
- onTwitterLogin?.(result);
111
- const session = await connector.connect(cubist.id, {
112
- payload: {
113
- type: "twitter",
114
- params: {
115
- code: result.code,
116
- codeVerifier: result.codeVerifier,
117
- redirectUri,
118
- },
119
- },
120
- });
121
- const cubeSignerSession = cubist.cubeSignerSession;
58
+ const oidcToken = await auth.loginByX();
59
+ const authSource = "twitter";
60
+ WalletAccount.clearInstance();
61
+ await WalletAccount.getInstance(oidcToken, authSource, config.cubeSigner);
62
+ const session = WalletAccount.getWalletSession();
63
+ const cubeSignerSession = WalletAccount.getCubeSignerSession();
64
+ onTwitterLogin?.({ code: "", codeVerifier: "", state: "" });
122
65
  if (cubeSignerSession)
123
66
  onCubeSignerSession?.(cubeSignerSession);
124
67
  onSocialLogin?.(providerId);
68
+ if (!session) {
69
+ throw new Error("Failed to build wallet session from X login");
70
+ }
125
71
  return session;
126
72
  }
127
73
  throw new Error(`Unsupported social provider: ${providerId}`);
@@ -152,7 +98,7 @@ export const useSignInModalController = ({ wallets, initialVisibleCount, onGoogl
152
98
  return session;
153
99
  }
154
100
  catch (error) {
155
- console.error(`[SDK] Wallet connect failed for ${walletId}:`, error);
101
+ console.error(`[predicate-market-sdk] Wallet connect failed for ${walletId}:`, error);
156
102
  throw error;
157
103
  }
158
104
  finally {
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
  }
@@ -1,3 +1,3 @@
1
1
  export { getEnv } from "./utils/env.js";
2
2
  export { getExplorerUrl } from "./utils/explorer.js";
3
- export { testBsc, clientIds as ClientIds, BSC_USD1_ADDRESS, BSC_USD1_PLATFORM_CONTRACT_ADDRESS, } from "./constants/chains.js";
3
+ export { ClientIds, DEFAULT_FUNDING_TOKEN_ADDRESS, DEFAULT_FUNDING_CHAIN_ID, getFundingTokenAddress, getChainInfo, type EvmChainInfo, } from "./constants/chains.js";
@@ -1,3 +1,3 @@
1
1
  export { getEnv } from "./utils/env.js";
2
2
  export { getExplorerUrl } from "./utils/explorer.js";
3
- export { testBsc, clientIds as ClientIds, BSC_USD1_ADDRESS, BSC_USD1_PLATFORM_CONTRACT_ADDRESS, } from "./constants/chains.js";
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.1",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/**/*",
@@ -20,8 +20,10 @@
20
20
  "registry": "https://registry.npmjs.org/"
21
21
  },
22
22
  "dependencies": {
23
+ "@cubist-labs/cubesigner-sdk": "^0.4.219",
23
24
  "axios": "^1.13.6",
24
25
  "qrcode-generator": "^2.0.4",
26
+ "@ab-org/wallet-utils": "0.0.7",
25
27
  "@ab-org/sdk-core": "0.0.1"
26
28
  },
27
29
  "peerDependencies": {