@agg-build/hooks 4.26.1 → 4.27.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/dist/deposit.d.mts +142 -138
- package/dist/deposit.d.mts.map +1 -0
- package/dist/deposit.d.ts +142 -138
- package/dist/deposit.d.ts.map +1 -0
- package/dist/deposit.js +751 -2414
- package/dist/deposit.mjs +744 -727
- package/dist/deposit.mjs.map +1 -0
- package/dist/index-CbNR3EIA.d.mts +341 -0
- package/dist/index-CbNR3EIA.d.mts.map +1 -0
- package/dist/index-CsP1jRMD.d.ts +341 -0
- package/dist/index-CsP1jRMD.d.ts.map +1 -0
- package/dist/index-D0fXU85a.d.mts +743 -0
- package/dist/index-D0fXU85a.d.mts.map +1 -0
- package/dist/index-D0fXU85a.d.ts +743 -0
- package/dist/index-D0fXU85a.d.ts.map +1 -0
- package/dist/index.d.mts +6458 -7515
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +6458 -7515
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7451 -11417
- package/dist/index.mjs +7154 -5221
- package/dist/index.mjs.map +1 -0
- package/dist/use-ramp-session-7RAG9tvY.js +30 -0
- package/dist/use-ramp-session-BFWHCtuc.mjs +21 -0
- package/dist/use-ramp-session-BFWHCtuc.mjs.map +1 -0
- package/dist/use-sync-balances-BkcqInJh.mjs +1779 -0
- package/dist/use-sync-balances-BkcqInJh.mjs.map +1 -0
- package/dist/{use-sync-balances-CeD8qZWP.d.ts → use-sync-balances-CVEg__2V.d.mts} +7 -8
- package/dist/use-sync-balances-CVEg__2V.d.mts.map +1 -0
- package/dist/{use-sync-balances-CeD8qZWP.d.mts → use-sync-balances-CVEg__2V.d.ts} +7 -8
- package/dist/use-sync-balances-CVEg__2V.d.ts.map +1 -0
- package/dist/use-sync-balances-HryB8Q7F.js +1974 -0
- package/dist/withdraw--f5kYKJA.mjs +1474 -0
- package/dist/withdraw--f5kYKJA.mjs.map +1 -0
- package/dist/withdraw-BXWZYmJa.js +1591 -0
- package/dist/withdraw.d.mts +3 -351
- package/dist/withdraw.d.ts +3 -351
- package/dist/withdraw.js +10 -2229
- package/dist/withdraw.mjs +3 -20
- package/package.json +7 -6
- package/dist/chunk-JFJNW2PD.mjs +0 -5649
- package/dist/chunk-LNIAMJTZ.mjs +0 -505
- package/dist/chunk-OZQ3K47U.mjs +0 -31
- package/dist/use-deposit-addresses-B9ICS-3U.d.mts +0 -236
- package/dist/use-deposit-addresses-B9ICS-3U.d.ts +0 -236
package/dist/deposit.mjs
CHANGED
|
@@ -1,750 +1,767 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
useAggUiConfig,
|
|
11
|
-
useDepositAddresses,
|
|
12
|
-
useOptionalAggClient,
|
|
13
|
-
useSyncBalances
|
|
14
|
-
} from "./chunk-JFJNW2PD.mjs";
|
|
15
|
-
|
|
16
|
-
// src/deposit/normalize-wallet-error.ts
|
|
1
|
+
import { a as useAggAuthState, c as useAggBalanceState, d as useAggUiConfig, l as useAggClient, p as useOptionalAggClient, r as useDepositAddresses, t as useSyncBalances } from "./use-sync-balances-BkcqInJh.mjs";
|
|
2
|
+
import { n as useRampQuotes, t as useRampSession } from "./use-ramp-session-BFWHCtuc.mjs";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { Connection, Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction } from "@solana/web3.js";
|
|
6
|
+
import { formatEther, formatUnits, parseUnits } from "viem";
|
|
7
|
+
import { useAccount, useReadContract, useSwitchChain, useWaitForTransactionReceipt, useWriteContract } from "wagmi";
|
|
8
|
+
import { useWallet } from "@solana/wallet-adapter-react";
|
|
9
|
+
//#region src/deposit/normalize-wallet-error.ts
|
|
17
10
|
function normalizeWalletError(error, supportedChains) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
11
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12
|
+
const lower = message.toLowerCase();
|
|
13
|
+
if (lower.includes("user rejected") || lower.includes("user denied") || lower.includes("rejected the request") || lower.includes("denied transaction signature")) return {
|
|
14
|
+
message: "Wallet request rejected.",
|
|
15
|
+
tone: "error"
|
|
16
|
+
};
|
|
17
|
+
if (message.includes("does not match the target chain") || message.includes("Expected Chain ID:")) {
|
|
18
|
+
const match = message.match(/Expected Chain ID:\s*(\d+)/i);
|
|
19
|
+
const targetChainId = match ? Number(match[1]) : void 0;
|
|
20
|
+
return {
|
|
21
|
+
message: `Wallet chain does not match. Please switch your wallet to ${supportedChains?.find((chain) => chain.chainId === targetChainId)?.name ?? (targetChainId ? `chain ${targetChainId}` : "the selected chain")}.`,
|
|
22
|
+
tone: "warning"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
message,
|
|
27
|
+
tone: "error"
|
|
28
|
+
};
|
|
35
29
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/deposit/constants.ts
|
|
32
|
+
/**
|
|
33
|
+
* Priority-ordered list of Solana RPC endpoints the deposit hooks fall back
|
|
34
|
+
* through when the previous endpoint errors or times out. The partner-supplied
|
|
35
|
+
* `solanaRpcUrl` (when present) is prepended at runtime; these are the
|
|
36
|
+
* always-on safety net so a single provider outage never strands users on a
|
|
37
|
+
* "0 balance" screen.
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_SOLANA_RPC_ENDPOINTS = [
|
|
40
|
+
"https://solana-mainnet.g.alchemy.com/v2",
|
|
41
|
+
"https://solana-rpc.publicnode.com",
|
|
42
|
+
"https://api.mainnet.solana.com",
|
|
43
|
+
"https://rpc.ankr.com/solana"
|
|
49
44
|
];
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Back-compat alias for callers that still want a single default endpoint.
|
|
47
|
+
* New code should prefer the full list and the failover helper.
|
|
48
|
+
*/
|
|
49
|
+
const DEFAULT_SOLANA_RPC_ENDPOINT = "https://solana-rpc.publicnode.com";
|
|
50
|
+
const SVM_CHAIN_IDS = /* @__PURE__ */ new Set([792703809]);
|
|
51
|
+
const MULTI_CHAIN_SOLANA_WALLET_NAMES = /* @__PURE__ */ new Set(["Phantom"]);
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/deposit/use-svm-address.ts
|
|
54
|
+
function getPhantomProvider$1() {
|
|
55
|
+
if (typeof window === "undefined") return void 0;
|
|
56
|
+
const w = window;
|
|
57
|
+
return w.phantom?.solana ?? w.solana;
|
|
62
58
|
}
|
|
63
59
|
function useSvmAddress() {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
});
|
|
78
|
-
}, []);
|
|
79
|
-
return (_a = adapterPublicKey == null ? void 0 : adapterPublicKey.toBase58()) != null ? _a : phantomAddress;
|
|
60
|
+
const { publicKey: adapterPublicKey } = useWallet();
|
|
61
|
+
const [phantomAddress, setPhantomAddress] = useState(void 0);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const provider = getPhantomProvider$1();
|
|
64
|
+
if (!provider) return;
|
|
65
|
+
const existing = provider.publicKey?.toBase58();
|
|
66
|
+
if (existing) {
|
|
67
|
+
setPhantomAddress(existing);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
provider.connect({ onlyIfTrusted: true }).then((res) => setPhantomAddress(res.publicKey.toString())).catch(() => {});
|
|
71
|
+
}, []);
|
|
72
|
+
return adapterPublicKey?.toBase58() ?? phantomAddress;
|
|
80
73
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/deposit/svm-connections.ts
|
|
76
|
+
/**
|
|
77
|
+
* Resolves the priority-ordered list of Solana RPC URLs to try, with the
|
|
78
|
+
* partner-supplied URL first (when present) and the built-in defaults as a
|
|
79
|
+
* safety net. De-duplicated so we never hit the same endpoint twice in a
|
|
80
|
+
* single failover round.
|
|
81
|
+
*/
|
|
86
82
|
function resolveSvmRpcEndpoints(partnerUrl) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
return out;
|
|
83
|
+
const seen = /* @__PURE__ */ new Set();
|
|
84
|
+
const out = [];
|
|
85
|
+
const candidates = partnerUrl ? [partnerUrl, ...DEFAULT_SOLANA_RPC_ENDPOINTS] : DEFAULT_SOLANA_RPC_ENDPOINTS;
|
|
86
|
+
for (const url of candidates) if (!seen.has(url)) {
|
|
87
|
+
seen.add(url);
|
|
88
|
+
out.push(url);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
97
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Returns a memoized list of `Connection` instances, one per resolved RPC
|
|
94
|
+
* endpoint, in failover priority order. The primary connection is
|
|
95
|
+
* `connections[0]`; subsequent entries are tried only when an operation
|
|
96
|
+
* against the previous entry throws.
|
|
97
|
+
*
|
|
98
|
+
* `new Connection(...)` is lazy (no socket is opened), so eagerly building
|
|
99
|
+
* fallback connections has negligible cost.
|
|
100
|
+
*/
|
|
98
101
|
function useSvmConnections(commitment) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
const { solanaRpcUrl } = useAggUiConfig();
|
|
103
|
+
const client = useOptionalAggClient();
|
|
104
|
+
const { data: tokenData } = useQuery({
|
|
105
|
+
queryKey: ["alchemy-rpc-token"],
|
|
106
|
+
queryFn: () => client ? client.getRpcToken() : Promise.resolve({
|
|
107
|
+
token: "",
|
|
108
|
+
expiresAt: 0
|
|
109
|
+
}),
|
|
110
|
+
enabled: !!client,
|
|
111
|
+
staleTime: 3e6
|
|
112
|
+
});
|
|
113
|
+
const token = tokenData?.token;
|
|
114
|
+
return useMemo(() => {
|
|
115
|
+
return resolveSvmRpcEndpoints(solanaRpcUrl).map((url) => {
|
|
116
|
+
const headers = {};
|
|
117
|
+
if (url.includes("alchemy.com") && token) headers["Authorization"] = `Bearer ${token}`;
|
|
118
|
+
return new Connection(url, {
|
|
119
|
+
commitment,
|
|
120
|
+
httpHeaders: Object.keys(headers).length > 0 ? headers : void 0
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}, [
|
|
124
|
+
solanaRpcUrl,
|
|
125
|
+
commitment,
|
|
126
|
+
token
|
|
127
|
+
]);
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Runs `operation` against the first connection that returns successfully.
|
|
131
|
+
* Connections are tried in priority order; on any thrown error (network
|
|
132
|
+
* failure, timeout, RPC error) we advance to the next.
|
|
133
|
+
*
|
|
134
|
+
* The happy path issues exactly one underlying RPC call — we only fan out
|
|
135
|
+
* when the previous endpoint failed. If every endpoint fails, the last
|
|
136
|
+
* error is rethrown so the caller (and `useQuery`) sees the real cause.
|
|
137
|
+
*/
|
|
138
|
+
async function withSvmFailover(connections, operation) {
|
|
139
|
+
if (connections.length === 0) throw new Error("No Solana RPC connections configured");
|
|
140
|
+
let lastError;
|
|
141
|
+
for (const connection of connections) try {
|
|
142
|
+
return await operation(connection);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
lastError = err;
|
|
145
|
+
}
|
|
146
|
+
throw lastError instanceof Error ? lastError : new Error(typeof lastError === "string" ? lastError : "All Solana RPC endpoints failed");
|
|
138
147
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/deposit/use-wallet-token-balance.ts
|
|
150
|
+
const ERC20_BALANCE_OF_ABI = [{
|
|
151
|
+
name: "balanceOf",
|
|
152
|
+
type: "function",
|
|
153
|
+
stateMutability: "view",
|
|
154
|
+
inputs: [{
|
|
155
|
+
name: "account",
|
|
156
|
+
type: "address"
|
|
157
|
+
}],
|
|
158
|
+
outputs: [{
|
|
159
|
+
name: "",
|
|
160
|
+
type: "uint256"
|
|
161
|
+
}]
|
|
162
|
+
}];
|
|
163
|
+
const SVM_BALANCE_RPC_TIMEOUT_MS = 2e3;
|
|
164
|
+
const SVM_BALANCE_STALE_TIME_MS = 3e4;
|
|
152
165
|
var SvmBalanceTimeoutError = class extends Error {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
constructor() {
|
|
167
|
+
super("Solana balance request timed out");
|
|
168
|
+
this.name = "SvmBalanceTimeoutError";
|
|
169
|
+
}
|
|
157
170
|
};
|
|
158
171
|
function withTimeout(promise, timeoutMs) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
);
|
|
171
|
-
});
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
const timer = setTimeout(() => reject(new SvmBalanceTimeoutError()), timeoutMs);
|
|
174
|
+
promise.then((value) => {
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
resolve(value);
|
|
177
|
+
}, (err) => {
|
|
178
|
+
clearTimeout(timer);
|
|
179
|
+
reject(err);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
172
182
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Reads the connected wallet's SPL token balance for `mint`.
|
|
185
|
+
*
|
|
186
|
+
* A Solana wallet routinely owns multiple SPL accounts for the same mint —
|
|
187
|
+
* the canonical ATA plus dust accounts created by dApps (each with their
|
|
188
|
+
* own `closeAuthority`). The RPC does not order them deterministically, so
|
|
189
|
+
* picking `accounts.value[0]` and calling `getTokenAccountBalance` for it
|
|
190
|
+
* (the previous implementation) frequently returned 0 even when the user
|
|
191
|
+
* was funded on a sibling account. Phantom/Solflare display the sum across
|
|
192
|
+
* every account on the mint; we match that here.
|
|
193
|
+
*
|
|
194
|
+
* Uses `getParsedTokenAccountsByOwner` so `uiAmount` arrives on the first
|
|
195
|
+
* response — one RPC round-trip per refetch instead of two. The per-endpoint
|
|
196
|
+
* timeout caps how long we wait before falling over to the next RPC.
|
|
197
|
+
*/
|
|
198
|
+
async function fetchSvmTokenBalance(connections, owner, mint) {
|
|
199
|
+
const accounts = await withSvmFailover(connections, (connection) => withTimeout(connection.getParsedTokenAccountsByOwner(owner, { mint }), SVM_BALANCE_RPC_TIMEOUT_MS));
|
|
200
|
+
let total = 0;
|
|
201
|
+
for (const entry of accounts.value) {
|
|
202
|
+
const uiAmount = entry.account.data.parsed?.info?.tokenAmount?.uiAmount;
|
|
203
|
+
if (typeof uiAmount === "number" && Number.isFinite(uiAmount)) total += uiAmount;
|
|
204
|
+
}
|
|
205
|
+
return total;
|
|
192
206
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Reads the connected wallet's on-chain balance for a given token on a given chain.
|
|
209
|
+
* Dispatches to wagmi (EVM) or @solana/web3.js (SVM) based on chainId.
|
|
210
|
+
*/
|
|
211
|
+
function useWalletTokenBalance({ isOpen = true, chainId, tokenAddress, decimals, svmAddress }) {
|
|
212
|
+
const isSvm = chainId !== void 0 && SVM_CHAIN_IDS.has(chainId);
|
|
213
|
+
const { address: evmAddress } = useAccount();
|
|
214
|
+
const { data: rawEvmBalance, isLoading: evmLoading } = useReadContract({
|
|
215
|
+
address: tokenAddress,
|
|
216
|
+
chainId,
|
|
217
|
+
abi: ERC20_BALANCE_OF_ABI,
|
|
218
|
+
functionName: "balanceOf",
|
|
219
|
+
args: evmAddress ? [evmAddress] : void 0,
|
|
220
|
+
query: {
|
|
221
|
+
enabled: isOpen && !isSvm && !!chainId && !!tokenAddress && !!evmAddress && decimals !== void 0,
|
|
222
|
+
staleTime: 0,
|
|
223
|
+
refetchOnMount: true
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
const resolvedSvmAddress = useSvmAddress() ?? svmAddress;
|
|
227
|
+
const svmEnabled = isOpen && isSvm && !!resolvedSvmAddress && !!tokenAddress;
|
|
228
|
+
const svmConnections = useSvmConnections();
|
|
229
|
+
const svmQuery = useQuery({
|
|
230
|
+
queryKey: [
|
|
231
|
+
"svm-token-balance",
|
|
232
|
+
resolvedSvmAddress ?? null,
|
|
233
|
+
tokenAddress ?? null,
|
|
234
|
+
isOpen
|
|
235
|
+
],
|
|
236
|
+
enabled: svmEnabled,
|
|
237
|
+
staleTime: SVM_BALANCE_STALE_TIME_MS,
|
|
238
|
+
retry: false,
|
|
239
|
+
refetchOnWindowFocus: "always",
|
|
240
|
+
refetchOnReconnect: "always",
|
|
241
|
+
queryFn: async () => {
|
|
242
|
+
if (!resolvedSvmAddress || !tokenAddress) return 0;
|
|
243
|
+
const owner = new PublicKey(resolvedSvmAddress);
|
|
244
|
+
const mint = new PublicKey(tokenAddress);
|
|
245
|
+
return await fetchSvmTokenBalance(svmConnections, owner, mint);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
if (isSvm) {
|
|
249
|
+
const awaitingSelection = isOpen && !!chainId && !tokenAddress;
|
|
250
|
+
return {
|
|
251
|
+
balance: svmQuery.data ?? 0,
|
|
252
|
+
isLoading: awaitingSelection || svmQuery.isLoading || svmQuery.isFetching
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
balance: rawEvmBalance !== void 0 && decimals !== void 0 ? Number(formatUnits(rawEvmBalance, decimals)) : 0,
|
|
257
|
+
isLoading: isOpen && !!chainId && !tokenAddress || evmLoading
|
|
258
|
+
};
|
|
240
259
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
import {
|
|
248
|
-
Keypair,
|
|
249
|
-
PublicKey as PublicKey2,
|
|
250
|
-
SystemProgram,
|
|
251
|
-
Transaction,
|
|
252
|
-
TransactionInstruction
|
|
253
|
-
} from "@solana/web3.js";
|
|
254
|
-
function getPhantomProvider2() {
|
|
255
|
-
var _a, _b;
|
|
256
|
-
if (typeof window === "undefined") return void 0;
|
|
257
|
-
const w = window;
|
|
258
|
-
return (_b = (_a = w.phantom) == null ? void 0 : _a.solana) != null ? _b : w.solana;
|
|
260
|
+
//#endregion
|
|
261
|
+
//#region src/deposit/use-wallet-send-token.ts
|
|
262
|
+
function getPhantomProvider() {
|
|
263
|
+
if (typeof window === "undefined") return void 0;
|
|
264
|
+
const w = window;
|
|
265
|
+
return w.phantom?.solana ?? w.solana;
|
|
259
266
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
267
|
+
const ERC20_TRANSFER_ABI = [{
|
|
268
|
+
constant: false,
|
|
269
|
+
inputs: [{
|
|
270
|
+
name: "to",
|
|
271
|
+
type: "address"
|
|
272
|
+
}, {
|
|
273
|
+
name: "amount",
|
|
274
|
+
type: "uint256"
|
|
275
|
+
}],
|
|
276
|
+
name: "transfer",
|
|
277
|
+
outputs: [{
|
|
278
|
+
name: "",
|
|
279
|
+
type: "bool"
|
|
280
|
+
}],
|
|
281
|
+
type: "function"
|
|
282
|
+
}];
|
|
283
|
+
/**
|
|
284
|
+
* Dual-chain sendToken implementation:
|
|
285
|
+
* - EVM: uses wagmi writeContract to call ERC-20 transfer
|
|
286
|
+
* - SVM: uses wallet-adapter (or injected Phantom) to submit an SPL TransferChecked tx
|
|
287
|
+
*
|
|
288
|
+
* Matches the `WalletActions.sendToken` signature so it can be passed into
|
|
289
|
+
* `AggProvider` config directly, or composed inside `useDepositFlow`.
|
|
290
|
+
*/
|
|
272
291
|
function useWalletSendToken() {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
292
|
+
const svmConnections = useSvmConnections("confirmed");
|
|
293
|
+
const { writeContractAsync } = useWriteContract();
|
|
294
|
+
const { publicKey, sendTransaction } = useWallet();
|
|
295
|
+
return useCallback(async (params) => {
|
|
296
|
+
if (!SVM_CHAIN_IDS.has(params.chainId)) {
|
|
297
|
+
const tokenAddress = params.token.address;
|
|
298
|
+
if (!tokenAddress) throw new Error("Missing ERC-20 token address for EVM transfer");
|
|
299
|
+
const value = parseUnits(params.amount || "0", params.token.decimals);
|
|
300
|
+
return { txId: await writeContractAsync({
|
|
301
|
+
chainId: params.chainId,
|
|
302
|
+
address: tokenAddress,
|
|
303
|
+
abi: ERC20_TRANSFER_ABI,
|
|
304
|
+
functionName: "transfer",
|
|
305
|
+
args: [params.to, value]
|
|
306
|
+
}) };
|
|
307
|
+
}
|
|
308
|
+
if (!params.token.address) throw new Error("Missing SPL token mint address for Solana transfer");
|
|
309
|
+
const phantom = getPhantomProvider();
|
|
310
|
+
let ownerBase58 = publicKey?.toBase58();
|
|
311
|
+
if (!ownerBase58 && phantom) try {
|
|
312
|
+
ownerBase58 = (await phantom.connect()).publicKey.toString();
|
|
313
|
+
} catch (err) {
|
|
314
|
+
throw new Error(`Failed to connect Phantom: ${err instanceof Error ? err.message : String(err)}`);
|
|
315
|
+
}
|
|
316
|
+
if (!ownerBase58) throw new Error("No Solana wallet connected");
|
|
317
|
+
const TOKEN_PROGRAM_ID = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
|
|
318
|
+
const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
|
|
319
|
+
const owner = new PublicKey(ownerBase58);
|
|
320
|
+
const mint = new PublicKey(params.token.address);
|
|
321
|
+
const destinationOwner = new PublicKey(params.to);
|
|
322
|
+
const getAta = (walletPk, mintPk) => {
|
|
323
|
+
const [ata] = PublicKey.findProgramAddressSync([
|
|
324
|
+
walletPk.toBuffer(),
|
|
325
|
+
TOKEN_PROGRAM_ID.toBuffer(),
|
|
326
|
+
mintPk.toBuffer()
|
|
327
|
+
], ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
328
|
+
return ata;
|
|
329
|
+
};
|
|
330
|
+
const sourceAta = getAta(owner, mint);
|
|
331
|
+
const destinationAta = getAta(destinationOwner, mint);
|
|
332
|
+
return { txId: await withSvmFailover(svmConnections, async (sendConnection) => {
|
|
333
|
+
const tx = new Transaction();
|
|
334
|
+
let destinationTokenAccountSigner = null;
|
|
335
|
+
const destinationOwnerInfo = await sendConnection.getAccountInfo(destinationOwner);
|
|
336
|
+
const destinationInfo = await sendConnection.getAccountInfo(destinationAta);
|
|
337
|
+
let destinationTokenAccount = destinationAta;
|
|
338
|
+
if (!destinationInfo) {
|
|
339
|
+
const rentExemptLamports = await sendConnection.getMinimumBalanceForRentExemption(165);
|
|
340
|
+
const payerLamports = await sendConnection.getBalance(owner, "confirmed");
|
|
341
|
+
const requiredLamports = rentExemptLamports + 1e4;
|
|
342
|
+
if (payerLamports < requiredLamports) {
|
|
343
|
+
const requiredSol = (requiredLamports / 1e9).toFixed(6);
|
|
344
|
+
const currentSol = (payerLamports / 1e9).toFixed(6);
|
|
345
|
+
throw new Error(`This Solana deposit needs about ${requiredSol} SOL to create the destination token account, but the connected wallet only has ${currentSol} SOL available.`);
|
|
346
|
+
}
|
|
347
|
+
if (destinationOwnerInfo?.owner?.equals(SystemProgram.programId) ?? false) {
|
|
348
|
+
const createIx = new TransactionInstruction({
|
|
349
|
+
programId: ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
350
|
+
keys: [
|
|
351
|
+
{
|
|
352
|
+
pubkey: owner,
|
|
353
|
+
isSigner: true,
|
|
354
|
+
isWritable: true
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
pubkey: destinationAta,
|
|
358
|
+
isSigner: false,
|
|
359
|
+
isWritable: true
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
pubkey: destinationOwner,
|
|
363
|
+
isSigner: false,
|
|
364
|
+
isWritable: false
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
pubkey: mint,
|
|
368
|
+
isSigner: false,
|
|
369
|
+
isWritable: false
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
pubkey: SystemProgram.programId,
|
|
373
|
+
isSigner: false,
|
|
374
|
+
isWritable: false
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
pubkey: TOKEN_PROGRAM_ID,
|
|
378
|
+
isSigner: false,
|
|
379
|
+
isWritable: false
|
|
380
|
+
}
|
|
381
|
+
],
|
|
382
|
+
data: Buffer.alloc(0)
|
|
383
|
+
});
|
|
384
|
+
tx.add(createIx);
|
|
385
|
+
} else {
|
|
386
|
+
destinationTokenAccountSigner = Keypair.generate();
|
|
387
|
+
destinationTokenAccount = destinationTokenAccountSigner.publicKey;
|
|
388
|
+
tx.add(SystemProgram.createAccount({
|
|
389
|
+
fromPubkey: owner,
|
|
390
|
+
newAccountPubkey: destinationTokenAccount,
|
|
391
|
+
lamports: rentExemptLamports,
|
|
392
|
+
space: 165,
|
|
393
|
+
programId: TOKEN_PROGRAM_ID
|
|
394
|
+
}));
|
|
395
|
+
const initAccountData = Buffer.concat([Buffer.from([18]), destinationOwner.toBuffer()]);
|
|
396
|
+
tx.add(new TransactionInstruction({
|
|
397
|
+
programId: TOKEN_PROGRAM_ID,
|
|
398
|
+
keys: [{
|
|
399
|
+
pubkey: destinationTokenAccount,
|
|
400
|
+
isSigner: false,
|
|
401
|
+
isWritable: true
|
|
402
|
+
}, {
|
|
403
|
+
pubkey: mint,
|
|
404
|
+
isSigner: false,
|
|
405
|
+
isWritable: false
|
|
406
|
+
}],
|
|
407
|
+
data: initAccountData
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
let factor = BigInt(1);
|
|
412
|
+
for (let i = 0; i < params.token.decimals; i += 1) factor *= BigInt(10);
|
|
413
|
+
const amountRaw = BigInt(Math.trunc(Number(params.amount || "0") * Number(factor)));
|
|
414
|
+
const data = /* @__PURE__ */ new Uint8Array(10);
|
|
415
|
+
data[0] = 12;
|
|
416
|
+
let remaining = amountRaw;
|
|
417
|
+
const mask = BigInt(255);
|
|
418
|
+
for (let i = 0; i < 8; i += 1) {
|
|
419
|
+
data[1 + i] = Number(remaining & mask);
|
|
420
|
+
remaining >>= BigInt(8);
|
|
421
|
+
}
|
|
422
|
+
data[9] = params.token.decimals;
|
|
423
|
+
const transferIx = new TransactionInstruction({
|
|
424
|
+
programId: TOKEN_PROGRAM_ID,
|
|
425
|
+
keys: [
|
|
426
|
+
{
|
|
427
|
+
pubkey: sourceAta,
|
|
428
|
+
isSigner: false,
|
|
429
|
+
isWritable: true
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
pubkey: mint,
|
|
433
|
+
isSigner: false,
|
|
434
|
+
isWritable: false
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
pubkey: destinationTokenAccount,
|
|
438
|
+
isSigner: false,
|
|
439
|
+
isWritable: true
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
pubkey: owner,
|
|
443
|
+
isSigner: true,
|
|
444
|
+
isWritable: false
|
|
445
|
+
}
|
|
446
|
+
],
|
|
447
|
+
data: Buffer.from(data)
|
|
448
|
+
});
|
|
449
|
+
tx.add(transferIx);
|
|
450
|
+
tx.feePayer = owner;
|
|
451
|
+
const { blockhash } = await sendConnection.getLatestBlockhash("confirmed");
|
|
452
|
+
tx.recentBlockhash = blockhash;
|
|
453
|
+
if (destinationTokenAccountSigner) tx.partialSign(destinationTokenAccountSigner);
|
|
454
|
+
if (publicKey && sendTransaction) return await sendTransaction(tx, sendConnection);
|
|
455
|
+
if (phantom) return (await phantom.signAndSendTransaction(tx)).signature;
|
|
456
|
+
throw new Error("No Solana signer available");
|
|
457
|
+
}) };
|
|
458
|
+
}, [
|
|
459
|
+
publicKey,
|
|
460
|
+
sendTransaction,
|
|
461
|
+
svmConnections,
|
|
462
|
+
writeContractAsync
|
|
463
|
+
]);
|
|
435
464
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const gasFeeEth = Number(formatEther(gasFeeWei));
|
|
490
|
-
const gasFee = gasFeeEth > 0 ? `${gasFeeEth.toFixed(6)} ETH` : "";
|
|
491
|
-
return { status: "settled", gasFee };
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/deposit/use-wallet-transaction-status.ts
|
|
467
|
+
function useWalletTransactionStatus({ chainId, txId }) {
|
|
468
|
+
const isSvm = chainId !== void 0 && SVM_CHAIN_IDS.has(chainId);
|
|
469
|
+
const evmHash = !isSvm && txId ? txId : void 0;
|
|
470
|
+
const evmReceipt = useWaitForTransactionReceipt({
|
|
471
|
+
chainId,
|
|
472
|
+
hash: evmHash,
|
|
473
|
+
query: {
|
|
474
|
+
enabled: !!evmHash && !!chainId,
|
|
475
|
+
refetchInterval: 2e3
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
const svmConnections = useSvmConnections("confirmed");
|
|
479
|
+
const svmQuery = useQuery({
|
|
480
|
+
queryKey: [
|
|
481
|
+
"wallet-transaction-status",
|
|
482
|
+
chainId ?? null,
|
|
483
|
+
txId ?? null
|
|
484
|
+
],
|
|
485
|
+
enabled: isSvm && !!txId,
|
|
486
|
+
refetchInterval: (query) => query.state.data === "settled" || query.state.data === "error" ? false : 2e3,
|
|
487
|
+
queryFn: async () => {
|
|
488
|
+
if (!txId) return "submitted";
|
|
489
|
+
const [status] = (await withSvmFailover(svmConnections, (connection) => connection.getSignatureStatuses([txId], { searchTransactionHistory: true }))).value;
|
|
490
|
+
if (!status) return "submitted";
|
|
491
|
+
if (status.err) return "error";
|
|
492
|
+
if (status.confirmationStatus === "confirmed" || status.confirmationStatus === "finalized") return "settled";
|
|
493
|
+
return "confirming";
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
if (isSvm) return {
|
|
497
|
+
status: txId ? svmQuery.data ?? "submitted" : null,
|
|
498
|
+
gasFee: ""
|
|
499
|
+
};
|
|
500
|
+
if (!txId) return {
|
|
501
|
+
status: null,
|
|
502
|
+
gasFee: ""
|
|
503
|
+
};
|
|
504
|
+
if (evmReceipt.isError) return {
|
|
505
|
+
status: "error",
|
|
506
|
+
gasFee: ""
|
|
507
|
+
};
|
|
508
|
+
if (!evmReceipt.data) return {
|
|
509
|
+
status: evmReceipt.isPending ? "confirming" : "submitted",
|
|
510
|
+
gasFee: ""
|
|
511
|
+
};
|
|
512
|
+
const gasFeeWei = evmReceipt.data.effectiveGasPrice * evmReceipt.data.gasUsed;
|
|
513
|
+
const gasFeeEth = Number(formatEther(gasFeeWei));
|
|
514
|
+
return {
|
|
515
|
+
status: "settled",
|
|
516
|
+
gasFee: gasFeeEth > 0 ? `${gasFeeEth.toFixed(6)} ETH` : ""
|
|
517
|
+
};
|
|
492
518
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
519
|
+
//#endregion
|
|
520
|
+
//#region src/deposit/use-deposit-flow.ts
|
|
521
|
+
const DEFAULT_WALLET_CHAIN_ID = 1;
|
|
522
|
+
const DEFAULT_WALLET_TOKEN_SYMBOL = "USDC";
|
|
523
|
+
/**
|
|
524
|
+
* Orchestrator hook that encapsulates all wallet + card deposit state and
|
|
525
|
+
* glue previously hand-wired inside partner apps. Spread the return value
|
|
526
|
+
* onto `<DepositModal>` to get the full plug-and-play deposit experience.
|
|
527
|
+
*
|
|
528
|
+
* Requires wagmi + @solana/wallet-adapter-react providers in the React tree.
|
|
529
|
+
*/
|
|
500
530
|
function useDepositFlow(options) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
734
|
-
}, [syncBalancesMutate]),
|
|
735
|
-
cardRedirectUrl,
|
|
736
|
-
onGetCardQuotes: handleGetCardQuotes,
|
|
737
|
-
onCreateCardSession: handleCreateCardSession
|
|
738
|
-
};
|
|
531
|
+
const { open, onOpenChange } = options;
|
|
532
|
+
const client = useAggClient();
|
|
533
|
+
const { user } = useAggAuthState();
|
|
534
|
+
const { walletActions } = useAggUiConfig();
|
|
535
|
+
const { totalBalance } = useAggBalanceState();
|
|
536
|
+
const { supportedChains } = useDepositAddresses({ enabled: !!user });
|
|
537
|
+
const { mutate: syncBalancesMutate } = useSyncBalances();
|
|
538
|
+
const defaultSendToken = useWalletSendToken();
|
|
539
|
+
const sendToken = walletActions?.sendToken ?? defaultSendToken;
|
|
540
|
+
const { mutateAsync: getRampQuotes } = useRampQuotes();
|
|
541
|
+
const { mutateAsync: createRampSession } = useRampSession();
|
|
542
|
+
const [depositAmount, setDepositAmount] = useState("");
|
|
543
|
+
const [cardAmount, setCardAmount] = useState("");
|
|
544
|
+
const [cardNetwork, setCardNetwork] = useState("solana");
|
|
545
|
+
const [walletModalChainId, setWalletModalChainId] = useState(null);
|
|
546
|
+
const [walletModalTokenSymbol, setWalletModalTokenSymbol] = useState(null);
|
|
547
|
+
const [walletTransaction, setWalletTransaction] = useState(null);
|
|
548
|
+
const [walletTransactionError, setWalletTransactionError] = useState(null);
|
|
549
|
+
const [walletTransactionErrorTone, setWalletTransactionErrorTone] = useState("error");
|
|
550
|
+
const [pendingCardPurchaseSummary, setPendingCardPurchaseSummary] = useState(null);
|
|
551
|
+
const rampRedirectHandled = useRef(false);
|
|
552
|
+
useEffect(() => {
|
|
553
|
+
if (rampRedirectHandled.current) return;
|
|
554
|
+
if (typeof window === "undefined") return;
|
|
555
|
+
const url = new URL(window.location.href);
|
|
556
|
+
const cryptoAmount = url.searchParams.get("cryptoAmount");
|
|
557
|
+
const cryptoCurrency = url.searchParams.get("cryptoCurrency");
|
|
558
|
+
if (!cryptoAmount) return;
|
|
559
|
+
rampRedirectHandled.current = true;
|
|
560
|
+
const network = url.searchParams.get("network") ?? "";
|
|
561
|
+
const totalFee = url.searchParams.get("totalFeeInFiat") ?? "";
|
|
562
|
+
setPendingCardPurchaseSummary({
|
|
563
|
+
amountReceived: `+${cryptoAmount} ${cryptoCurrency ?? "USDC"}`,
|
|
564
|
+
network: network.charAt(0).toUpperCase() + network.slice(1),
|
|
565
|
+
fees: totalFee ? `$${totalFee}` : ""
|
|
566
|
+
});
|
|
567
|
+
onOpenChange(true);
|
|
568
|
+
}, []);
|
|
569
|
+
const { chainId: connectedChainId, address: connectedAddress } = useAccount();
|
|
570
|
+
const { switchChainAsync } = useSwitchChain();
|
|
571
|
+
const { wallet: solanaWallet } = useWallet();
|
|
572
|
+
const resolvedSvmAddress = useSvmAddress();
|
|
573
|
+
const solanaAdapterName = solanaWallet?.adapter?.name;
|
|
574
|
+
const isMultiChainSolanaWallet = Boolean(solanaAdapterName && MULTI_CHAIN_SOLANA_WALLET_NAMES.has(solanaAdapterName));
|
|
575
|
+
const hasSolanaCapability = Boolean(resolvedSvmAddress || solanaWallet?.adapter);
|
|
576
|
+
const hasEvmCapability = Boolean(connectedAddress) || isMultiChainSolanaWallet;
|
|
577
|
+
const defaultBalanceChainId = hasEvmCapability && supportedChains?.some((chain) => chain.chainId === DEFAULT_WALLET_CHAIN_ID) ? DEFAULT_WALLET_CHAIN_ID : connectedChainId;
|
|
578
|
+
const balanceChainId = walletModalChainId ? Number(walletModalChainId) : defaultBalanceChainId;
|
|
579
|
+
const selectedChainTokens = useMemo(() => {
|
|
580
|
+
if (!supportedChains || !balanceChainId) return [];
|
|
581
|
+
return supportedChains.find((c) => c.chainId === balanceChainId)?.tokens ?? [];
|
|
582
|
+
}, [supportedChains, balanceChainId]);
|
|
583
|
+
const selectedTokenMeta = useMemo(() => {
|
|
584
|
+
const preferredTokenSymbol = walletModalTokenSymbol ?? DEFAULT_WALLET_TOKEN_SYMBOL;
|
|
585
|
+
const match = selectedChainTokens.find((t) => t.symbol === preferredTokenSymbol);
|
|
586
|
+
return match ? {
|
|
587
|
+
address: match.address,
|
|
588
|
+
decimals: match.decimals
|
|
589
|
+
} : void 0;
|
|
590
|
+
}, [selectedChainTokens, walletModalTokenSymbol]);
|
|
591
|
+
const isSvmBalanceChain = balanceChainId !== void 0 && SVM_CHAIN_IDS.has(balanceChainId);
|
|
592
|
+
const walletAddress = useMemo(() => {
|
|
593
|
+
if (isSvmBalanceChain) return resolvedSvmAddress ?? "";
|
|
594
|
+
return connectedAddress ?? "";
|
|
595
|
+
}, [
|
|
596
|
+
connectedAddress,
|
|
597
|
+
isSvmBalanceChain,
|
|
598
|
+
resolvedSvmAddress
|
|
599
|
+
]);
|
|
600
|
+
const walletLabel = useMemo(() => {
|
|
601
|
+
return walletAddress ? `${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}` : "—";
|
|
602
|
+
}, [walletAddress]);
|
|
603
|
+
const { balance: walletBalance, isLoading: isWalletBalanceLoading } = useWalletTokenBalance({
|
|
604
|
+
isOpen: open,
|
|
605
|
+
chainId: balanceChainId,
|
|
606
|
+
tokenAddress: selectedTokenMeta?.address,
|
|
607
|
+
decimals: selectedTokenMeta?.decimals
|
|
608
|
+
});
|
|
609
|
+
const { status: observedWalletTransactionStatus, gasFee: walletTransactionGasFee } = useWalletTransactionStatus({
|
|
610
|
+
chainId: walletTransaction?.chainId,
|
|
611
|
+
txId: walletTransaction?.txId ?? null
|
|
612
|
+
});
|
|
613
|
+
const walletTransactionStatus = walletTransactionError ? "error" : walletTransaction ? observedWalletTransactionStatus ?? "submitted" : void 0;
|
|
614
|
+
const syncedDepositTxIdRef = useRef(null);
|
|
615
|
+
useEffect(() => {
|
|
616
|
+
if (walletTransactionStatus !== "settled" || !walletTransaction?.txId) return;
|
|
617
|
+
if (syncedDepositTxIdRef.current === walletTransaction.txId) return;
|
|
618
|
+
syncedDepositTxIdRef.current = walletTransaction.txId;
|
|
619
|
+
syncBalancesMutate();
|
|
620
|
+
}, [
|
|
621
|
+
syncBalancesMutate,
|
|
622
|
+
walletTransaction?.txId,
|
|
623
|
+
walletTransactionStatus
|
|
624
|
+
]);
|
|
625
|
+
const handleDepositModalOpenChange = useCallback((isOpen) => {
|
|
626
|
+
if (!isOpen) {
|
|
627
|
+
setDepositAmount("");
|
|
628
|
+
setCardAmount("");
|
|
629
|
+
setCardNetwork("solana");
|
|
630
|
+
setWalletModalChainId(null);
|
|
631
|
+
setWalletModalTokenSymbol(null);
|
|
632
|
+
setWalletTransaction(null);
|
|
633
|
+
setWalletTransactionError(null);
|
|
634
|
+
setWalletTransactionErrorTone("error");
|
|
635
|
+
setPendingCardPurchaseSummary(null);
|
|
636
|
+
syncedDepositTxIdRef.current = null;
|
|
637
|
+
}
|
|
638
|
+
onOpenChange(isOpen);
|
|
639
|
+
}, [onOpenChange]);
|
|
640
|
+
const handleCancelWalletDeposit = useCallback(() => {
|
|
641
|
+
setWalletTransaction(null);
|
|
642
|
+
setWalletTransactionError(null);
|
|
643
|
+
setWalletTransactionErrorTone("error");
|
|
644
|
+
syncedDepositTxIdRef.current = null;
|
|
645
|
+
}, []);
|
|
646
|
+
const handleConfirmWalletDeposit = useCallback(async (params) => {
|
|
647
|
+
const { address, amount, chainId, token } = params;
|
|
648
|
+
const tokenMeta = (supportedChains?.find((c) => c.chainId === chainId) ?? null)?.tokens.find((t) => t.symbol === token) ?? null;
|
|
649
|
+
try {
|
|
650
|
+
setWalletTransactionError(null);
|
|
651
|
+
setWalletTransactionErrorTone("error");
|
|
652
|
+
if (!SVM_CHAIN_IDS.has(chainId) && connectedChainId && connectedChainId !== chainId) await switchChainAsync({ chainId });
|
|
653
|
+
const { txId } = await sendToken({
|
|
654
|
+
chainId,
|
|
655
|
+
token: {
|
|
656
|
+
symbol: token,
|
|
657
|
+
address: tokenMeta?.address,
|
|
658
|
+
decimals: tokenMeta?.decimals ?? 6
|
|
659
|
+
},
|
|
660
|
+
to: address,
|
|
661
|
+
amount
|
|
662
|
+
});
|
|
663
|
+
setWalletTransaction({
|
|
664
|
+
chainId,
|
|
665
|
+
txId
|
|
666
|
+
});
|
|
667
|
+
client.recordDepositIntent({
|
|
668
|
+
chainId,
|
|
669
|
+
txHash: txId
|
|
670
|
+
}).catch(() => {});
|
|
671
|
+
} catch (err) {
|
|
672
|
+
setWalletTransaction(null);
|
|
673
|
+
const normalizedError = normalizeWalletError(err, supportedChains);
|
|
674
|
+
setWalletTransactionError(normalizedError.message);
|
|
675
|
+
setWalletTransactionErrorTone(normalizedError.tone);
|
|
676
|
+
throw err;
|
|
677
|
+
}
|
|
678
|
+
}, [
|
|
679
|
+
client,
|
|
680
|
+
connectedChainId,
|
|
681
|
+
sendToken,
|
|
682
|
+
supportedChains,
|
|
683
|
+
switchChainAsync
|
|
684
|
+
]);
|
|
685
|
+
const handleGetCardQuotes = useCallback(async (params) => {
|
|
686
|
+
return (await getRampQuotes(params)).map((q, i) => ({
|
|
687
|
+
id: q.serviceProvider,
|
|
688
|
+
name: q.serviceProvider,
|
|
689
|
+
quote: `~ ${q.destinationAmount.toFixed(2)} USDC`,
|
|
690
|
+
fee: `$${q.totalFee.toFixed(2)}`,
|
|
691
|
+
badge: i === 0 ? "best" : q.lowKyc ? "low-kyc" : void 0
|
|
692
|
+
}));
|
|
693
|
+
}, [getRampQuotes]);
|
|
694
|
+
const handleCreateCardSession = useCallback(async (params) => {
|
|
695
|
+
if (!params.walletAddress) throw new Error("Deposit address not available for the selected network");
|
|
696
|
+
const session = await createRampSession(params);
|
|
697
|
+
return {
|
|
698
|
+
id: session.id,
|
|
699
|
+
widgetUrl: session.widgetUrl,
|
|
700
|
+
serviceProviderWidgetUrl: session.serviceProviderWidgetUrl
|
|
701
|
+
};
|
|
702
|
+
}, [createRampSession]);
|
|
703
|
+
const cardRedirectUrl = typeof window !== "undefined" ? `${window.location.origin}/profile?tab=activity` : void 0;
|
|
704
|
+
return {
|
|
705
|
+
open,
|
|
706
|
+
onOpenChange: handleDepositModalOpenChange,
|
|
707
|
+
pendingCardPurchaseSummary,
|
|
708
|
+
walletFlow: {
|
|
709
|
+
balance: totalBalance ?? 0,
|
|
710
|
+
walletLabel,
|
|
711
|
+
walletBalance,
|
|
712
|
+
isWalletBalanceLoading,
|
|
713
|
+
amount: depositAmount,
|
|
714
|
+
formErrorMessage: walletTransaction ? void 0 : walletTransactionError ?? void 0,
|
|
715
|
+
formErrorTone: walletTransactionErrorTone,
|
|
716
|
+
transactionStatus: walletTransactionStatus,
|
|
717
|
+
transactionErrorMessage: walletTransactionError ?? void 0,
|
|
718
|
+
successSummary: {
|
|
719
|
+
fromWallet: walletLabel,
|
|
720
|
+
gasFee: walletTransactionGasFee
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
onWalletAmountChange: setDepositAmount,
|
|
724
|
+
onWalletMax: useCallback(() => setDepositAmount(String(walletBalance)), [walletBalance]),
|
|
725
|
+
onConfirmWalletDeposit: handleConfirmWalletDeposit,
|
|
726
|
+
onCancelWalletDeposit: handleCancelWalletDeposit,
|
|
727
|
+
onWalletNetworkChange: setWalletModalChainId,
|
|
728
|
+
onWalletTokenChange: setWalletModalTokenSymbol,
|
|
729
|
+
initialWalletChainId: connectedChainId ? String(connectedChainId) : void 0,
|
|
730
|
+
connectedWalletKind: hasEvmCapability && !hasSolanaCapability ? "evm" : hasSolanaCapability && !hasEvmCapability ? "solana" : void 0,
|
|
731
|
+
sendCryptoConfig: {
|
|
732
|
+
minDeposit: "$1",
|
|
733
|
+
eta: "~30s"
|
|
734
|
+
},
|
|
735
|
+
onDoneSendCrypto: useCallback(() => handleDepositModalOpenChange(false), [handleDepositModalOpenChange]),
|
|
736
|
+
cardFlow: {
|
|
737
|
+
amount: cardAmount,
|
|
738
|
+
currency: "USD",
|
|
739
|
+
tokenOptions: [{
|
|
740
|
+
value: "USDC",
|
|
741
|
+
label: "USDC"
|
|
742
|
+
}],
|
|
743
|
+
selectedToken: "USDC",
|
|
744
|
+
selectedNetwork: cardNetwork,
|
|
745
|
+
providers: [],
|
|
746
|
+
purchaseSummary: {
|
|
747
|
+
amountReceived: "",
|
|
748
|
+
network: "",
|
|
749
|
+
fees: ""
|
|
750
|
+
},
|
|
751
|
+
minAmount: 11
|
|
752
|
+
},
|
|
753
|
+
onCardAmountChange: setCardAmount,
|
|
754
|
+
onCardNetworkChange: setCardNetwork,
|
|
755
|
+
onDoneCardPurchase: useCallback(() => {
|
|
756
|
+
syncBalancesMutate();
|
|
757
|
+
if (typeof window !== "undefined") window.history.replaceState({}, "", window.location.pathname);
|
|
758
|
+
}, [syncBalancesMutate]),
|
|
759
|
+
cardRedirectUrl,
|
|
760
|
+
onGetCardQuotes: handleGetCardQuotes,
|
|
761
|
+
onCreateCardSession: handleCreateCardSession
|
|
762
|
+
};
|
|
739
763
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
useDepositAddresses,
|
|
745
|
-
useDepositFlow,
|
|
746
|
-
useSyncBalances,
|
|
747
|
-
useWalletSendToken,
|
|
748
|
-
useWalletTokenBalance,
|
|
749
|
-
useWalletTransactionStatus
|
|
750
|
-
};
|
|
764
|
+
//#endregion
|
|
765
|
+
export { DEFAULT_SOLANA_RPC_ENDPOINT, SVM_CHAIN_IDS, normalizeWalletError, useDepositAddresses, useDepositFlow, useSyncBalances, useWalletSendToken, useWalletTokenBalance, useWalletTransactionStatus };
|
|
766
|
+
|
|
767
|
+
//# sourceMappingURL=deposit.mjs.map
|