@xswap-link/sdk 0.10.14 → 0.11.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/CHANGELOG.md +12 -0
- package/dist/index.global.js +394 -394
- package/dist/index.js +29742 -87
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +29799 -87
- package/dist/index.mjs.map +1 -0
- package/package.json +2 -2
- package/polyfills/buffer.js +3 -7
- package/src/assets/icons/DotRedIcon.tsx +12 -0
- package/src/assets/icons/index.ts +1 -0
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/SwapPanel/index.tsx +15 -8
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +196 -29
- package/src/components/Swap/SwapView/ConfirmationView/TxResult/index.tsx +11 -1
- package/src/components/Swap/SwapView/FeesPanel/Fees/index.tsx +12 -7
- package/src/components/Swap/SwapView/FeesPanel/index.tsx +158 -9
- package/src/components/Swap/SwapView/SwapButton/index.tsx +40 -11
- package/src/components/Swap/SwapView/SwapPanel/AmountPanel/index.tsx +27 -1
- package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +60 -18
- package/src/components/Swap/SwapView/WalletPicker/index.tsx +98 -27
- package/src/constants/index.ts +5 -2
- package/src/context/SwapProvider.tsx +10 -2
- package/src/services/svm/core/client/index.ts +8 -2
- package/src/services/svm/core/client/send.ts +3 -44
- package/src/services/svm/core/models.ts +1 -1
- package/tsup.config.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xswap-link/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "JavaScript SDK for XSwap platform",
|
|
5
5
|
"homepage": "https://github.com/xswap-link/xswap-sdk",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
|
39
39
|
"@r2wc/core": "^1.1.0",
|
|
40
40
|
"@solana/spl-token": "^0.4.13",
|
|
41
|
-
"@solana/wallet-adapter-react": "^0.15.
|
|
41
|
+
"@solana/wallet-adapter-react": "^0.15.38",
|
|
42
42
|
"@solana/wallet-adapter-wallets": "^0.19.32",
|
|
43
43
|
"@solana/web3.js": "^1.98.0",
|
|
44
44
|
"@tanstack/react-query": "^5.64.2",
|
package/polyfills/buffer.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
+
import { Buffer } from "buffer";
|
|
2
|
+
|
|
1
3
|
if (typeof globalThis.Buffer === "undefined") {
|
|
2
|
-
|
|
3
|
-
.then(({ Buffer }) => {
|
|
4
|
-
globalThis.Buffer = Buffer;
|
|
5
|
-
})
|
|
6
|
-
.catch((err) => {
|
|
7
|
-
console.error("Failed to load Buffer polyfill:", err);
|
|
8
|
-
});
|
|
4
|
+
globalThis.Buffer = Buffer;
|
|
9
5
|
}
|
|
@@ -15,6 +15,7 @@ export * from "./CloseIcon";
|
|
|
15
15
|
export * from "./CoinsIcon";
|
|
16
16
|
export * from "./CopyIcon";
|
|
17
17
|
export * from "./DotGreenIcon";
|
|
18
|
+
export * from "./DotRedIcon";
|
|
18
19
|
export * from "./DownArrorIcon";
|
|
19
20
|
export * from "./ErrorIcon";
|
|
20
21
|
export * from "./ErrorModalStatusIcon";
|
|
@@ -2,6 +2,7 @@ import { TokenLogo } from "@src/components";
|
|
|
2
2
|
import { useSwapContext } from "@src/context";
|
|
3
3
|
import { SwapPanelType } from "../../../index";
|
|
4
4
|
import { useMemo } from "react";
|
|
5
|
+
import { Ecosystem } from "@src/models";
|
|
5
6
|
|
|
6
7
|
type Props = {
|
|
7
8
|
type: SwapPanelType;
|
|
@@ -16,6 +17,7 @@ export const SwapPanel = ({ type }: Props) => {
|
|
|
16
17
|
srcValueUsd,
|
|
17
18
|
dstValue,
|
|
18
19
|
dstValueUsd,
|
|
20
|
+
bridgeUI,
|
|
19
21
|
} = useSwapContext();
|
|
20
22
|
|
|
21
23
|
const token = useMemo(
|
|
@@ -28,15 +30,20 @@ export const SwapPanel = ({ type }: Props) => {
|
|
|
28
30
|
[dstChain, srcChain, type],
|
|
29
31
|
);
|
|
30
32
|
|
|
31
|
-
const value = useMemo(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const value = useMemo(() => {
|
|
34
|
+
if (
|
|
35
|
+
type === "destination" &&
|
|
36
|
+
bridgeUI &&
|
|
37
|
+
srcChain?.ecosystem === Ecosystem.SOLANA
|
|
38
|
+
) {
|
|
39
|
+
return srcValue;
|
|
40
|
+
}
|
|
41
|
+
return type === "source" ? srcValue : dstValue;
|
|
42
|
+
}, [type, bridgeUI, srcChain?.ecosystem, srcValue, dstValue]);
|
|
35
43
|
|
|
36
|
-
const valueUsd = useMemo(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
44
|
+
const valueUsd = useMemo(() => {
|
|
45
|
+
return type === "source" ? srcValueUsd : dstValueUsd;
|
|
46
|
+
}, [dstValueUsd, srcValueUsd, type]);
|
|
40
47
|
|
|
41
48
|
return (
|
|
42
49
|
<div className="flex justify-between bg-gradient-to-r from-t_main_accent_light/20 to-t_main_accent_dark/20 p-5 rounded-xl">
|
|
@@ -18,7 +18,11 @@ import {
|
|
|
18
18
|
} from "@src/contracts";
|
|
19
19
|
import { useEvmContractApi } from "@src/hooks";
|
|
20
20
|
import useNetworks from "@src/hooks/networkManagement/useNetworks";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
Ecosystem,
|
|
23
|
+
TxStatus,
|
|
24
|
+
Transaction as EVMTransaction,
|
|
25
|
+
} from "@src/models";
|
|
22
26
|
import { renderTxStatus } from "@src/services";
|
|
23
27
|
import { ContractReceipt, constants, ethers } from "ethers";
|
|
24
28
|
import { useCallback } from "react";
|
|
@@ -28,20 +32,31 @@ import { Header } from "./Header";
|
|
|
28
32
|
import { Steps } from "./Steps";
|
|
29
33
|
import { SwapPanel } from "./SwapPanel";
|
|
30
34
|
import { useWallet } from "@solana/wallet-adapter-react";
|
|
35
|
+
|
|
31
36
|
import {
|
|
32
37
|
Connection,
|
|
33
|
-
Keypair,
|
|
34
38
|
PublicKey,
|
|
39
|
+
Transaction,
|
|
35
40
|
VersionedTransaction,
|
|
36
41
|
} from "@solana/web3.js";
|
|
37
42
|
import {
|
|
43
|
+
AddressConversion,
|
|
38
44
|
CCIPClient,
|
|
39
45
|
CCIPContext,
|
|
40
46
|
CCIPCoreConfig,
|
|
41
47
|
CCIPProvider,
|
|
42
48
|
CCIPSendRequest,
|
|
43
49
|
} from "@src/services/svm";
|
|
44
|
-
import
|
|
50
|
+
import { BN } from "@coral-xyz/anchor";
|
|
51
|
+
import {
|
|
52
|
+
Account,
|
|
53
|
+
createApproveInstruction,
|
|
54
|
+
getAccount,
|
|
55
|
+
getAssociatedTokenAddressSync,
|
|
56
|
+
TOKEN_2022_PROGRAM_ID,
|
|
57
|
+
TOKEN_PROGRAM_ID,
|
|
58
|
+
} from "@solana/spl-token";
|
|
59
|
+
import { findFeeBillingSignerPDA } from "@src/services/svm/utils/pdas";
|
|
45
60
|
|
|
46
61
|
type Props = {
|
|
47
62
|
onCloseClick: () => void;
|
|
@@ -77,7 +92,8 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
77
92
|
evm: { signer, address: evmAddress },
|
|
78
93
|
},
|
|
79
94
|
} = useNetworks();
|
|
80
|
-
const
|
|
95
|
+
const { publicKey, signTransaction } = useWallet();
|
|
96
|
+
|
|
81
97
|
const evmContractApi = useEvmContractApi();
|
|
82
98
|
|
|
83
99
|
const trackTransaction = useCallback(
|
|
@@ -86,7 +102,7 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
86
102
|
return;
|
|
87
103
|
}
|
|
88
104
|
|
|
89
|
-
const transactionData:
|
|
105
|
+
const transactionData: EVMTransaction = {
|
|
90
106
|
hash: txReceipt.transactionHash,
|
|
91
107
|
timestamp: Date.now() / 1000,
|
|
92
108
|
sourceChainId: srcChain.chainId,
|
|
@@ -278,18 +294,57 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
278
294
|
"confirmed",
|
|
279
295
|
);
|
|
280
296
|
|
|
297
|
+
if (!publicKey) {
|
|
298
|
+
throw new Error("No public key found");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Delegate token authority first (APPROVE)
|
|
302
|
+
await checkAndDelegateTokenAuthority(
|
|
303
|
+
connection,
|
|
304
|
+
publicKey,
|
|
305
|
+
new PublicKey(srcToken.address),
|
|
306
|
+
CCIP_ROUTER_PROGRAM_ID,
|
|
307
|
+
signTransaction as (
|
|
308
|
+
tx: VersionedTransaction,
|
|
309
|
+
) => Promise<VersionedTransaction>,
|
|
310
|
+
);
|
|
311
|
+
|
|
281
312
|
const provider: CCIPProvider = {
|
|
282
313
|
connection,
|
|
283
|
-
wallet:
|
|
314
|
+
wallet: null,
|
|
284
315
|
getAddress() {
|
|
285
|
-
return
|
|
316
|
+
return publicKey || PublicKey.default;
|
|
286
317
|
},
|
|
287
318
|
async signTransaction(tx: VersionedTransaction) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
319
|
+
if (!signTransaction) {
|
|
320
|
+
throw new Error("No sign transaction function found");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!(tx instanceof VersionedTransaction)) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
"Transaction must be a VersionedTransaction",
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const signedTx = await signTransaction(tx);
|
|
331
|
+
|
|
332
|
+
if (!signedTx.signatures[0]?.some((byte) => byte !== 0)) {
|
|
333
|
+
throw new Error("Transaction was not properly signed");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return signedTx;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error("Transaction signing/sending failed:", error);
|
|
339
|
+
if (error instanceof Error && "getLogs" in error) {
|
|
340
|
+
console.error(
|
|
341
|
+
"Transaction logs:",
|
|
342
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
343
|
+
await (error as any).getLogs(),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
throw error;
|
|
291
347
|
}
|
|
292
|
-
return signedTransaction;
|
|
293
348
|
},
|
|
294
349
|
};
|
|
295
350
|
|
|
@@ -312,37 +367,45 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
312
367
|
const client = new CCIPClient(context);
|
|
313
368
|
|
|
314
369
|
const extraArgs = client.createExtraArgs({
|
|
315
|
-
gasLimit: 0,
|
|
316
370
|
allowOutOfOrderExecution: true,
|
|
317
371
|
});
|
|
318
372
|
|
|
319
|
-
if (
|
|
373
|
+
if (
|
|
374
|
+
!evmAddress ||
|
|
375
|
+
!evmAddress.startsWith("0x") ||
|
|
376
|
+
evmAddress.length !== 42
|
|
377
|
+
) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
"Invalid EVM address. Must be a valid 20-byte address starting with 0x",
|
|
380
|
+
);
|
|
381
|
+
}
|
|
320
382
|
|
|
321
383
|
const sendRequest: CCIPSendRequest = {
|
|
322
|
-
destChainSelector: new
|
|
323
|
-
receiver:
|
|
324
|
-
|
|
325
|
-
? evmAddress.slice(2)
|
|
326
|
-
: evmAddress || "",
|
|
327
|
-
"hex",
|
|
328
|
-
),
|
|
329
|
-
data: Buffer.alloc(0),
|
|
384
|
+
destChainSelector: new BN(dstChain?.ccipChainId || "0"),
|
|
385
|
+
receiver: AddressConversion.evmAddressToSolanaBytes(evmAddress),
|
|
386
|
+
data: new Uint8Array(0),
|
|
330
387
|
tokenAmounts: [
|
|
331
388
|
{
|
|
332
389
|
token: new PublicKey(srcToken.address),
|
|
333
|
-
amount: new
|
|
390
|
+
amount: new BN(srcValueWei.toString()),
|
|
334
391
|
},
|
|
335
392
|
],
|
|
336
393
|
feeToken: PublicKey.default,
|
|
337
394
|
extraArgs: extraArgs,
|
|
338
395
|
};
|
|
339
396
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
397
|
+
try {
|
|
398
|
+
const result = await client.sendWithMessageId(sendRequest);
|
|
399
|
+
|
|
400
|
+
if (result.txSignature) {
|
|
401
|
+
return result.txSignature;
|
|
402
|
+
}
|
|
403
|
+
} catch (sendError) {
|
|
404
|
+
console.error("CCIP transaction failed:", sendError);
|
|
405
|
+
}
|
|
343
406
|
}
|
|
344
407
|
|
|
345
|
-
if (!route || !route.swapTransaction || !
|
|
408
|
+
if (!route || !route.swapTransaction || !publicKey) {
|
|
346
409
|
return;
|
|
347
410
|
}
|
|
348
411
|
|
|
@@ -350,7 +413,7 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
350
413
|
Buffer.from(route.swapTransaction, "base64"),
|
|
351
414
|
);
|
|
352
415
|
|
|
353
|
-
return
|
|
416
|
+
return signTransaction?.(transaction);
|
|
354
417
|
},
|
|
355
418
|
txStatus: TxStatus.SIGNING_TX,
|
|
356
419
|
handlers: {
|
|
@@ -376,11 +439,12 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
376
439
|
trackTransaction,
|
|
377
440
|
dstChain?.chainId,
|
|
378
441
|
dstChain?.ccipChainId,
|
|
379
|
-
dstToken,
|
|
442
|
+
dstToken?.address,
|
|
380
443
|
dstValueWei,
|
|
381
444
|
setTxNeedsApproval,
|
|
382
445
|
bridgeUI,
|
|
383
|
-
|
|
446
|
+
signTransaction,
|
|
447
|
+
publicKey,
|
|
384
448
|
evmAddress,
|
|
385
449
|
srcValueWei,
|
|
386
450
|
]);
|
|
@@ -417,3 +481,106 @@ export const TxOverview = ({ onCloseClick }: Props) => {
|
|
|
417
481
|
</div>
|
|
418
482
|
);
|
|
419
483
|
};
|
|
484
|
+
|
|
485
|
+
// Add this function to check token delegation and delegate if necessary
|
|
486
|
+
async function checkAndDelegateTokenAuthority(
|
|
487
|
+
connection: Connection,
|
|
488
|
+
publicKey: PublicKey,
|
|
489
|
+
tokenMint: PublicKey,
|
|
490
|
+
routerProgramId: PublicKey,
|
|
491
|
+
signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>,
|
|
492
|
+
) {
|
|
493
|
+
try {
|
|
494
|
+
const [feeBillingSignerPDA] = findFeeBillingSignerPDA(routerProgramId);
|
|
495
|
+
|
|
496
|
+
const tokenAccount = getAssociatedTokenAddressSync(
|
|
497
|
+
tokenMint,
|
|
498
|
+
publicKey,
|
|
499
|
+
false,
|
|
500
|
+
TOKEN_PROGRAM_ID,
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
const token2022Account = getAssociatedTokenAddressSync(
|
|
504
|
+
tokenMint,
|
|
505
|
+
publicKey,
|
|
506
|
+
false,
|
|
507
|
+
TOKEN_2022_PROGRAM_ID,
|
|
508
|
+
);
|
|
509
|
+
let is2022Token = false;
|
|
510
|
+
try {
|
|
511
|
+
let tokenAccountInfo: Account | undefined;
|
|
512
|
+
try {
|
|
513
|
+
tokenAccountInfo = await getAccount(
|
|
514
|
+
connection,
|
|
515
|
+
tokenAccount,
|
|
516
|
+
connection.commitment,
|
|
517
|
+
TOKEN_PROGRAM_ID,
|
|
518
|
+
);
|
|
519
|
+
} catch (err) {
|
|
520
|
+
console.error("Failed to fetch Token account:", err);
|
|
521
|
+
}
|
|
522
|
+
if (!tokenAccountInfo) {
|
|
523
|
+
try {
|
|
524
|
+
tokenAccountInfo = await getAccount(
|
|
525
|
+
connection,
|
|
526
|
+
token2022Account,
|
|
527
|
+
connection.commitment,
|
|
528
|
+
TOKEN_2022_PROGRAM_ID,
|
|
529
|
+
);
|
|
530
|
+
is2022Token = true;
|
|
531
|
+
} catch (err) {
|
|
532
|
+
console.error("Failed to fetch Token-2022 account:", err);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (!tokenAccountInfo) {
|
|
536
|
+
throw new Error("Failed to fetch tokenAccountInfo");
|
|
537
|
+
}
|
|
538
|
+
if (
|
|
539
|
+
tokenAccountInfo.delegate !== null &&
|
|
540
|
+
tokenAccountInfo.delegate.equals(feeBillingSignerPDA) &&
|
|
541
|
+
tokenAccountInfo.delegatedAmount > BigInt(0)
|
|
542
|
+
) {
|
|
543
|
+
// Token already properly delegated, skipping approval
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.error(
|
|
548
|
+
"Error checking token account, proceeding with delegation:",
|
|
549
|
+
error,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const MAX_UINT64 = ((BigInt(1) << BigInt(64)) - BigInt(1)).toString();
|
|
554
|
+
const approveInstruction = createApproveInstruction(
|
|
555
|
+
is2022Token ? token2022Account : tokenAccount,
|
|
556
|
+
feeBillingSignerPDA,
|
|
557
|
+
publicKey,
|
|
558
|
+
BigInt(MAX_UINT64),
|
|
559
|
+
[],
|
|
560
|
+
is2022Token ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID,
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
const { blockhash, lastValidBlockHeight } =
|
|
564
|
+
await connection.getLatestBlockhash();
|
|
565
|
+
const transaction = new Transaction({
|
|
566
|
+
feePayer: publicKey,
|
|
567
|
+
blockhash,
|
|
568
|
+
lastValidBlockHeight,
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
transaction.add(approveInstruction);
|
|
572
|
+
|
|
573
|
+
const versionedTx = new VersionedTransaction(transaction.compileMessage());
|
|
574
|
+
const signedTx = await signTransaction(versionedTx);
|
|
575
|
+
const signature = await connection.sendRawTransaction(signedTx.serialize());
|
|
576
|
+
|
|
577
|
+
await connection.confirmTransaction({
|
|
578
|
+
signature,
|
|
579
|
+
blockhash,
|
|
580
|
+
lastValidBlockHeight,
|
|
581
|
+
});
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.error("Failed to check or delegate token authority:", error);
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
} from "@src/assets/icons";
|
|
8
8
|
import { useSwapContext, useTxUIWrapper } from "@src/context";
|
|
9
9
|
import { TokenItem } from "./TokenItem";
|
|
10
|
+
import { useMemo } from "react";
|
|
11
|
+
import { Ecosystem } from "@src/models";
|
|
10
12
|
|
|
11
13
|
type Props = {
|
|
12
14
|
onCloseClick: () => void;
|
|
@@ -22,8 +24,16 @@ export const TxResult = ({ onCloseClick }: Props) => {
|
|
|
22
24
|
dstToken,
|
|
23
25
|
srcValue,
|
|
24
26
|
dstValue,
|
|
27
|
+
bridgeUI,
|
|
25
28
|
} = useSwapContext();
|
|
26
29
|
|
|
30
|
+
const value = useMemo(() => {
|
|
31
|
+
if (bridgeUI && srcChain?.ecosystem === Ecosystem.SOLANA) {
|
|
32
|
+
return srcValue;
|
|
33
|
+
}
|
|
34
|
+
return dstValue;
|
|
35
|
+
}, [bridgeUI, srcChain?.ecosystem, srcValue, dstValue]);
|
|
36
|
+
|
|
27
37
|
return (
|
|
28
38
|
<div className="flex flex-col gap-5 text-t_text_primary justify-center items-center">
|
|
29
39
|
{txError ? (
|
|
@@ -63,7 +73,7 @@ export const TxResult = ({ onCloseClick }: Props) => {
|
|
|
63
73
|
<div className="-rotate-90 flex justify-center w-8 fill-t_text_primary opacity-50">
|
|
64
74
|
<ArrowDownLongIcon />
|
|
65
75
|
</div>
|
|
66
|
-
<TokenItem chain={dstChain} token={dstToken} value={
|
|
76
|
+
<TokenItem chain={dstChain} token={dstToken} value={value} />
|
|
67
77
|
</div>
|
|
68
78
|
)}
|
|
69
79
|
<a
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { useSwapContext } from "@src/context";
|
|
2
2
|
import { weiToHumanReadable } from "@src/utils";
|
|
3
|
-
import { FC } from "react";
|
|
4
3
|
import { Fee } from "../Fee";
|
|
5
4
|
|
|
6
|
-
export const Fees
|
|
5
|
+
export const Fees = ({
|
|
6
|
+
solanaBridgeFee,
|
|
7
|
+
}: {
|
|
8
|
+
solanaBridgeFee: string | null;
|
|
9
|
+
}) => {
|
|
7
10
|
const { route, slippage, feeToken, dstToken, expressDelivery } =
|
|
8
11
|
useSwapContext();
|
|
9
12
|
|
|
@@ -11,11 +14,13 @@ export const Fees: FC = () => {
|
|
|
11
14
|
{
|
|
12
15
|
name: "CCIP Fee: ",
|
|
13
16
|
value: `${
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
solanaBridgeFee
|
|
18
|
+
? solanaBridgeFee
|
|
19
|
+
: weiToHumanReadable({
|
|
20
|
+
amount: route?.xSwapFees.ccipFee || "0",
|
|
21
|
+
decimals: feeToken?.decimals || 18,
|
|
22
|
+
precisionFractionalPlaces: 5,
|
|
23
|
+
}) || 0
|
|
19
24
|
} ${feeToken?.symbol || ""}`,
|
|
20
25
|
},
|
|
21
26
|
{
|
|
@@ -2,15 +2,156 @@ import { ArrowDownIcon, TokensIcon } from "@src/assets/icons";
|
|
|
2
2
|
import { Skeleton } from "@src/components";
|
|
3
3
|
import { useSwapContext } from "@src/context";
|
|
4
4
|
import { safeBigNumberFrom, weiToHumanReadable } from "@src/utils";
|
|
5
|
-
import { useMemo, useState } from "react";
|
|
5
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6
6
|
import { DeliveryInfo } from "./DeliveryInfo";
|
|
7
7
|
import { Fees } from "./Fees";
|
|
8
|
+
import { Ecosystem } from "@src/models";
|
|
9
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
10
|
+
import {
|
|
11
|
+
CCIP_ROUTER_PROGRAM_ID,
|
|
12
|
+
FEE_QUOTER_PROGRAM_ID,
|
|
13
|
+
LINK_TOKEN_MINT,
|
|
14
|
+
PROGRAM_ID,
|
|
15
|
+
RMN_REMOTE_PROGRAM_ID,
|
|
16
|
+
SOLANA_MAINNET_RPC_URL,
|
|
17
|
+
SYSTEM_PROGRAM_ID,
|
|
18
|
+
} from "@src/constants";
|
|
19
|
+
import {
|
|
20
|
+
AddressConversion,
|
|
21
|
+
CCIPClient,
|
|
22
|
+
CCIPContext,
|
|
23
|
+
CCIPCoreConfig,
|
|
24
|
+
CCIPProvider,
|
|
25
|
+
} from "@src/services/svm";
|
|
26
|
+
import { BN } from "@coral-xyz/anchor";
|
|
27
|
+
import { useWallet } from "@solana/wallet-adapter-react";
|
|
28
|
+
import useNetworks from "@src/hooks/networkManagement/useNetworks";
|
|
8
29
|
|
|
9
30
|
export const FeesPanel = () => {
|
|
10
31
|
const [expanded, setExpanded] = useState(false);
|
|
32
|
+
const [isFetchingSolanaFee, setIsFetchingSolanaFee] = useState(false);
|
|
33
|
+
const [solanaBridgeFee, setSolanaBridgeFee] = useState<string | null>(null);
|
|
11
34
|
|
|
12
|
-
const {
|
|
13
|
-
|
|
35
|
+
const {
|
|
36
|
+
route,
|
|
37
|
+
feeToken,
|
|
38
|
+
isFetchingRoute,
|
|
39
|
+
srcChain,
|
|
40
|
+
dstChain,
|
|
41
|
+
srcToken,
|
|
42
|
+
srcValueWei,
|
|
43
|
+
bridgeUI,
|
|
44
|
+
} = useSwapContext();
|
|
45
|
+
|
|
46
|
+
const { publicKey } = useWallet();
|
|
47
|
+
const {
|
|
48
|
+
networks: {
|
|
49
|
+
evm: { address: evmAddress },
|
|
50
|
+
},
|
|
51
|
+
} = useNetworks();
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const fetchSolanaBridgeFee = async () => {
|
|
55
|
+
if (
|
|
56
|
+
bridgeUI &&
|
|
57
|
+
srcChain?.ecosystem === Ecosystem.SOLANA &&
|
|
58
|
+
dstChain?.ccipChainId &&
|
|
59
|
+
srcToken &&
|
|
60
|
+
srcValueWei &&
|
|
61
|
+
publicKey &&
|
|
62
|
+
evmAddress
|
|
63
|
+
) {
|
|
64
|
+
try {
|
|
65
|
+
setIsFetchingSolanaFee(true);
|
|
66
|
+
|
|
67
|
+
const connection = new Connection(
|
|
68
|
+
SOLANA_MAINNET_RPC_URL,
|
|
69
|
+
"confirmed",
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const provider: CCIPProvider = {
|
|
73
|
+
connection,
|
|
74
|
+
wallet: null,
|
|
75
|
+
getAddress() {
|
|
76
|
+
return publicKey;
|
|
77
|
+
},
|
|
78
|
+
async signTransaction(tx) {
|
|
79
|
+
return tx;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const config: CCIPCoreConfig = {
|
|
84
|
+
ccipRouterProgramId: CCIP_ROUTER_PROGRAM_ID,
|
|
85
|
+
feeQuoterProgramId: FEE_QUOTER_PROGRAM_ID,
|
|
86
|
+
tokenMint: new PublicKey(srcToken.address),
|
|
87
|
+
rmnRemoteProgramId: RMN_REMOTE_PROGRAM_ID,
|
|
88
|
+
linkTokenMint: LINK_TOKEN_MINT,
|
|
89
|
+
nativeSol: PublicKey.default,
|
|
90
|
+
systemProgramId: SYSTEM_PROGRAM_ID,
|
|
91
|
+
programId: PROGRAM_ID,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const context: CCIPContext = {
|
|
95
|
+
provider,
|
|
96
|
+
config,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const client = new CCIPClient(context);
|
|
100
|
+
|
|
101
|
+
const extraArgs = client.createExtraArgs({
|
|
102
|
+
allowOutOfOrderExecution: true,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const receiver =
|
|
106
|
+
AddressConversion.evmAddressToSolanaBytes(evmAddress);
|
|
107
|
+
|
|
108
|
+
const feeRequest = {
|
|
109
|
+
destChainSelector: new BN(dstChain.ccipChainId),
|
|
110
|
+
message: {
|
|
111
|
+
receiver: receiver,
|
|
112
|
+
data: new Uint8Array(0),
|
|
113
|
+
tokenAmounts: [
|
|
114
|
+
{
|
|
115
|
+
token: new PublicKey(srcToken.address),
|
|
116
|
+
amount: new BN(srcValueWei.toString()),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
feeToken: PublicKey.default,
|
|
120
|
+
extraArgs: extraArgs,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const feeResult = await client.getFee(feeRequest);
|
|
125
|
+
const feeInLamports = feeResult.amount.toString();
|
|
126
|
+
setSolanaBridgeFee(feeInLamports);
|
|
127
|
+
|
|
128
|
+
const feeInSol = weiToHumanReadable({
|
|
129
|
+
amount: feeInLamports,
|
|
130
|
+
decimals: 9,
|
|
131
|
+
precisionFractionalPlaces: 5,
|
|
132
|
+
});
|
|
133
|
+
setSolanaBridgeFee(feeInSol);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
setSolanaBridgeFee(null);
|
|
136
|
+
} finally {
|
|
137
|
+
setIsFetchingSolanaFee(false);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
setSolanaBridgeFee(null);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
fetchSolanaBridgeFee();
|
|
145
|
+
}, [
|
|
146
|
+
bridgeUI,
|
|
147
|
+
srcChain?.ecosystem,
|
|
148
|
+
dstChain?.ccipChainId,
|
|
149
|
+
srcToken?.address,
|
|
150
|
+
srcValueWei,
|
|
151
|
+
publicKey,
|
|
152
|
+
evmAddress,
|
|
153
|
+
srcToken,
|
|
154
|
+
]);
|
|
14
155
|
|
|
15
156
|
const totalFee = useMemo(() => {
|
|
16
157
|
if (!route) {
|
|
@@ -28,6 +169,12 @@ export const FeesPanel = () => {
|
|
|
28
169
|
});
|
|
29
170
|
}, [feeToken?.decimals, route]);
|
|
30
171
|
|
|
172
|
+
const isLoading =
|
|
173
|
+
isFetchingRoute ||
|
|
174
|
+
(srcChain?.ecosystem === Ecosystem.SOLANA &&
|
|
175
|
+
bridgeUI &&
|
|
176
|
+
isFetchingSolanaFee);
|
|
177
|
+
|
|
31
178
|
return (
|
|
32
179
|
<div className="flex flex-col x-border rounded-xl py-2 px-4 bg-t_bg_tertiary bg-opacity-5">
|
|
33
180
|
<button
|
|
@@ -39,16 +186,18 @@ export const FeesPanel = () => {
|
|
|
39
186
|
<div className="flex gap-2 items-center text-xs font-medium sm:text-sm fill-t_text_primary">
|
|
40
187
|
<TokensIcon />
|
|
41
188
|
<p className="opacity-60">Fee: </p>
|
|
42
|
-
{
|
|
189
|
+
{isLoading ? (
|
|
43
190
|
<Skeleton className="w-[100px] h-full" />
|
|
44
191
|
) : (
|
|
45
|
-
<p>{`${totalFee} ${
|
|
192
|
+
<p>{`${solanaBridgeFee ? solanaBridgeFee : totalFee} ${
|
|
193
|
+
feeToken?.symbol || ""
|
|
194
|
+
}`}</p>
|
|
46
195
|
)}
|
|
47
196
|
</div>
|
|
48
197
|
<div className="flex gap-2 items-center fill-t_text_primary">
|
|
49
|
-
{route &&
|
|
50
|
-
|
|
51
|
-
|
|
198
|
+
{route && !isLoading && srcChain?.chainId !== dstChain?.chainId && (
|
|
199
|
+
<DeliveryInfo />
|
|
200
|
+
)}
|
|
52
201
|
{expanded ? (
|
|
53
202
|
<div className="w-4 h-2 rotate-180">
|
|
54
203
|
<ArrowDownIcon />
|
|
@@ -60,7 +209,7 @@ export const FeesPanel = () => {
|
|
|
60
209
|
)}
|
|
61
210
|
</div>
|
|
62
211
|
</div>
|
|
63
|
-
{expanded && <Fees />}
|
|
212
|
+
{expanded && <Fees solanaBridgeFee={solanaBridgeFee} />}
|
|
64
213
|
</button>
|
|
65
214
|
</div>
|
|
66
215
|
);
|