@shogun-sdk/swap 0.0.2-test.4 → 0.0.2-test.41
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/core.cjs +1050 -165
- package/dist/core.d.cts +4 -136
- package/dist/core.d.ts +4 -136
- package/dist/core.js +1050 -138
- package/dist/index-BGHbFEV3.d.cts +401 -0
- package/dist/index-DB1gT72l.d.ts +401 -0
- package/dist/index.cjs +1038 -535
- package/dist/index.d.cts +4 -9
- package/dist/index.d.ts +4 -9
- package/dist/index.js +1038 -505
- package/dist/react.cjs +1588 -487
- package/dist/react.d.cts +285 -88
- package/dist/react.d.ts +285 -88
- package/dist/react.js +1580 -469
- package/dist/{wallet-MmUIz8GE.d.cts → wallet-B9bKceyN.d.cts} +3 -3
- package/dist/{wallet-MmUIz8GE.d.ts → wallet-B9bKceyN.d.ts} +3 -3
- package/dist/wallet-adapter.cjs +25659 -27
- package/dist/wallet-adapter.d.cts +1 -2
- package/dist/wallet-adapter.d.ts +1 -2
- package/dist/wallet-adapter.js +25679 -16
- package/package.json +44 -14
- package/dist/execute-FaLLPp1i.d.cts +0 -147
- package/dist/execute-HX1fQ7wG.d.ts +0 -147
package/dist/index.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return intentsGetTokenList(params);
|
|
5
|
-
}
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
4
|
|
|
7
5
|
// src/core/getQuote.ts
|
|
8
6
|
import { QuoteProvider } from "@shogun-sdk/intents-sdk";
|
|
9
7
|
import { parseUnits } from "viem";
|
|
10
8
|
|
|
11
|
-
// src/core/
|
|
12
|
-
import { isEvmChain } from "@shogun-sdk/intents-sdk";
|
|
9
|
+
// src/core/execute/normalizeNative.ts
|
|
10
|
+
import { isEvmChain as isEvmChain2 } from "@shogun-sdk/intents-sdk";
|
|
13
11
|
|
|
14
12
|
// src/utils/address.ts
|
|
13
|
+
import { zeroAddress } from "viem";
|
|
15
14
|
var NATIVE_TOKEN = {
|
|
16
15
|
ETH: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
|
17
16
|
SOL: "So11111111111111111111111111111111111111111",
|
|
@@ -22,13 +21,32 @@ var isNativeAddress = (tokenAddress) => {
|
|
|
22
21
|
const normalizedTokenAddress = tokenAddress.toLowerCase();
|
|
23
22
|
return !!tokenAddress && NATIVE_ADDRESSES.includes(normalizedTokenAddress);
|
|
24
23
|
};
|
|
24
|
+
function normalizeEvmTokenAddress(address) {
|
|
25
|
+
const lower = address.toLowerCase();
|
|
26
|
+
return lower === NATIVE_TOKEN.ETH.toLowerCase() ? zeroAddress : address;
|
|
27
|
+
}
|
|
25
28
|
|
|
26
29
|
// src/utils/chain.ts
|
|
27
|
-
import { ChainID } from "@shogun-sdk/intents-sdk";
|
|
28
|
-
var SOLANA_CHAIN_ID =
|
|
29
|
-
var
|
|
30
|
+
import { ChainID as BaseChainID, isEvmChain as isEvmChainIntent } from "@shogun-sdk/intents-sdk";
|
|
31
|
+
var SOLANA_CHAIN_ID = BaseChainID.Solana;
|
|
32
|
+
var CURRENT_SUPPORTED = [
|
|
33
|
+
BaseChainID.Solana,
|
|
34
|
+
BaseChainID.BSC,
|
|
35
|
+
BaseChainID.Base,
|
|
36
|
+
BaseChainID.MONAD
|
|
37
|
+
];
|
|
38
|
+
var ChainId = Object.entries(BaseChainID).reduce(
|
|
39
|
+
(acc, [key, value]) => {
|
|
40
|
+
if (typeof value === "number" && CURRENT_SUPPORTED.includes(value)) {
|
|
41
|
+
acc[key] = value;
|
|
42
|
+
}
|
|
43
|
+
return acc;
|
|
44
|
+
},
|
|
45
|
+
{}
|
|
46
|
+
);
|
|
47
|
+
var SupportedChainsInternal = [
|
|
30
48
|
{
|
|
31
|
-
id:
|
|
49
|
+
id: BaseChainID.Arbitrum,
|
|
32
50
|
name: "Arbitrum",
|
|
33
51
|
isEVM: true,
|
|
34
52
|
wrapped: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
@@ -37,7 +55,7 @@ var SupportedChains = [
|
|
|
37
55
|
tokenAddress: NATIVE_TOKEN.ETH
|
|
38
56
|
},
|
|
39
57
|
{
|
|
40
|
-
id:
|
|
58
|
+
id: BaseChainID.Optimism,
|
|
41
59
|
name: "Optimism",
|
|
42
60
|
isEVM: true,
|
|
43
61
|
wrapped: "0x4200000000000000000000000000000000000006",
|
|
@@ -46,7 +64,7 @@ var SupportedChains = [
|
|
|
46
64
|
tokenAddress: NATIVE_TOKEN.ETH
|
|
47
65
|
},
|
|
48
66
|
{
|
|
49
|
-
id:
|
|
67
|
+
id: BaseChainID.Base,
|
|
50
68
|
name: "Base",
|
|
51
69
|
isEVM: true,
|
|
52
70
|
wrapped: "0x4200000000000000000000000000000000000006",
|
|
@@ -55,7 +73,7 @@ var SupportedChains = [
|
|
|
55
73
|
tokenAddress: NATIVE_TOKEN.ETH
|
|
56
74
|
},
|
|
57
75
|
{
|
|
58
|
-
id:
|
|
76
|
+
id: BaseChainID.Hyperliquid,
|
|
59
77
|
name: "Hyperliquid",
|
|
60
78
|
isEVM: true,
|
|
61
79
|
wrapped: "0x5555555555555555555555555555555555555555",
|
|
@@ -64,7 +82,7 @@ var SupportedChains = [
|
|
|
64
82
|
tokenAddress: NATIVE_TOKEN.ETH
|
|
65
83
|
},
|
|
66
84
|
{
|
|
67
|
-
id:
|
|
85
|
+
id: BaseChainID.BSC,
|
|
68
86
|
name: "BSC",
|
|
69
87
|
isEVM: true,
|
|
70
88
|
wrapped: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
|
@@ -80,8 +98,21 @@ var SupportedChains = [
|
|
|
80
98
|
symbol: "SOL",
|
|
81
99
|
decimals: 9,
|
|
82
100
|
tokenAddress: NATIVE_TOKEN.SOL
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: BaseChainID.MONAD,
|
|
104
|
+
name: "Monad",
|
|
105
|
+
isEVM: true,
|
|
106
|
+
wrapped: "0x3bd359C1119dA7Da1D913D1C4D2B7c461115433A",
|
|
107
|
+
symbol: "MON",
|
|
108
|
+
decimals: 18,
|
|
109
|
+
tokenAddress: NATIVE_TOKEN.ETH
|
|
83
110
|
}
|
|
84
111
|
];
|
|
112
|
+
var SupportedChains = SupportedChainsInternal.filter(
|
|
113
|
+
(c) => CURRENT_SUPPORTED.includes(c.id)
|
|
114
|
+
);
|
|
115
|
+
var isEvmChain = isEvmChainIntent;
|
|
85
116
|
|
|
86
117
|
// src/utils/viem.ts
|
|
87
118
|
function isViemWalletClient(wallet) {
|
|
@@ -109,9 +140,9 @@ function serializeBigIntsToStrings(obj) {
|
|
|
109
140
|
return obj;
|
|
110
141
|
}
|
|
111
142
|
|
|
112
|
-
// src/core/
|
|
143
|
+
// src/core/execute/normalizeNative.ts
|
|
113
144
|
function normalizeNative(chainId, address) {
|
|
114
|
-
if (
|
|
145
|
+
if (isEvmChain2(chainId) && isNativeAddress(address)) {
|
|
115
146
|
const chain = SupportedChains.find((c) => c.id === chainId);
|
|
116
147
|
if (!chain?.wrapped)
|
|
117
148
|
throw new Error(`Wrapped token not found for chainId ${chainId}`);
|
|
@@ -122,39 +153,72 @@ function normalizeNative(chainId, address) {
|
|
|
122
153
|
|
|
123
154
|
// src/core/getQuote.ts
|
|
124
155
|
async function getQuote(params) {
|
|
156
|
+
const amount = BigInt(params.amount);
|
|
125
157
|
if (!params.tokenIn?.address || !params.tokenOut?.address) {
|
|
126
158
|
throw new Error("Both tokenIn and tokenOut must include an address.");
|
|
127
159
|
}
|
|
128
160
|
if (!params.sourceChainId || !params.destChainId) {
|
|
129
161
|
throw new Error("Both sourceChainId and destChainId are required.");
|
|
130
162
|
}
|
|
131
|
-
if (
|
|
163
|
+
if (amount <= 0n) {
|
|
132
164
|
throw new Error("Amount must be greater than 0.");
|
|
133
165
|
}
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
destChainId: params.destChainId,
|
|
138
|
-
tokenIn: normalizedTokenIn,
|
|
139
|
-
tokenOut: params.tokenOut.address,
|
|
140
|
-
amount: params.amount
|
|
141
|
-
});
|
|
142
|
-
const inputSlippage = params.slippage ?? 5;
|
|
143
|
-
const slippageDecimal = inputSlippage / 100;
|
|
144
|
-
const slippage = Math.min(Math.max(slippageDecimal, 0), 0.5);
|
|
166
|
+
const slippagePercent = Math.min(Math.max(params.slippage ?? 0.5, 0), 50);
|
|
167
|
+
const slippageBps = BigInt(Math.round(slippagePercent * 100));
|
|
168
|
+
const applyUserSlippage = (value) => value * (10000n - slippageBps) / 10000n;
|
|
145
169
|
let warning;
|
|
146
|
-
if (
|
|
147
|
-
warning = `\u26A0\uFE0F High slippage tolerance (${
|
|
170
|
+
if (slippagePercent > 10) {
|
|
171
|
+
warning = `\u26A0\uFE0F High slippage tolerance (${slippagePercent.toFixed(2)}%) \u2014 price may vary significantly.`;
|
|
148
172
|
}
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
173
|
+
const normalizedTokenIn = normalizeNative(params.sourceChainId, params.tokenIn.address);
|
|
174
|
+
const isSingleChain = params.sourceChainId === params.destChainId;
|
|
175
|
+
let data;
|
|
176
|
+
let netFloor;
|
|
177
|
+
let minStablecoinsAmount;
|
|
178
|
+
if (isSingleChain) {
|
|
179
|
+
const { quote, netAmountOut } = await QuoteProvider.getSingleChainNetQuote({
|
|
180
|
+
chainId: params.sourceChainId,
|
|
181
|
+
amount,
|
|
182
|
+
tokenIn: normalizedTokenIn,
|
|
183
|
+
tokenOut: params.tokenOut.address
|
|
184
|
+
});
|
|
185
|
+
netFloor = netAmountOut;
|
|
186
|
+
minStablecoinsAmount = 0n;
|
|
187
|
+
data = {
|
|
188
|
+
amountInUsd: quote.amountInUsd,
|
|
189
|
+
estimatedAmountInAsMinStablecoinAmount: 0n,
|
|
190
|
+
estimatedAmountOut: quote.amountOut,
|
|
191
|
+
estimatedAmountOutUsd: quote.amountOutUsd,
|
|
192
|
+
// filled in below once the user slippage buffer is known
|
|
193
|
+
estimatedAmountOutReduced: netAmountOut,
|
|
194
|
+
estimatedAmountOutUsdReduced: quote.amountOutUsd,
|
|
195
|
+
tokenIn: { address: params.tokenIn.address, chainId: params.sourceChainId },
|
|
196
|
+
tokenOut: { address: params.tokenOut.address, chainId: params.destChainId }
|
|
197
|
+
};
|
|
198
|
+
} else {
|
|
199
|
+
const crossChainQuote = await QuoteProvider.getCrossChainNetQuote({
|
|
200
|
+
sourceChainId: params.sourceChainId,
|
|
201
|
+
destChainId: params.destChainId,
|
|
202
|
+
tokenIn: normalizedTokenIn,
|
|
203
|
+
tokenOut: params.tokenOut.address,
|
|
204
|
+
amount
|
|
205
|
+
});
|
|
206
|
+
data = crossChainQuote;
|
|
207
|
+
netFloor = BigInt(crossChainQuote.estimatedAmountOutReduced);
|
|
208
|
+
minStablecoinsAmount = applyUserSlippage(
|
|
209
|
+
BigInt(crossChainQuote.estimatedAmountInAsMinStablecoinAmount)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const amountOut = applyUserSlippage(netFloor);
|
|
213
|
+
const grossAmountOut = BigInt(data.estimatedAmountOut);
|
|
214
|
+
const pricePerTokenOutInUsd = grossAmountOut > 0n ? data.estimatedAmountOutUsd / Number(grossAmountOut) : 0;
|
|
215
|
+
const amountOutUsd = Number(amountOut) * pricePerTokenOutInUsd;
|
|
216
|
+
const pricePerInputToken = grossAmountOut * 10n ** BigInt(params.tokenIn.decimals ?? 18) / amount;
|
|
153
217
|
return {
|
|
154
|
-
amountOut
|
|
155
|
-
amountOutUsd
|
|
218
|
+
amountOut,
|
|
219
|
+
amountOutUsd,
|
|
156
220
|
amountInUsd: data.amountInUsd,
|
|
157
|
-
minStablecoinsAmount
|
|
221
|
+
minStablecoinsAmount,
|
|
158
222
|
tokenIn: {
|
|
159
223
|
address: params.tokenIn.address,
|
|
160
224
|
decimals: params.tokenIn.decimals ?? 18,
|
|
@@ -165,12 +229,13 @@ async function getQuote(params) {
|
|
|
165
229
|
decimals: params.tokenOut.decimals ?? 18,
|
|
166
230
|
chainId: params.destChainId
|
|
167
231
|
},
|
|
168
|
-
amountIn:
|
|
232
|
+
amountIn: amount,
|
|
169
233
|
pricePerInputToken,
|
|
170
|
-
slippage,
|
|
234
|
+
slippage: slippagePercent,
|
|
171
235
|
internal: {
|
|
172
236
|
...data,
|
|
173
|
-
estimatedAmountOutReduced:
|
|
237
|
+
estimatedAmountOutReduced: amountOut,
|
|
238
|
+
estimatedAmountOutUsdReduced: amountOutUsd
|
|
174
239
|
},
|
|
175
240
|
warning
|
|
176
241
|
};
|
|
@@ -188,7 +253,7 @@ function buildQuoteParams({
|
|
|
188
253
|
tokenOut,
|
|
189
254
|
sourceChainId,
|
|
190
255
|
destChainId,
|
|
191
|
-
amount: parseUnits(amount.toString(), tokenIn.decimals ?? 18),
|
|
256
|
+
amount: parseUnits(amount.toString(), tokenIn.decimals ?? 18).toString(),
|
|
192
257
|
slippage
|
|
193
258
|
};
|
|
194
259
|
}
|
|
@@ -235,107 +300,73 @@ async function getBalances(params, options) {
|
|
|
235
300
|
const evmItems = data.evm?.items ?? [];
|
|
236
301
|
const svmItems = data.svm?.items ?? [];
|
|
237
302
|
const combined = [...evmItems, ...svmItems];
|
|
303
|
+
const filtered = combined.filter(
|
|
304
|
+
(b) => CURRENT_SUPPORTED.includes(b.chainId)
|
|
305
|
+
);
|
|
238
306
|
return {
|
|
239
|
-
results:
|
|
307
|
+
results: filtered,
|
|
240
308
|
nextCursorEvm: data.evm?.cursor ?? null,
|
|
241
309
|
nextCursorSvm: data.svm?.cursor ?? null
|
|
242
310
|
};
|
|
243
311
|
}
|
|
244
312
|
|
|
245
|
-
// src/core/
|
|
246
|
-
import {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const res = await connection.getSignatureStatus(txHash, { searchTransactionHistory: true });
|
|
264
|
-
if (res?.value?.confirmationStatus === "confirmed" || res?.value?.confirmationStatus === "finalized") {
|
|
265
|
-
return txHash;
|
|
266
|
-
}
|
|
267
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
268
|
-
}
|
|
269
|
-
throw new Error(`Transaction not confirmed after ${maxRetries * (delayMs / 1e3)}s`);
|
|
270
|
-
};
|
|
271
|
-
const signTypedData = async () => {
|
|
272
|
-
throw new Error("signTypedData not implemented for Solana");
|
|
273
|
-
};
|
|
274
|
-
const switchChain = async (newChainId) => {
|
|
275
|
-
_chainId = newChainId;
|
|
276
|
-
};
|
|
313
|
+
// src/core/token-list.ts
|
|
314
|
+
import { TOKEN_SEARCH_API_BASE_URL as TOKEN_SEARCH_API_BASE_URL2 } from "@shogun-sdk/intents-sdk";
|
|
315
|
+
async function getTokenList(params) {
|
|
316
|
+
const url = new URL(`${TOKEN_SEARCH_API_BASE_URL2}/tokens/search`);
|
|
317
|
+
if (params.q) url.searchParams.append("q", params.q);
|
|
318
|
+
if (params.networkId) url.searchParams.append("networkId", String(params.networkId));
|
|
319
|
+
if (params.page) url.searchParams.append("page", String(params.page));
|
|
320
|
+
if (params.limit) url.searchParams.append("limit", String(params.limit));
|
|
321
|
+
const res = await fetch(url.toString(), {
|
|
322
|
+
signal: params.signal
|
|
323
|
+
});
|
|
324
|
+
if (!res.ok) {
|
|
325
|
+
throw new Error(`Failed to fetch tokens: ${res.status} ${res.statusText}`);
|
|
326
|
+
}
|
|
327
|
+
const data = await res.json();
|
|
328
|
+
const filteredResults = data.results.filter(
|
|
329
|
+
(token) => CURRENT_SUPPORTED.includes(token.chainId)
|
|
330
|
+
);
|
|
277
331
|
return {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
sendTransaction,
|
|
282
|
-
switchChain,
|
|
283
|
-
signTypedData,
|
|
284
|
-
rpcUrl
|
|
332
|
+
...data,
|
|
333
|
+
results: filteredResults,
|
|
334
|
+
count: filteredResults.length
|
|
285
335
|
};
|
|
286
|
-
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/core/token.ts
|
|
339
|
+
import { TOKEN_SEARCH_API_BASE_URL as TOKEN_SEARCH_API_BASE_URL3 } from "@shogun-sdk/intents-sdk";
|
|
340
|
+
async function getTokensData(addresses) {
|
|
341
|
+
if (!addresses?.length) return [];
|
|
342
|
+
const response = await fetch(`${TOKEN_SEARCH_API_BASE_URL3}/tokens/tokens`, {
|
|
343
|
+
method: "POST",
|
|
344
|
+
headers: {
|
|
345
|
+
"Content-Type": "application/json",
|
|
346
|
+
accept: "*/*"
|
|
347
|
+
},
|
|
348
|
+
body: JSON.stringify({ addresses })
|
|
349
|
+
});
|
|
350
|
+
if (!response.ok) {
|
|
351
|
+
throw new Error(`Failed to fetch token data: ${response.statusText}`);
|
|
352
|
+
}
|
|
353
|
+
const data = await response.json();
|
|
354
|
+
const filtered = data.filter((t) => CURRENT_SUPPORTED.includes(Number(t.chainId)));
|
|
355
|
+
return filtered;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/core/execute/execute.ts
|
|
359
|
+
import { ChainID as ChainID2, isEvmChain as isEvmChain3 } from "@shogun-sdk/intents-sdk";
|
|
360
|
+
import "viem";
|
|
287
361
|
|
|
288
362
|
// src/wallet-adapter/evm-wallet-adapter/adapter.ts
|
|
289
|
-
import "ethers/lib/ethers.js";
|
|
290
|
-
import { hexValue } from "ethers/lib/utils.js";
|
|
291
363
|
import {
|
|
292
|
-
custom
|
|
364
|
+
custom,
|
|
365
|
+
publicActions
|
|
293
366
|
} from "viem";
|
|
294
367
|
function isEVMTransaction(tx) {
|
|
295
368
|
return typeof tx.from === "string";
|
|
296
369
|
}
|
|
297
|
-
var adaptEthersSigner = (signer, transport) => {
|
|
298
|
-
const signTypedData = async (signData) => {
|
|
299
|
-
const typedSigner = signer;
|
|
300
|
-
return await typedSigner._signTypedData(
|
|
301
|
-
signData.domain,
|
|
302
|
-
signData.types,
|
|
303
|
-
signData.value
|
|
304
|
-
);
|
|
305
|
-
};
|
|
306
|
-
const sendTransaction = async (transaction) => {
|
|
307
|
-
if (!isEVMTransaction(transaction)) {
|
|
308
|
-
throw new Error("Expected EVMTransaction but got SolanaTransaction");
|
|
309
|
-
}
|
|
310
|
-
const tx = await signer.sendTransaction({
|
|
311
|
-
from: transaction.from,
|
|
312
|
-
to: transaction.to,
|
|
313
|
-
data: transaction.data,
|
|
314
|
-
value: transaction.value
|
|
315
|
-
});
|
|
316
|
-
return tx.hash;
|
|
317
|
-
};
|
|
318
|
-
const switchChain = async (chainId) => {
|
|
319
|
-
try {
|
|
320
|
-
await window.ethereum.request({
|
|
321
|
-
method: "wallet_switchEthereumChain",
|
|
322
|
-
params: [{ chainId: hexValue(chainId) }]
|
|
323
|
-
});
|
|
324
|
-
} catch (error) {
|
|
325
|
-
console.error("Failed to switch chain:", error);
|
|
326
|
-
throw error;
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
return {
|
|
330
|
-
vmType: "EVM" /* EVM */,
|
|
331
|
-
transport,
|
|
332
|
-
getChainId: async () => signer.getChainId(),
|
|
333
|
-
address: async () => signer.getAddress(),
|
|
334
|
-
sendTransaction,
|
|
335
|
-
signTypedData,
|
|
336
|
-
switchChain
|
|
337
|
-
};
|
|
338
|
-
};
|
|
339
370
|
var adaptViemWallet = (wallet) => {
|
|
340
371
|
const signTypedData = async (signData) => {
|
|
341
372
|
return await wallet.signTypedData({
|
|
@@ -350,15 +381,26 @@ var adaptViemWallet = (wallet) => {
|
|
|
350
381
|
if (!isEVMTransaction(transaction)) {
|
|
351
382
|
throw new Error("Expected EVMTransaction but got SolanaTransaction");
|
|
352
383
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
384
|
+
if (wallet.transport.type === "http") {
|
|
385
|
+
const request = await wallet.prepareTransactionRequest({
|
|
386
|
+
to: transaction.to,
|
|
387
|
+
data: transaction.data,
|
|
388
|
+
value: transaction.value,
|
|
389
|
+
chain: wallet.chain
|
|
390
|
+
});
|
|
391
|
+
const serializedTransaction = await wallet.signTransaction(request);
|
|
392
|
+
const tx = await wallet.sendRawTransaction({ serializedTransaction });
|
|
393
|
+
return tx;
|
|
394
|
+
} else {
|
|
395
|
+
const hash = await wallet.sendTransaction({
|
|
396
|
+
to: transaction.to,
|
|
397
|
+
data: transaction.data,
|
|
398
|
+
value: transaction.value,
|
|
399
|
+
chain: wallet.chain,
|
|
400
|
+
account: wallet.account
|
|
401
|
+
});
|
|
402
|
+
return hash;
|
|
403
|
+
}
|
|
362
404
|
};
|
|
363
405
|
const switchChain = async (chainId) => {
|
|
364
406
|
try {
|
|
@@ -381,6 +423,20 @@ var adaptViemWallet = (wallet) => {
|
|
|
381
423
|
if (!addr) throw new Error("No address found");
|
|
382
424
|
return addr;
|
|
383
425
|
};
|
|
426
|
+
const readContract = async ({
|
|
427
|
+
address: address2,
|
|
428
|
+
abi,
|
|
429
|
+
functionName,
|
|
430
|
+
args = []
|
|
431
|
+
}) => {
|
|
432
|
+
const publicClient = wallet.extend(publicActions);
|
|
433
|
+
return await publicClient.readContract({
|
|
434
|
+
address: address2,
|
|
435
|
+
abi,
|
|
436
|
+
functionName,
|
|
437
|
+
args
|
|
438
|
+
});
|
|
439
|
+
};
|
|
384
440
|
return {
|
|
385
441
|
vmType: "EVM" /* EVM */,
|
|
386
442
|
transport: custom(wallet.transport),
|
|
@@ -388,39 +444,65 @@ var adaptViemWallet = (wallet) => {
|
|
|
388
444
|
address,
|
|
389
445
|
sendTransaction,
|
|
390
446
|
signTypedData,
|
|
391
|
-
switchChain
|
|
447
|
+
switchChain,
|
|
448
|
+
readContract
|
|
392
449
|
};
|
|
393
450
|
};
|
|
394
451
|
|
|
395
|
-
// src/core/
|
|
452
|
+
// src/core/execute/handleEvmExecution.ts
|
|
396
453
|
import {
|
|
397
454
|
getEVMSingleChainOrderTypedData,
|
|
398
|
-
getEVMCrossChainOrderTypedData
|
|
399
|
-
PERMIT2_ADDRESS
|
|
455
|
+
getEVMCrossChainOrderTypedData
|
|
400
456
|
} from "@shogun-sdk/intents-sdk";
|
|
401
|
-
import { encodeFunctionData
|
|
457
|
+
import { encodeFunctionData as encodeFunctionData2 } from "viem";
|
|
402
458
|
|
|
403
|
-
// src/core/
|
|
459
|
+
// src/core/execute/stageMessages.ts
|
|
404
460
|
var DEFAULT_STAGE_MESSAGES = {
|
|
405
461
|
processing: "Preparing transaction for execution",
|
|
406
462
|
approving: "Approving token allowance",
|
|
407
463
|
approved: "Token approved successfully",
|
|
408
|
-
signing: "Signing
|
|
409
|
-
submitting: "Submitting
|
|
410
|
-
|
|
411
|
-
|
|
464
|
+
signing: "Signing transaction for submission",
|
|
465
|
+
submitting: "Submitting transaction",
|
|
466
|
+
initiated: "Transaction initiated.",
|
|
467
|
+
success: "Transaction Executed successfully",
|
|
468
|
+
success_limit: "Limit order has been submitted successfully.",
|
|
469
|
+
shogun_processing: "Shogun is processing your transaction",
|
|
470
|
+
error: "Transaction failed during submission"
|
|
412
471
|
};
|
|
413
472
|
|
|
414
|
-
// src/core/
|
|
473
|
+
// src/core/execute/buildOrder.ts
|
|
415
474
|
import { CrossChainOrder, SingleChainOrder } from "@shogun-sdk/intents-sdk";
|
|
475
|
+
import { formatUnits, parseUnits as parseUnits2 } from "viem";
|
|
476
|
+
|
|
477
|
+
// src/utils/order.ts
|
|
478
|
+
var OrderExecutionType = /* @__PURE__ */ ((OrderExecutionType2) => {
|
|
479
|
+
OrderExecutionType2["LIMIT"] = "limit";
|
|
480
|
+
OrderExecutionType2["MARKET"] = "market";
|
|
481
|
+
return OrderExecutionType2;
|
|
482
|
+
})(OrderExecutionType || {});
|
|
483
|
+
|
|
484
|
+
// src/core/execute/buildOrder.ts
|
|
416
485
|
async function buildOrder({
|
|
417
486
|
quote,
|
|
418
487
|
accountAddress,
|
|
419
488
|
destination,
|
|
420
489
|
deadline,
|
|
421
|
-
isSingleChain
|
|
490
|
+
isSingleChain,
|
|
491
|
+
orderType,
|
|
492
|
+
options
|
|
422
493
|
}) {
|
|
423
494
|
const { tokenIn, tokenOut } = quote;
|
|
495
|
+
let amountOutMin = BigInt(quote.internal.estimatedAmountOutReduced);
|
|
496
|
+
if (orderType === "limit" /* LIMIT */ && options && "executionPrice" in options) {
|
|
497
|
+
const executionPrice = Number(options.executionPrice);
|
|
498
|
+
if (Number.isFinite(executionPrice) && executionPrice > 0) {
|
|
499
|
+
const decimalsIn = tokenIn.decimals ?? 18;
|
|
500
|
+
const decimalsOut = tokenOut.decimals ?? 18;
|
|
501
|
+
const formattedAmountIn = Number(formatUnits(BigInt(quote.amountIn.toString()), decimalsIn));
|
|
502
|
+
const rawAmountOut = formattedAmountIn * executionPrice;
|
|
503
|
+
amountOutMin = parseUnits2(rawAmountOut.toString(), decimalsOut);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
424
506
|
if (isSingleChain) {
|
|
425
507
|
return await SingleChainOrder.create({
|
|
426
508
|
user: accountAddress,
|
|
@@ -428,7 +510,7 @@ async function buildOrder({
|
|
|
428
510
|
tokenIn: tokenIn.address,
|
|
429
511
|
tokenOut: tokenOut.address,
|
|
430
512
|
amountIn: quote.amountIn,
|
|
431
|
-
amountOutMin
|
|
513
|
+
amountOutMin,
|
|
432
514
|
deadline,
|
|
433
515
|
destinationAddress: destination
|
|
434
516
|
});
|
|
@@ -442,12 +524,166 @@ async function buildOrder({
|
|
|
442
524
|
destinationTokenAddress: tokenOut.address,
|
|
443
525
|
destinationAddress: destination,
|
|
444
526
|
deadline,
|
|
445
|
-
destinationTokenMinAmount:
|
|
527
|
+
destinationTokenMinAmount: amountOutMin,
|
|
446
528
|
minStablecoinAmount: quote.minStablecoinsAmount
|
|
447
529
|
});
|
|
448
530
|
}
|
|
449
531
|
|
|
450
|
-
// src/
|
|
532
|
+
// src/utils/pollOrderStatus.ts
|
|
533
|
+
import { AUCTIONEER_URL } from "@shogun-sdk/intents-sdk";
|
|
534
|
+
async function pollOrderStatus(address, orderId, options = {}) {
|
|
535
|
+
const { intervalMs = 2e3, timeoutMs = 3e5, authToken } = options;
|
|
536
|
+
const startTime = Date.now();
|
|
537
|
+
const isDebug = process.env.NODE_ENV !== "production";
|
|
538
|
+
const isEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
539
|
+
const isSuiAddress = /^0x[a-fA-F0-9]{64}$/.test(address);
|
|
540
|
+
const isSolanaAddress = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
|
|
541
|
+
let queryParam;
|
|
542
|
+
if (isEvmAddress) queryParam = `evmWallets=${address}`;
|
|
543
|
+
else if (isSuiAddress) queryParam = `suiWallets=${address}`;
|
|
544
|
+
else if (isSolanaAddress) queryParam = `solanaWallets=${address}`;
|
|
545
|
+
else throw new Error(`Unrecognized wallet address format: ${address}`);
|
|
546
|
+
const queryUrl = `${AUCTIONEER_URL}/user_intent?${queryParam}`;
|
|
547
|
+
return new Promise((resolve, reject) => {
|
|
548
|
+
const pollInterval = setInterval(async () => {
|
|
549
|
+
try {
|
|
550
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
551
|
+
clearInterval(pollInterval);
|
|
552
|
+
return resolve("Timeout");
|
|
553
|
+
}
|
|
554
|
+
const res = await fetch(queryUrl, {
|
|
555
|
+
method: "GET",
|
|
556
|
+
headers: {
|
|
557
|
+
"Content-Type": "application/json",
|
|
558
|
+
// The /user_intent status endpoint requires the SIWE JWT; without it
|
|
559
|
+
// the request 401s after the order has already been submitted.
|
|
560
|
+
...authToken ? { Authorization: `Bearer ${authToken}` } : {}
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
if (!res.ok) {
|
|
564
|
+
clearInterval(pollInterval);
|
|
565
|
+
return reject(
|
|
566
|
+
new Error(`Failed to fetch orders: ${res.status} ${res.statusText}`)
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
const json = await res.json();
|
|
570
|
+
const data = json?.data ?? {};
|
|
571
|
+
const allOrders = [
|
|
572
|
+
...data.crossChainDcaOrders ?? [],
|
|
573
|
+
...data.crossChainLimitOrders ?? [],
|
|
574
|
+
...data.singleChainDcaOrders ?? [],
|
|
575
|
+
...data.singleChainLimitOrders ?? []
|
|
576
|
+
];
|
|
577
|
+
const targetOrder = allOrders.find((o) => o.orderId === orderId);
|
|
578
|
+
if (!targetOrder) {
|
|
579
|
+
if (isDebug)
|
|
580
|
+
console.debug(`[pollOrderStatus] [${orderId}] Not found yet`);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const { orderStatus } = targetOrder;
|
|
584
|
+
if (isDebug) {
|
|
585
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
586
|
+
console.debug(`targetOrder`, targetOrder);
|
|
587
|
+
console.debug(
|
|
588
|
+
`[pollOrderStatus] [${orderId}] status=${orderStatus} (elapsed ${elapsed}s)`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
if (["Fulfilled", "Cancelled", "Outdated"].includes(orderStatus)) {
|
|
592
|
+
clearInterval(pollInterval);
|
|
593
|
+
return resolve(orderStatus);
|
|
594
|
+
}
|
|
595
|
+
} catch (error) {
|
|
596
|
+
clearInterval(pollInterval);
|
|
597
|
+
return reject(error);
|
|
598
|
+
}
|
|
599
|
+
}, intervalMs);
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/core/execute/handleOrderPollingResult.ts
|
|
604
|
+
async function handleOrderPollingResult({
|
|
605
|
+
status,
|
|
606
|
+
orderId,
|
|
607
|
+
chainId,
|
|
608
|
+
update,
|
|
609
|
+
messageFor
|
|
610
|
+
}) {
|
|
611
|
+
switch (status) {
|
|
612
|
+
case "Fulfilled":
|
|
613
|
+
update("success", messageFor("success"));
|
|
614
|
+
return {
|
|
615
|
+
status: true,
|
|
616
|
+
orderId,
|
|
617
|
+
chainId,
|
|
618
|
+
finalStatus: status,
|
|
619
|
+
stage: "success"
|
|
620
|
+
};
|
|
621
|
+
case "Cancelled":
|
|
622
|
+
update("error", "Order was cancelled before fulfillment");
|
|
623
|
+
break;
|
|
624
|
+
case "Timeout":
|
|
625
|
+
update("error", "Order polling timed out");
|
|
626
|
+
break;
|
|
627
|
+
case "NotFound":
|
|
628
|
+
default:
|
|
629
|
+
update("error", "Order not found");
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
status: false,
|
|
634
|
+
orderId,
|
|
635
|
+
chainId,
|
|
636
|
+
finalStatus: status,
|
|
637
|
+
stage: "error"
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/core/execute/ensurePermit2Allowance.ts
|
|
642
|
+
import { encodeFunctionData, erc20Abi, maxUint256 } from "viem";
|
|
643
|
+
import { PERMIT2_ADDRESS } from "@shogun-sdk/intents-sdk";
|
|
644
|
+
async function ensurePermit2Allowance({
|
|
645
|
+
chainId,
|
|
646
|
+
tokenIn,
|
|
647
|
+
wallet,
|
|
648
|
+
accountAddress,
|
|
649
|
+
requiredAmount,
|
|
650
|
+
increaseByDelta = false
|
|
651
|
+
}) {
|
|
652
|
+
const spender = PERMIT2_ADDRESS[chainId];
|
|
653
|
+
let currentAllowance = 0n;
|
|
654
|
+
try {
|
|
655
|
+
if (!wallet.readContract) {
|
|
656
|
+
throw new Error("Wallet does not implement readContract()");
|
|
657
|
+
}
|
|
658
|
+
currentAllowance = await wallet.readContract({
|
|
659
|
+
address: tokenIn,
|
|
660
|
+
abi: erc20Abi,
|
|
661
|
+
functionName: "allowance",
|
|
662
|
+
args: [accountAddress, spender]
|
|
663
|
+
});
|
|
664
|
+
} catch (error) {
|
|
665
|
+
console.warn(`[Permit2] Failed to read allowance for ${tokenIn}`, error);
|
|
666
|
+
}
|
|
667
|
+
const approvalAmount = increaseByDelta ? currentAllowance + requiredAmount : maxUint256;
|
|
668
|
+
console.debug(
|
|
669
|
+
`[Permit2] Approving ${approvalAmount} for ${tokenIn} (current: ${currentAllowance}, required: ${requiredAmount})`
|
|
670
|
+
);
|
|
671
|
+
await wallet.sendTransaction({
|
|
672
|
+
to: tokenIn,
|
|
673
|
+
from: accountAddress,
|
|
674
|
+
data: encodeFunctionData({
|
|
675
|
+
abi: erc20Abi,
|
|
676
|
+
functionName: "approve",
|
|
677
|
+
args: [spender, approvalAmount]
|
|
678
|
+
}),
|
|
679
|
+
value: 0n
|
|
680
|
+
});
|
|
681
|
+
console.info(
|
|
682
|
+
`[Permit2] Approval transaction sent for ${tokenIn} on chain ${chainId}`
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/core/execute/handleEvmExecution.ts
|
|
451
687
|
async function handleEvmExecution({
|
|
452
688
|
recipientAddress,
|
|
453
689
|
quote,
|
|
@@ -455,18 +691,22 @@ async function handleEvmExecution({
|
|
|
455
691
|
accountAddress,
|
|
456
692
|
wallet,
|
|
457
693
|
isSingleChain,
|
|
458
|
-
|
|
459
|
-
|
|
694
|
+
update,
|
|
695
|
+
orderType,
|
|
696
|
+
options
|
|
460
697
|
}) {
|
|
461
|
-
const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
|
|
698
|
+
const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage] ?? "";
|
|
699
|
+
const deadline = options?.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
|
|
462
700
|
await wallet.switchChain(chainId);
|
|
463
701
|
const tokenIn = normalizeNative(chainId, quote.tokenIn.address);
|
|
702
|
+
quote.tokenOut.address = normalizeEvmTokenAddress(quote.tokenOut.address);
|
|
464
703
|
const shouldWrapNative = isNativeAddress(quote.tokenIn.address);
|
|
465
704
|
update("processing", shouldWrapNative ? `${messageFor("processing")} (wrapping native token)` : messageFor("processing"));
|
|
466
705
|
if (shouldWrapNative) {
|
|
706
|
+
quote.tokenIn.address === tokenIn;
|
|
467
707
|
await wallet.sendTransaction({
|
|
468
708
|
to: tokenIn,
|
|
469
|
-
data:
|
|
709
|
+
data: encodeFunctionData2({
|
|
470
710
|
abi: [{ type: "function", name: "deposit", stateMutability: "payable", inputs: [], outputs: [] }],
|
|
471
711
|
functionName: "deposit",
|
|
472
712
|
args: []
|
|
@@ -475,48 +715,74 @@ async function handleEvmExecution({
|
|
|
475
715
|
from: accountAddress
|
|
476
716
|
});
|
|
477
717
|
}
|
|
478
|
-
update("
|
|
479
|
-
await
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}),
|
|
486
|
-
value: 0n,
|
|
487
|
-
from: accountAddress
|
|
718
|
+
update("processing", messageFor("approving"));
|
|
719
|
+
await ensurePermit2Allowance({
|
|
720
|
+
chainId,
|
|
721
|
+
tokenIn,
|
|
722
|
+
wallet,
|
|
723
|
+
accountAddress,
|
|
724
|
+
requiredAmount: BigInt(quote.amountIn)
|
|
488
725
|
});
|
|
489
|
-
update("
|
|
726
|
+
update("processing", messageFor("approved"));
|
|
490
727
|
const destination = recipientAddress ?? accountAddress;
|
|
491
728
|
const order = await buildOrder({
|
|
492
729
|
quote,
|
|
493
730
|
accountAddress,
|
|
494
731
|
destination,
|
|
495
732
|
deadline,
|
|
496
|
-
isSingleChain
|
|
733
|
+
isSingleChain,
|
|
734
|
+
orderType,
|
|
735
|
+
options
|
|
497
736
|
});
|
|
498
|
-
|
|
737
|
+
console.debug(`order`, order);
|
|
738
|
+
update("processing", messageFor("signing"));
|
|
499
739
|
const { orderTypedData, nonce } = isSingleChain ? await getEVMSingleChainOrderTypedData(order) : await getEVMCrossChainOrderTypedData(order);
|
|
740
|
+
const typedData = serializeBigIntsToStrings(orderTypedData);
|
|
500
741
|
if (!wallet.signTypedData) {
|
|
501
742
|
throw new Error("Wallet does not support EIP-712 signing");
|
|
502
743
|
}
|
|
503
|
-
const signature = await wallet.signTypedData(
|
|
504
|
-
|
|
744
|
+
const signature = await wallet.signTypedData({
|
|
745
|
+
domain: typedData.domain,
|
|
746
|
+
types: typedData.types,
|
|
747
|
+
primaryType: typedData.primaryType,
|
|
748
|
+
value: typedData.message,
|
|
749
|
+
message: typedData.message
|
|
750
|
+
});
|
|
751
|
+
update("processing", messageFor("submitting"));
|
|
505
752
|
const res = await order.sendToAuctioneer({ signature, nonce: nonce.toString() });
|
|
506
753
|
if (!res.success) {
|
|
507
754
|
throw new Error("Auctioneer submission failed");
|
|
508
755
|
}
|
|
509
|
-
update("
|
|
510
|
-
|
|
756
|
+
update("initiated", messageFor("initiated"));
|
|
757
|
+
const { intentId: orderId } = res.data;
|
|
758
|
+
update("initiated", messageFor("shogun_processing"));
|
|
759
|
+
if (orderType === "limit" /* LIMIT */) {
|
|
760
|
+
update("success", messageFor("success_limit"));
|
|
761
|
+
return {
|
|
762
|
+
status: true,
|
|
763
|
+
orderId,
|
|
764
|
+
chainId,
|
|
765
|
+
finalStatus: "OrderPlaced",
|
|
766
|
+
stage: "success"
|
|
767
|
+
};
|
|
768
|
+
} else {
|
|
769
|
+
const status = await pollOrderStatus(accountAddress, orderId, { authToken: options?.authToken });
|
|
770
|
+
return await handleOrderPollingResult({
|
|
771
|
+
status,
|
|
772
|
+
orderId,
|
|
773
|
+
chainId,
|
|
774
|
+
update,
|
|
775
|
+
messageFor
|
|
776
|
+
});
|
|
777
|
+
}
|
|
511
778
|
}
|
|
512
779
|
|
|
513
|
-
// src/core/
|
|
780
|
+
// src/core/execute/handleSolanaExecution.ts
|
|
514
781
|
import {
|
|
515
|
-
ChainID as ChainID3,
|
|
516
782
|
getSolanaSingleChainOrderInstructions,
|
|
517
783
|
getSolanaCrossChainOrderInstructions
|
|
518
784
|
} from "@shogun-sdk/intents-sdk";
|
|
519
|
-
import { VersionedTransaction
|
|
785
|
+
import { VersionedTransaction } from "@solana/web3.js";
|
|
520
786
|
async function handleSolanaExecution({
|
|
521
787
|
recipientAddress,
|
|
522
788
|
quote,
|
|
@@ -524,12 +790,14 @@ async function handleSolanaExecution({
|
|
|
524
790
|
isSingleChain,
|
|
525
791
|
update,
|
|
526
792
|
accountAddress,
|
|
527
|
-
|
|
793
|
+
orderType,
|
|
794
|
+
options
|
|
528
795
|
}) {
|
|
529
796
|
if (!wallet.rpcUrl) {
|
|
530
797
|
throw new Error("Solana wallet is missing rpcUrl");
|
|
531
798
|
}
|
|
532
|
-
const
|
|
799
|
+
const deadline = options?.deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
|
|
800
|
+
const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage] ?? "";
|
|
533
801
|
update("processing", messageFor("processing"));
|
|
534
802
|
const destination = recipientAddress ?? accountAddress;
|
|
535
803
|
const order = await buildOrder({
|
|
@@ -537,18 +805,19 @@ async function handleSolanaExecution({
|
|
|
537
805
|
accountAddress,
|
|
538
806
|
destination,
|
|
539
807
|
deadline,
|
|
540
|
-
isSingleChain
|
|
808
|
+
isSingleChain,
|
|
809
|
+
orderType,
|
|
810
|
+
options
|
|
541
811
|
});
|
|
542
812
|
const txData = await getSolanaOrderInstructions({
|
|
543
813
|
order,
|
|
544
814
|
isSingleChain,
|
|
545
815
|
rpcUrl: wallet.rpcUrl
|
|
546
816
|
});
|
|
547
|
-
const transaction =
|
|
548
|
-
update("
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
update("submitting", messageFor("submitting"));
|
|
817
|
+
const transaction = VersionedTransaction.deserialize(Uint8Array.from(txData.txBytes));
|
|
818
|
+
update("processing", messageFor("signing"));
|
|
819
|
+
await wallet.sendTransaction(transaction);
|
|
820
|
+
update("processing", messageFor("submitting"));
|
|
552
821
|
const response = await submitToAuctioneer({
|
|
553
822
|
order,
|
|
554
823
|
isSingleChain,
|
|
@@ -557,13 +826,28 @@ async function handleSolanaExecution({
|
|
|
557
826
|
if (!response.success) {
|
|
558
827
|
throw new Error("Auctioneer submission failed");
|
|
559
828
|
}
|
|
560
|
-
update("
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
829
|
+
update("initiated", messageFor("initiated"));
|
|
830
|
+
const { intentId: orderId } = response.data;
|
|
831
|
+
update("initiated", messageFor("shogun_processing"));
|
|
832
|
+
if (orderType === "limit" /* LIMIT */) {
|
|
833
|
+
update("success", messageFor("success_limit"));
|
|
834
|
+
return {
|
|
835
|
+
status: true,
|
|
836
|
+
orderId,
|
|
837
|
+
chainId: SOLANA_CHAIN_ID,
|
|
838
|
+
finalStatus: "OrderPlaced",
|
|
839
|
+
stage: "success"
|
|
840
|
+
};
|
|
841
|
+
} else {
|
|
842
|
+
const status = await pollOrderStatus(accountAddress, orderId, { authToken: options?.authToken });
|
|
843
|
+
return await handleOrderPollingResult({
|
|
844
|
+
status,
|
|
845
|
+
orderId,
|
|
846
|
+
chainId: SOLANA_CHAIN_ID,
|
|
847
|
+
update,
|
|
848
|
+
messageFor
|
|
849
|
+
});
|
|
850
|
+
}
|
|
567
851
|
}
|
|
568
852
|
async function getSolanaOrderInstructions({
|
|
569
853
|
order,
|
|
@@ -596,52 +880,93 @@ async function submitToAuctioneer({
|
|
|
596
880
|
});
|
|
597
881
|
}
|
|
598
882
|
|
|
599
|
-
// src/core/
|
|
883
|
+
// src/core/execute/execute.ts
|
|
600
884
|
async function executeOrder({
|
|
601
885
|
quote,
|
|
602
886
|
accountAddress,
|
|
603
887
|
recipientAddress,
|
|
604
888
|
wallet,
|
|
605
889
|
onStatus,
|
|
890
|
+
orderType = "market" /* MARKET */,
|
|
606
891
|
options = {}
|
|
607
892
|
}) {
|
|
608
|
-
const
|
|
893
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
894
|
+
const log = (...args) => {
|
|
895
|
+
if (isDev) console.debug("[OneShot::executeOrder]", ...args);
|
|
896
|
+
};
|
|
609
897
|
const messageFor = (stage) => DEFAULT_STAGE_MESSAGES[stage];
|
|
610
|
-
const update = (stage, message) =>
|
|
898
|
+
const update = (stage, message) => {
|
|
899
|
+
log("Stage:", stage, "| Message:", message ?? messageFor(stage));
|
|
900
|
+
onStatus?.(stage, message ?? messageFor(stage));
|
|
901
|
+
};
|
|
611
902
|
try {
|
|
903
|
+
log("Starting execution:", {
|
|
904
|
+
accountAddress,
|
|
905
|
+
recipientAddress,
|
|
906
|
+
tokenIn: quote?.tokenIn,
|
|
907
|
+
tokenOut: quote?.tokenOut
|
|
908
|
+
});
|
|
612
909
|
const adapter = normalizeWallet(wallet);
|
|
613
910
|
if (!adapter) throw new Error("No wallet provided");
|
|
614
911
|
const { tokenIn, tokenOut } = quote;
|
|
912
|
+
const srcChain = Number(tokenIn.chainId);
|
|
913
|
+
const destChain = Number(tokenOut.chainId);
|
|
914
|
+
if (!CURRENT_SUPPORTED.includes(srcChain) || !CURRENT_SUPPORTED.includes(destChain)) {
|
|
915
|
+
const unsupportedChains = [
|
|
916
|
+
!CURRENT_SUPPORTED.includes(srcChain) ? srcChain : null,
|
|
917
|
+
!CURRENT_SUPPORTED.includes(destChain) ? destChain : null
|
|
918
|
+
].filter(Boolean).join(", ");
|
|
919
|
+
const errorMsg = `Unsupported chain(s): ${unsupportedChains}`;
|
|
920
|
+
update("error", errorMsg);
|
|
921
|
+
log("Error:", errorMsg);
|
|
922
|
+
throw new Error(errorMsg);
|
|
923
|
+
}
|
|
615
924
|
const isSingleChain = tokenIn.chainId === tokenOut.chainId;
|
|
616
925
|
const chainId = Number(tokenIn.chainId);
|
|
617
|
-
update("processing"
|
|
618
|
-
if (
|
|
619
|
-
|
|
926
|
+
update("processing");
|
|
927
|
+
if (isEvmChain3(chainId)) {
|
|
928
|
+
log("Detected EVM chain:", chainId);
|
|
929
|
+
const result = await handleEvmExecution({
|
|
620
930
|
recipientAddress,
|
|
621
931
|
quote,
|
|
622
932
|
chainId,
|
|
623
933
|
accountAddress,
|
|
624
934
|
wallet: adapter,
|
|
625
935
|
isSingleChain,
|
|
626
|
-
|
|
627
|
-
|
|
936
|
+
update,
|
|
937
|
+
orderType,
|
|
938
|
+
options
|
|
628
939
|
});
|
|
940
|
+
log("EVM execution result:", result);
|
|
941
|
+
return result;
|
|
629
942
|
}
|
|
630
|
-
if (chainId ===
|
|
631
|
-
|
|
943
|
+
if (chainId === ChainID2.Solana) {
|
|
944
|
+
log("Detected Solana chain");
|
|
945
|
+
const result = await handleSolanaExecution({
|
|
632
946
|
recipientAddress,
|
|
633
947
|
quote,
|
|
634
948
|
accountAddress,
|
|
635
949
|
wallet: adapter,
|
|
636
950
|
isSingleChain,
|
|
637
|
-
|
|
638
|
-
|
|
951
|
+
update,
|
|
952
|
+
orderType,
|
|
953
|
+
options
|
|
639
954
|
});
|
|
955
|
+
log("Solana execution result:", result);
|
|
956
|
+
return result;
|
|
640
957
|
}
|
|
641
|
-
|
|
642
|
-
|
|
958
|
+
const unsupported = `Unsupported chain: ${chainId}`;
|
|
959
|
+
update("error", unsupported);
|
|
960
|
+
log("Error:", unsupported);
|
|
961
|
+
return { status: false, message: unsupported, stage: "error" };
|
|
643
962
|
} catch (error) {
|
|
644
|
-
|
|
963
|
+
let message = "An unknown error occurred";
|
|
964
|
+
if (error && typeof error === "object") {
|
|
965
|
+
const err = error;
|
|
966
|
+
message = err.details ?? err.message ?? message;
|
|
967
|
+
} else if (typeof error === "string") {
|
|
968
|
+
message = error;
|
|
969
|
+
}
|
|
645
970
|
update("error", message);
|
|
646
971
|
return { status: false, message, stage: "error" };
|
|
647
972
|
}
|
|
@@ -652,318 +977,526 @@ function normalizeWallet(wallet) {
|
|
|
652
977
|
return wallet;
|
|
653
978
|
}
|
|
654
979
|
|
|
655
|
-
// src/
|
|
656
|
-
import {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const [
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return JSON.stringify({
|
|
667
|
-
q: params.q?.trim().toLowerCase() ?? "",
|
|
668
|
-
networkId: params.networkId ?? "all",
|
|
669
|
-
page: params.page ?? 1,
|
|
670
|
-
limit: params.limit ?? 50
|
|
671
|
-
});
|
|
672
|
-
}, [params.q, params.networkId, params.page, params.limit]);
|
|
673
|
-
async function fetchTokens(signal) {
|
|
674
|
-
if (tokenCache.has(cacheKey)) {
|
|
675
|
-
setData(tokenCache.get(cacheKey));
|
|
676
|
-
setLoading(false);
|
|
677
|
-
setError(null);
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
try {
|
|
681
|
-
setLoading(true);
|
|
682
|
-
const result = await getTokenList({ ...params, signal });
|
|
683
|
-
tokenCache.set(cacheKey, result);
|
|
684
|
-
setData(result);
|
|
685
|
-
setError(null);
|
|
686
|
-
} catch (err) {
|
|
687
|
-
if (err.name !== "AbortError") {
|
|
688
|
-
const e = err instanceof Error ? err : new Error("Unknown error while fetching tokens");
|
|
689
|
-
setError(e);
|
|
690
|
-
}
|
|
691
|
-
} finally {
|
|
692
|
-
setLoading(false);
|
|
693
|
-
}
|
|
980
|
+
// src/core/orders/getOrders.ts
|
|
981
|
+
import { fetchUserOrders } from "@shogun-sdk/intents-sdk";
|
|
982
|
+
async function getOrders({
|
|
983
|
+
evmAddress,
|
|
984
|
+
solAddress
|
|
985
|
+
}) {
|
|
986
|
+
const wallets = [evmAddress, solAddress].filter(
|
|
987
|
+
(wallet) => Boolean(wallet)
|
|
988
|
+
);
|
|
989
|
+
if (wallets.length === 0) {
|
|
990
|
+
throw new Error("At least one wallet address (EVM, Solana) must be provided.");
|
|
694
991
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
698
|
-
if (controllerRef.current) controllerRef.current.abort();
|
|
699
|
-
const controller = new AbortController();
|
|
700
|
-
controllerRef.current = controller;
|
|
701
|
-
debounceRef.current = setTimeout(() => {
|
|
702
|
-
fetchTokens(controller.signal);
|
|
703
|
-
}, debounceMs);
|
|
704
|
-
return () => {
|
|
705
|
-
controller.abort();
|
|
706
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
707
|
-
};
|
|
708
|
-
}, [cacheKey, debounceMs]);
|
|
709
|
-
return useMemo(
|
|
710
|
-
() => ({
|
|
711
|
-
/** Current fetched data (cached when possible) */
|
|
712
|
-
data,
|
|
713
|
-
/** Whether a request is in progress */
|
|
714
|
-
loading,
|
|
715
|
-
/** Error object if a request failed */
|
|
716
|
-
error,
|
|
717
|
-
/** Manually refetch the token list */
|
|
718
|
-
refetch: () => fetchTokens(),
|
|
719
|
-
/** Clear all cached token results (shared across hook instances) */
|
|
720
|
-
clearCache: () => tokenCache.clear()
|
|
721
|
-
}),
|
|
722
|
-
[data, loading, error]
|
|
992
|
+
const results = await Promise.all(
|
|
993
|
+
wallets.map((wallet) => fetchUserOrders({ wallet }))
|
|
723
994
|
);
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const [status, setStatus] = useState2("processing");
|
|
730
|
-
const [message, setMessage] = useState2(null);
|
|
731
|
-
const [loading, setLoading] = useState2(false);
|
|
732
|
-
const [data, setData] = useState2(null);
|
|
733
|
-
const [error, setError] = useState2(null);
|
|
734
|
-
const isMounted = useRef2(true);
|
|
735
|
-
useEffect2(() => {
|
|
736
|
-
return () => {
|
|
737
|
-
isMounted.current = false;
|
|
738
|
-
};
|
|
739
|
-
}, []);
|
|
740
|
-
const execute = useCallback(
|
|
741
|
-
async ({
|
|
742
|
-
quote,
|
|
743
|
-
accountAddress,
|
|
744
|
-
recipientAddress,
|
|
745
|
-
wallet,
|
|
746
|
-
deadline
|
|
747
|
-
}) => {
|
|
748
|
-
if (!quote || !wallet) {
|
|
749
|
-
throw new Error("Quote and wallet are required for order execution.");
|
|
750
|
-
}
|
|
751
|
-
setLoading(true);
|
|
752
|
-
setError(null);
|
|
753
|
-
setData(null);
|
|
754
|
-
setMessage(null);
|
|
755
|
-
try {
|
|
756
|
-
const effectiveDeadline = deadline ?? Math.floor(Date.now() / 1e3) + 20 * 60;
|
|
757
|
-
const onStatus = (stage, msg) => {
|
|
758
|
-
if (!isMounted.current) return;
|
|
759
|
-
setStatus(stage);
|
|
760
|
-
if (msg) setMessage(msg);
|
|
761
|
-
};
|
|
762
|
-
const result = await executeOrder({
|
|
763
|
-
quote,
|
|
764
|
-
accountAddress,
|
|
765
|
-
recipientAddress,
|
|
766
|
-
wallet,
|
|
767
|
-
onStatus,
|
|
768
|
-
options: { deadline: effectiveDeadline }
|
|
769
|
-
});
|
|
770
|
-
if (!isMounted.current) return result;
|
|
771
|
-
setData(result);
|
|
772
|
-
setStatus(result.stage);
|
|
773
|
-
setMessage("Order executed successfully");
|
|
774
|
-
return result;
|
|
775
|
-
} catch (err) {
|
|
776
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
777
|
-
if (isMounted.current) {
|
|
778
|
-
setError(errorObj);
|
|
779
|
-
setStatus("error");
|
|
780
|
-
setMessage(errorObj.message);
|
|
781
|
-
setData({
|
|
782
|
-
status: false,
|
|
783
|
-
stage: "error",
|
|
784
|
-
message: errorObj.message
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
return {
|
|
788
|
-
status: false,
|
|
789
|
-
stage: "error",
|
|
790
|
-
message: errorObj.message
|
|
791
|
-
};
|
|
792
|
-
} finally {
|
|
793
|
-
if (isMounted.current) setLoading(false);
|
|
794
|
-
}
|
|
995
|
+
return results.reduce(
|
|
996
|
+
(merged, orders) => {
|
|
997
|
+
merged.singleChainLimitOrders.push(...orders.singleChainLimitOrders);
|
|
998
|
+
merged.crossChainLimitOrders.push(...orders.crossChainLimitOrders);
|
|
999
|
+
return merged;
|
|
795
1000
|
},
|
|
796
|
-
[]
|
|
1001
|
+
{ singleChainLimitOrders: [], crossChainLimitOrders: [] }
|
|
797
1002
|
);
|
|
798
|
-
return {
|
|
799
|
-
/** Executes the swap order. */
|
|
800
|
-
execute,
|
|
801
|
-
/** Current execution stage. */
|
|
802
|
-
status,
|
|
803
|
-
/** Human-readable status message. */
|
|
804
|
-
message,
|
|
805
|
-
/** Whether execution is ongoing. */
|
|
806
|
-
loading,
|
|
807
|
-
/** Raw SDK response data. */
|
|
808
|
-
data,
|
|
809
|
-
/** Captured error (if execution failed). */
|
|
810
|
-
error
|
|
811
|
-
};
|
|
812
1003
|
}
|
|
813
1004
|
|
|
814
|
-
// src/
|
|
815
|
-
import {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
setWarning(null);
|
|
840
|
-
const result = await getQuote(params);
|
|
841
|
-
const serializeResult = serializeBigIntsToStrings(result);
|
|
842
|
-
if (!mounted.current) return;
|
|
843
|
-
setData((prev) => {
|
|
844
|
-
if (JSON.stringify(prev) === JSON.stringify(serializeResult)) return prev;
|
|
845
|
-
return serializeResult;
|
|
846
|
-
});
|
|
847
|
-
setWarning(result.warning ?? null);
|
|
848
|
-
setError(null);
|
|
849
|
-
} catch (err) {
|
|
850
|
-
if (err.name === "AbortError") return;
|
|
851
|
-
console.error("[useQuote] fetch error:", err);
|
|
852
|
-
if (mounted.current) setError(err instanceof Error ? err : new Error(String(err)));
|
|
853
|
-
} finally {
|
|
854
|
-
if (mounted.current) setLoading(false);
|
|
855
|
-
}
|
|
856
|
-
},
|
|
857
|
-
[params]
|
|
858
|
-
);
|
|
859
|
-
useEffect3(() => {
|
|
860
|
-
if (!params) return;
|
|
861
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
862
|
-
debounceRef.current = setTimeout(() => {
|
|
863
|
-
fetchQuote();
|
|
864
|
-
}, debounceMs);
|
|
865
|
-
return () => {
|
|
866
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
867
|
-
abortRef.current?.abort();
|
|
868
|
-
};
|
|
869
|
-
}, [params, debounceMs, fetchQuote]);
|
|
870
|
-
useEffect3(() => {
|
|
871
|
-
if (!autoRefreshMs || !params) return;
|
|
872
|
-
const interval = setInterval(() => fetchQuote(), autoRefreshMs);
|
|
873
|
-
return () => clearInterval(interval);
|
|
874
|
-
}, [autoRefreshMs, params, fetchQuote]);
|
|
875
|
-
return useMemo2(
|
|
876
|
-
() => ({
|
|
877
|
-
data,
|
|
878
|
-
loading,
|
|
879
|
-
error,
|
|
880
|
-
warning,
|
|
881
|
-
refetch: () => fetchQuote()
|
|
882
|
-
}),
|
|
883
|
-
[data, loading, error, warning, fetchQuote]
|
|
884
|
-
);
|
|
1005
|
+
// src/core/orders/cancelOrder.ts
|
|
1006
|
+
import {
|
|
1007
|
+
cancelCrossChainOrderInstructionsAsBytes,
|
|
1008
|
+
cancelSingleChainOrderInstructionsAsBytes,
|
|
1009
|
+
ChainID as ChainID3,
|
|
1010
|
+
CROSS_CHAIN_GUARD_ADDRESSES,
|
|
1011
|
+
PERMIT2_ADDRESS as PERMIT2_ADDRESS2,
|
|
1012
|
+
SINGLE_CHAIN_GUARD_ADDRESSES
|
|
1013
|
+
} from "@shogun-sdk/intents-sdk";
|
|
1014
|
+
import {
|
|
1015
|
+
VersionedMessage,
|
|
1016
|
+
VersionedTransaction as VersionedTransaction2
|
|
1017
|
+
} from "@solana/web3.js";
|
|
1018
|
+
import { encodeFunctionData as encodeFunctionData3 } from "viem";
|
|
1019
|
+
var inFlightCancels = /* @__PURE__ */ new Map();
|
|
1020
|
+
function cancelIntentsOrder(args) {
|
|
1021
|
+
const srcChain = "srcChainId" in args.order ? args.order.srcChainId : args.order.chainId;
|
|
1022
|
+
const key = `${srcChain}:${args.order.orderId}`;
|
|
1023
|
+
const existing = inFlightCancels.get(key);
|
|
1024
|
+
if (existing) return existing;
|
|
1025
|
+
const pending = performCancel(args).finally(() => {
|
|
1026
|
+
inFlightCancels.delete(key);
|
|
1027
|
+
});
|
|
1028
|
+
inFlightCancels.set(key, pending);
|
|
1029
|
+
return pending;
|
|
885
1030
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const
|
|
892
|
-
const
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
if (!
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1031
|
+
async function performCancel({
|
|
1032
|
+
order,
|
|
1033
|
+
wallet,
|
|
1034
|
+
sol_rpc
|
|
1035
|
+
}) {
|
|
1036
|
+
const isCrossChain = "srcChainId" in order && "destChainId" in order && order.srcChainId !== order.destChainId;
|
|
1037
|
+
const srcChain = "srcChainId" in order ? order.srcChainId : order.chainId;
|
|
1038
|
+
const isSolanaOrder = srcChain === ChainID3.Solana;
|
|
1039
|
+
if (isSolanaOrder) {
|
|
1040
|
+
if (!isCrossChain) {
|
|
1041
|
+
const { versionedMessageBytes: versionedMessageBytes2 } = await cancelSingleChainOrderInstructionsAsBytes(
|
|
1042
|
+
order.orderId,
|
|
1043
|
+
order.user,
|
|
1044
|
+
{ rpcUrl: sol_rpc }
|
|
1045
|
+
);
|
|
1046
|
+
const message2 = VersionedMessage.deserialize(
|
|
1047
|
+
versionedMessageBytes2
|
|
1048
|
+
);
|
|
1049
|
+
const tx2 = new VersionedTransaction2(message2);
|
|
1050
|
+
return await wallet.sendTransaction(tx2);
|
|
1051
|
+
}
|
|
1052
|
+
const { versionedMessageBytes } = await cancelCrossChainOrderInstructionsAsBytes(
|
|
1053
|
+
order.orderId,
|
|
1054
|
+
order.user,
|
|
1055
|
+
{ rpcUrl: sol_rpc }
|
|
1056
|
+
);
|
|
1057
|
+
const message = VersionedMessage.deserialize(
|
|
1058
|
+
versionedMessageBytes
|
|
1059
|
+
);
|
|
1060
|
+
const tx = new VersionedTransaction2(message);
|
|
1061
|
+
return await wallet.sendTransaction(tx);
|
|
1062
|
+
}
|
|
1063
|
+
const chainId = srcChain;
|
|
1064
|
+
const { readContract } = wallet;
|
|
1065
|
+
if (!readContract) {
|
|
1066
|
+
throw new Error("Wallet does not support readContract");
|
|
1067
|
+
}
|
|
1068
|
+
const nonce = BigInt(order.nonce ?? "0");
|
|
1069
|
+
const nonceWordPos = nonce >> 8n;
|
|
1070
|
+
const nonceBitPos = nonce - nonceWordPos * 256n;
|
|
1071
|
+
if (isCrossChain) {
|
|
1072
|
+
const [orderData = [false, false], currentNonceBitmap = 0n] = await Promise.all([
|
|
1073
|
+
readContract({
|
|
1074
|
+
address: CROSS_CHAIN_GUARD_ADDRESSES[chainId],
|
|
1075
|
+
abi: [
|
|
1076
|
+
{
|
|
1077
|
+
inputs: [{ name: "orderId", type: "bytes32" }],
|
|
1078
|
+
name: "orderData",
|
|
1079
|
+
outputs: [
|
|
1080
|
+
{ name: "initialized", type: "bool" },
|
|
1081
|
+
{ name: "deactivated", type: "bool" }
|
|
1082
|
+
],
|
|
1083
|
+
stateMutability: "view",
|
|
1084
|
+
type: "function"
|
|
1085
|
+
}
|
|
1086
|
+
],
|
|
1087
|
+
functionName: "orderData",
|
|
1088
|
+
args: [order.orderId]
|
|
1089
|
+
}),
|
|
1090
|
+
readContract({
|
|
1091
|
+
address: PERMIT2_ADDRESS2[chainId],
|
|
1092
|
+
abi: [
|
|
1093
|
+
{
|
|
1094
|
+
inputs: [
|
|
1095
|
+
{ name: "owner", type: "address" },
|
|
1096
|
+
{ name: "wordPos", type: "uint256" }
|
|
1097
|
+
],
|
|
1098
|
+
name: "nonceBitmap",
|
|
1099
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
1100
|
+
stateMutability: "view",
|
|
1101
|
+
type: "function"
|
|
1102
|
+
}
|
|
1103
|
+
],
|
|
1104
|
+
functionName: "nonceBitmap",
|
|
1105
|
+
args: [order.user, nonceWordPos]
|
|
1106
|
+
})
|
|
1107
|
+
]);
|
|
1108
|
+
const [initialized, deactivated] = orderData;
|
|
1109
|
+
if (initialized) {
|
|
1110
|
+
if (deactivated) {
|
|
1111
|
+
throw new Error("Order is already deactivated");
|
|
1112
|
+
}
|
|
1113
|
+
return await wallet.sendTransaction({
|
|
1114
|
+
to: CROSS_CHAIN_GUARD_ADDRESSES[order.srcChainId],
|
|
1115
|
+
data: encodeFunctionData3({
|
|
1116
|
+
abi: [
|
|
1117
|
+
{
|
|
1118
|
+
inputs: [
|
|
1119
|
+
{
|
|
1120
|
+
components: [
|
|
1121
|
+
{ name: "user", type: "address" },
|
|
1122
|
+
{ name: "tokenIn", type: "address" },
|
|
1123
|
+
{ name: "srcChainId", type: "uint256" },
|
|
1124
|
+
{ name: "deadline", type: "uint256" },
|
|
1125
|
+
{ name: "amountIn", type: "uint256" },
|
|
1126
|
+
{ name: "minStablecoinsAmount", type: "uint256" },
|
|
1127
|
+
{ name: "executionDetailsHash", type: "bytes32" },
|
|
1128
|
+
{ name: "nonce", type: "uint256" }
|
|
1129
|
+
],
|
|
1130
|
+
name: "orderInfo",
|
|
1131
|
+
type: "tuple"
|
|
1132
|
+
}
|
|
1133
|
+
],
|
|
1134
|
+
name: "cancelOrder",
|
|
1135
|
+
outputs: [],
|
|
1136
|
+
stateMutability: "nonpayable",
|
|
1137
|
+
type: "function"
|
|
1138
|
+
}
|
|
1139
|
+
],
|
|
1140
|
+
functionName: "cancelOrder",
|
|
1141
|
+
args: [
|
|
1142
|
+
{
|
|
1143
|
+
user: order.user,
|
|
1144
|
+
tokenIn: order.tokenIn,
|
|
1145
|
+
srcChainId: BigInt(order.srcChainId),
|
|
1146
|
+
deadline: BigInt(order.deadline),
|
|
1147
|
+
amountIn: BigInt(order.amountIn),
|
|
1148
|
+
minStablecoinsAmount: BigInt(order.minStablecoinsAmount),
|
|
1149
|
+
executionDetailsHash: order.executionDetailsHash,
|
|
1150
|
+
nonce
|
|
1151
|
+
}
|
|
1152
|
+
]
|
|
1153
|
+
}),
|
|
1154
|
+
value: BigInt(0),
|
|
1155
|
+
from: order.user
|
|
1156
|
+
});
|
|
1157
|
+
} else {
|
|
1158
|
+
if ((currentNonceBitmap & 1n << nonceBitPos) !== 0n) {
|
|
1159
|
+
throw new Error("Nonce is already invalidated");
|
|
1160
|
+
}
|
|
1161
|
+
const mask2 = 1n << nonceBitPos;
|
|
1162
|
+
return await wallet.sendTransaction({
|
|
1163
|
+
to: PERMIT2_ADDRESS2[order.srcChainId],
|
|
1164
|
+
data: encodeFunctionData3({
|
|
1165
|
+
abi: [
|
|
1166
|
+
{
|
|
1167
|
+
inputs: [
|
|
1168
|
+
{ name: "wordPos", type: "uint256" },
|
|
1169
|
+
{ name: "mask", type: "uint256" }
|
|
1170
|
+
],
|
|
1171
|
+
name: "invalidateUnorderedNonces",
|
|
1172
|
+
outputs: [],
|
|
1173
|
+
stateMutability: "nonpayable",
|
|
1174
|
+
type: "function"
|
|
1175
|
+
}
|
|
1176
|
+
],
|
|
1177
|
+
functionName: "invalidateUnorderedNonces",
|
|
1178
|
+
args: [nonceWordPos, mask2]
|
|
1179
|
+
}),
|
|
1180
|
+
value: BigInt(0),
|
|
1181
|
+
from: order.user
|
|
918
1182
|
});
|
|
919
|
-
return result;
|
|
920
|
-
} catch (err) {
|
|
921
|
-
if (err.name === "AbortError") return;
|
|
922
|
-
const e = err instanceof Error ? err : new Error(String(err));
|
|
923
|
-
setError(e);
|
|
924
|
-
throw e;
|
|
925
|
-
} finally {
|
|
926
|
-
setLoading(false);
|
|
927
1183
|
}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
|
|
1184
|
+
}
|
|
1185
|
+
const [wasManuallyInitialized, currentBitmap = 0n] = await Promise.all([
|
|
1186
|
+
readContract({
|
|
1187
|
+
address: SINGLE_CHAIN_GUARD_ADDRESSES[chainId],
|
|
1188
|
+
abi: [
|
|
1189
|
+
{
|
|
1190
|
+
inputs: [{ name: "orderHash", type: "bytes32" }],
|
|
1191
|
+
name: "orderManuallyInitialized",
|
|
1192
|
+
outputs: [{ name: "", type: "bool" }],
|
|
1193
|
+
stateMutability: "view",
|
|
1194
|
+
type: "function"
|
|
1195
|
+
}
|
|
1196
|
+
],
|
|
1197
|
+
functionName: "orderManuallyInitialized",
|
|
1198
|
+
args: [order.orderId]
|
|
1199
|
+
}),
|
|
1200
|
+
readContract({
|
|
1201
|
+
address: PERMIT2_ADDRESS2[chainId],
|
|
1202
|
+
abi: [
|
|
1203
|
+
{
|
|
1204
|
+
inputs: [
|
|
1205
|
+
{ name: "owner", type: "address" },
|
|
1206
|
+
{ name: "wordPos", type: "uint256" }
|
|
1207
|
+
],
|
|
1208
|
+
name: "nonceBitmap",
|
|
1209
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
1210
|
+
stateMutability: "view",
|
|
1211
|
+
type: "function"
|
|
1212
|
+
}
|
|
1213
|
+
],
|
|
1214
|
+
functionName: "nonceBitmap",
|
|
1215
|
+
args: [order.user, nonceWordPos]
|
|
1216
|
+
})
|
|
1217
|
+
]);
|
|
1218
|
+
if (wasManuallyInitialized) {
|
|
1219
|
+
return await wallet.sendTransaction({
|
|
1220
|
+
to: SINGLE_CHAIN_GUARD_ADDRESSES[chainId],
|
|
1221
|
+
data: encodeFunctionData3({
|
|
1222
|
+
abi: [
|
|
1223
|
+
{
|
|
1224
|
+
inputs: [
|
|
1225
|
+
{
|
|
1226
|
+
name: "order",
|
|
1227
|
+
type: "tuple",
|
|
1228
|
+
components: [
|
|
1229
|
+
{ name: "amountIn", type: "uint256" },
|
|
1230
|
+
{ name: "tokenIn", type: "address" },
|
|
1231
|
+
{ name: "deadline", type: "uint256" },
|
|
1232
|
+
{ name: "nonce", type: "uint256" },
|
|
1233
|
+
{ name: "encodedExternalCallData", type: "bytes" },
|
|
1234
|
+
{
|
|
1235
|
+
name: "extraTransfers",
|
|
1236
|
+
type: "tuple[]",
|
|
1237
|
+
components: [
|
|
1238
|
+
{ name: "amount", type: "uint256" },
|
|
1239
|
+
{ name: "receiver", type: "address" },
|
|
1240
|
+
{ name: "token", type: "address" }
|
|
1241
|
+
]
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
name: "requestedOutput",
|
|
1245
|
+
type: "tuple",
|
|
1246
|
+
components: [
|
|
1247
|
+
{ name: "amount", type: "uint256" },
|
|
1248
|
+
{ name: "receiver", type: "address" },
|
|
1249
|
+
{ name: "token", type: "address" }
|
|
1250
|
+
]
|
|
1251
|
+
},
|
|
1252
|
+
{ name: "user", type: "address" }
|
|
1253
|
+
]
|
|
1254
|
+
}
|
|
1255
|
+
],
|
|
1256
|
+
name: "cancelManuallyCreatedOrder",
|
|
1257
|
+
outputs: [],
|
|
1258
|
+
stateMutability: "nonpayable",
|
|
1259
|
+
type: "function"
|
|
1260
|
+
}
|
|
1261
|
+
],
|
|
1262
|
+
functionName: "cancelManuallyCreatedOrder",
|
|
1263
|
+
args: [
|
|
1264
|
+
{
|
|
1265
|
+
amountIn: BigInt(order.amountIn),
|
|
1266
|
+
tokenIn: order.tokenIn,
|
|
1267
|
+
deadline: BigInt(order.deadline),
|
|
1268
|
+
nonce,
|
|
1269
|
+
encodedExternalCallData: "0x",
|
|
1270
|
+
extraTransfers: (order.extraTransfers || []).map((t) => ({
|
|
1271
|
+
amount: BigInt(t.amount),
|
|
1272
|
+
receiver: t.receiver,
|
|
1273
|
+
token: t.token
|
|
1274
|
+
})),
|
|
1275
|
+
requestedOutput: {
|
|
1276
|
+
amount: BigInt(order.amountOutMin),
|
|
1277
|
+
receiver: order.destinationAddress,
|
|
1278
|
+
token: order.tokenOut
|
|
1279
|
+
},
|
|
1280
|
+
user: order.user
|
|
1281
|
+
}
|
|
1282
|
+
]
|
|
1283
|
+
}),
|
|
1284
|
+
value: 0n,
|
|
1285
|
+
from: order.user
|
|
931
1286
|
});
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1287
|
+
}
|
|
1288
|
+
const mask = 1n << nonceBitPos;
|
|
1289
|
+
if ((currentBitmap & mask) !== 0n) {
|
|
1290
|
+
throw new Error("Nonce is already invalidated");
|
|
1291
|
+
}
|
|
1292
|
+
return await wallet.sendTransaction({
|
|
1293
|
+
to: PERMIT2_ADDRESS2[chainId],
|
|
1294
|
+
data: encodeFunctionData3({
|
|
1295
|
+
abi: [
|
|
1296
|
+
{
|
|
1297
|
+
inputs: [
|
|
1298
|
+
{ name: "wordPos", type: "uint256" },
|
|
1299
|
+
{ name: "mask", type: "uint256" }
|
|
1300
|
+
],
|
|
1301
|
+
name: "invalidateUnorderedNonces",
|
|
1302
|
+
outputs: [],
|
|
1303
|
+
stateMutability: "nonpayable",
|
|
1304
|
+
type: "function"
|
|
1305
|
+
}
|
|
1306
|
+
],
|
|
1307
|
+
functionName: "invalidateUnorderedNonces",
|
|
1308
|
+
args: [nonceWordPos, mask]
|
|
946
1309
|
}),
|
|
947
|
-
|
|
948
|
-
|
|
1310
|
+
value: 0n,
|
|
1311
|
+
from: order.user
|
|
1312
|
+
});
|
|
949
1313
|
}
|
|
1314
|
+
|
|
1315
|
+
// src/core/client.ts
|
|
1316
|
+
var SwapSDK = class {
|
|
1317
|
+
constructor(config) {
|
|
1318
|
+
__publicField(this, "apiKey");
|
|
1319
|
+
/**
|
|
1320
|
+
* Fetches metadata for one or more tokens from the Shogun Token Search API.
|
|
1321
|
+
*
|
|
1322
|
+
* ---
|
|
1323
|
+
* ### Overview
|
|
1324
|
+
* `getTokensData` retrieves normalized token information — such as symbol, name,
|
|
1325
|
+
* decimals, logo URI, and verified status — for a given list of token addresses.
|
|
1326
|
+
*
|
|
1327
|
+
* It supports both **EVM** and **SVM (Solana)** tokens, returning metadata from
|
|
1328
|
+
* Shogun’s unified token registry.
|
|
1329
|
+
*
|
|
1330
|
+
* ---
|
|
1331
|
+
* @example
|
|
1332
|
+
* ```ts
|
|
1333
|
+
* const tokens = await getTokensData([
|
|
1334
|
+
* "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
|
|
1335
|
+
* "So11111111111111111111111111111111111111112", // SOL
|
|
1336
|
+
* ]);
|
|
1337
|
+
*
|
|
1338
|
+
* console.log(tokens);
|
|
1339
|
+
* [
|
|
1340
|
+
* { symbol: "USDC", name: "USD Coin", chainId: 1, decimals: 6, ... },
|
|
1341
|
+
* { symbol: "SOL", name: "Solana", chainId: 101, decimals: 9, ... }
|
|
1342
|
+
* ]
|
|
1343
|
+
* ```
|
|
1344
|
+
*
|
|
1345
|
+
* @param addresses - An array of token addresses (EVM or SVM) to fetch metadata for.
|
|
1346
|
+
* @returns A promise resolving to an array of {@link TokenInfo} objects.
|
|
1347
|
+
*
|
|
1348
|
+
* @throws Will throw an error if the network request fails or the API responds with a non-OK status.
|
|
1349
|
+
*/
|
|
1350
|
+
__publicField(this, "getTokensData", getTokensData.bind(this));
|
|
1351
|
+
if (!config.apiKey) {
|
|
1352
|
+
throw new Error("SwapSDK: Missing API key");
|
|
1353
|
+
}
|
|
1354
|
+
this.apiKey = config.apiKey;
|
|
1355
|
+
if (this.apiKey) void this.apiKey;
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Retrieves a swap quote for the given input and output tokens.
|
|
1359
|
+
*
|
|
1360
|
+
* @param params - Quote parameters including source/destination tokens and amount.
|
|
1361
|
+
* @returns A normalized `SwapQuoteResponse` containing output amount, route, and metadata.
|
|
1362
|
+
*/
|
|
1363
|
+
async getQuote(params) {
|
|
1364
|
+
return getQuote(params);
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Fetches token balances for the specified user wallet(s).
|
|
1368
|
+
*
|
|
1369
|
+
* Supports both EVM and SVM (Solana) wallet addresses.
|
|
1370
|
+
*
|
|
1371
|
+
* @param params - Wallet address and optional chain filters.
|
|
1372
|
+
* @param options - Optional abort signal for cancellation.
|
|
1373
|
+
* @returns A unified balance response with per-chain token details.
|
|
1374
|
+
*/
|
|
1375
|
+
async getBalances(params, options) {
|
|
1376
|
+
return getBalances(params, options);
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Retrieves a list of verified tokens based on search query or chain filter.
|
|
1380
|
+
*
|
|
1381
|
+
* @param params - Search parameters (query, chain ID, pagination options).
|
|
1382
|
+
* @returns Paginated `TokenSearchResponse` containing token metadata.
|
|
1383
|
+
*/
|
|
1384
|
+
async getTokenList(params) {
|
|
1385
|
+
return getTokenList(params);
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Executes a prepared swap quote using the provided wallet and configuration.
|
|
1389
|
+
*
|
|
1390
|
+
* Handles:
|
|
1391
|
+
* - Token approval (if required)
|
|
1392
|
+
* - Transaction signing and broadcasting
|
|
1393
|
+
* - Confirmation polling and stage-based status updates
|
|
1394
|
+
*
|
|
1395
|
+
* Supports both:
|
|
1396
|
+
* - Market orders (with optional deadline)
|
|
1397
|
+
* - Limit orders (requires executionPrice and deadline)
|
|
1398
|
+
*
|
|
1399
|
+
* @param quote - The swap quote to execute, containing route and metadata.
|
|
1400
|
+
* @param accountAddress - The user's wallet address executing the swap.
|
|
1401
|
+
* @param recipientAddress - Optional recipient address for the output tokens (defaults to sender).
|
|
1402
|
+
* @param wallet - Adapted wallet instance (EVM/Solana) or a standard Viem `WalletClient`.
|
|
1403
|
+
* @param onStatus - Optional callback for receiving execution stage updates and messages.
|
|
1404
|
+
* @param orderType - Defines whether this is a market or limit order.
|
|
1405
|
+
* @param options - Execution parameters (different per order type).
|
|
1406
|
+
*
|
|
1407
|
+
* @returns A finalized execution result containing transaction hash, status, and any returned data.
|
|
1408
|
+
*
|
|
1409
|
+
* @example
|
|
1410
|
+
* ```ts
|
|
1411
|
+
* * Market order
|
|
1412
|
+
* const result = await sdk.executeTransaction({
|
|
1413
|
+
* quote,
|
|
1414
|
+
* accountAddress: "0x123...",
|
|
1415
|
+
* wallet,
|
|
1416
|
+
* orderType: OrderExecutionType.MARKET,
|
|
1417
|
+
* options: { deadline: 1800 },
|
|
1418
|
+
* onStatus: (stage, msg) => console.log(stage, msg),
|
|
1419
|
+
* });
|
|
1420
|
+
*
|
|
1421
|
+
* * Limit order
|
|
1422
|
+
* const result = await sdk.executeTransaction({
|
|
1423
|
+
* quote,
|
|
1424
|
+
* accountAddress: "0x123...",
|
|
1425
|
+
* wallet,
|
|
1426
|
+
* orderType: OrderExecutionType.LIMIT,
|
|
1427
|
+
* options: { executionPrice: "0.0021", deadline: 3600 },
|
|
1428
|
+
* });
|
|
1429
|
+
* ```
|
|
1430
|
+
*/
|
|
1431
|
+
async executeTransaction({
|
|
1432
|
+
quote,
|
|
1433
|
+
accountAddress,
|
|
1434
|
+
recipientAddress,
|
|
1435
|
+
wallet,
|
|
1436
|
+
onStatus,
|
|
1437
|
+
orderType,
|
|
1438
|
+
options
|
|
1439
|
+
}) {
|
|
1440
|
+
return executeOrder({
|
|
1441
|
+
quote,
|
|
1442
|
+
wallet,
|
|
1443
|
+
accountAddress,
|
|
1444
|
+
recipientAddress,
|
|
1445
|
+
onStatus,
|
|
1446
|
+
orderType,
|
|
1447
|
+
options
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Fetches all user orders (Market, Limit, Cross-chain) for connected wallets.
|
|
1452
|
+
*
|
|
1453
|
+
* ---
|
|
1454
|
+
* ### Overview
|
|
1455
|
+
* Retrieves both **single-chain** and **cross-chain** orders from the Shogun Intents API.
|
|
1456
|
+
* Works across EVM, Solana
|
|
1457
|
+
*
|
|
1458
|
+
* ---
|
|
1459
|
+
* @example
|
|
1460
|
+
* ```ts
|
|
1461
|
+
* const orders = await sdk.getOrders({
|
|
1462
|
+
* evmAddress: "0x123...",
|
|
1463
|
+
* solAddress: "9d12hF...abc",
|
|
1464
|
+
* });
|
|
1465
|
+
*
|
|
1466
|
+
* console.log(orders.singleChainLimitOrders);
|
|
1467
|
+
* ```
|
|
1468
|
+
*
|
|
1469
|
+
* @param params - Wallet addresses to fetch orders for (EVM, Solana).
|
|
1470
|
+
* @returns A structured {@link ApiUserOrders} object containing all user orders.
|
|
1471
|
+
*/
|
|
1472
|
+
async getOrders(params) {
|
|
1473
|
+
return getOrders(params);
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Cancels an order (single-chain or cross-chain) on EVM or Solana.
|
|
1477
|
+
*
|
|
1478
|
+
* Internally routes to the correct guard contract or Solana program to
|
|
1479
|
+
* deactivate the order or invalidate its nonce.
|
|
1480
|
+
*
|
|
1481
|
+
* @param params.order - Order payload returned from the Intents API.
|
|
1482
|
+
* @param params.wallet - Adapted wallet used to submit the cancellation transaction.
|
|
1483
|
+
* @param params.solRpc - Solana RPC URL used for SVM cancellations.
|
|
1484
|
+
*
|
|
1485
|
+
* @returns A transaction signature or hash from the underlying wallet.
|
|
1486
|
+
*/
|
|
1487
|
+
async cancelOrder(params) {
|
|
1488
|
+
return cancelIntentsOrder({
|
|
1489
|
+
order: params.order,
|
|
1490
|
+
wallet: params.wallet,
|
|
1491
|
+
sol_rpc: params.solRpc
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
950
1495
|
export {
|
|
951
|
-
|
|
952
|
-
|
|
1496
|
+
ChainId,
|
|
1497
|
+
OrderExecutionType,
|
|
953
1498
|
SupportedChains,
|
|
954
|
-
|
|
955
|
-
adaptSolanaWallet,
|
|
956
|
-
adaptViemWallet,
|
|
1499
|
+
SwapSDK,
|
|
957
1500
|
buildQuoteParams,
|
|
958
|
-
|
|
959
|
-
getBalances,
|
|
960
|
-
getQuote,
|
|
961
|
-
getTokenList,
|
|
962
|
-
isNativeAddress,
|
|
963
|
-
isViemWalletClient,
|
|
964
|
-
serializeBigIntsToStrings,
|
|
965
|
-
useBalances,
|
|
966
|
-
useExecuteOrder,
|
|
967
|
-
useQuote,
|
|
968
|
-
useTokenList
|
|
1501
|
+
isEvmChain
|
|
969
1502
|
};
|