@xswap-link/sdk 0.10.6 → 0.10.7

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.
Files changed (46) hide show
  1. package/.github/workflows/main.yml +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/dist/index.d.mts +39 -8
  4. package/dist/index.d.ts +39 -8
  5. package/dist/index.global.js +827 -440
  6. package/dist/index.js +9 -9
  7. package/dist/index.mjs +9 -9
  8. package/package.json +12 -1
  9. package/src/components/AllWalletsConfig/index.tsx +40 -0
  10. package/src/components/Swap/HistoryView/index.tsx +28 -5
  11. package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +73 -47
  12. package/src/components/Swap/SwapView/SwapButton/index.tsx +45 -10
  13. package/src/components/Swap/SwapView/SwapPanel/TokenPanel/TokenPicker/index.tsx +123 -4
  14. package/src/components/Swap/SwapView/SwapPanel/WalletPanel/index.tsx +71 -0
  15. package/src/components/Swap/SwapView/SwapPanel/index.tsx +4 -0
  16. package/src/components/Swap/SwapView/WalletPicker/index.tsx +63 -28
  17. package/src/components/Swap/index.tsx +2 -2
  18. package/src/components/TxConfigForm/index.tsx +9 -34
  19. package/src/components/TxDataCard/TxDataCardUI/index.tsx +16 -10
  20. package/src/components/TxDataCard/index.tsx +1 -0
  21. package/src/components/index.ts +1 -0
  22. package/src/config/wagmiConfig.ts +1 -6
  23. package/src/constants/index.ts +14 -0
  24. package/src/context/HistoryProvider.tsx +121 -25
  25. package/src/context/SwapProvider.tsx +104 -48
  26. package/src/context/TransactionProvider.tsx +60 -1
  27. package/src/context/TxUIWrapper.tsx +187 -71
  28. package/src/context/WalletProvider.tsx +108 -0
  29. package/src/contracts/addresses.ts +4 -0
  30. package/src/contracts/idl/jupiter.ts +2879 -0
  31. package/src/models/Ecosystem.ts +1 -0
  32. package/src/models/Route.ts +14 -3
  33. package/src/models/SolanaTransaction.ts +38 -0
  34. package/src/models/SolanaWeb3Network.ts +5 -0
  35. package/src/models/TxUIWrapper.ts +8 -4
  36. package/src/models/index.ts +1 -0
  37. package/src/models/payloads/GetBalancesPayload.ts +2 -1
  38. package/src/models/payloads/GetSolanaRoutePayload.ts +8 -0
  39. package/src/models/payloads/index.ts +1 -0
  40. package/src/services/api.ts +21 -3
  41. package/src/services/solana/jupiter/extract-swap-data.ts +183 -0
  42. package/src/services/solana/jupiter/get-events.ts +37 -0
  43. package/src/services/solana/jupiter/instruction-parser.ts +217 -0
  44. package/src/services/solana/jupiter/utils.ts +37 -0
  45. package/src/utils/strings.ts +16 -0
  46. package/test/context/SwapProvider.test.tsx +6 -9
@@ -1,3 +1,4 @@
1
1
  export enum Ecosystem {
2
2
  EVM = "evm",
3
+ SOLANA = "solana",
3
4
  }
@@ -1,10 +1,21 @@
1
- export type Route = {
1
+ interface BaseRoute {
2
2
  estAmountOut: string;
3
3
  minAmountOut: string;
4
- transactions: Transactions;
5
4
  xSwapFees: XSwapFees;
6
5
  message?: string;
7
- };
6
+ }
7
+
8
+ export interface EVMRoute extends BaseRoute {
9
+ ecosystem: "evm";
10
+ transactions: Transactions;
11
+ }
12
+
13
+ export interface SolanaRoute extends BaseRoute {
14
+ ecosystem: "solana";
15
+ swapTransaction: string;
16
+ }
17
+
18
+ export type UnifiedRoute = EVMRoute | SolanaRoute;
8
19
 
9
20
  export type Transactions = {
10
21
  approve?: TransactionRequest;
@@ -0,0 +1,38 @@
1
+ import { IdlEvents, IdlTypes } from "@coral-xyz/anchor";
2
+ import { ParsedInstruction, PublicKey } from "@solana/web3.js";
3
+ import { Jupiter } from "@src/contracts/idl/jupiter";
4
+
5
+ export type SwapEvent = IdlEvents<Jupiter>["SwapEvent"];
6
+ export type FeeEvent = IdlEvents<Jupiter>["FeeEvent"];
7
+ type RoutePlanStep = IdlTypes<Jupiter>["RoutePlanStep"];
8
+ export type RoutePlan = RoutePlanStep[];
9
+
10
+ export interface PartialInstruction {
11
+ programId: PublicKey;
12
+ data: string /** Expecting base58 */;
13
+ accounts: PublicKey[];
14
+ }
15
+
16
+ // Subset of @solana/web3.js ParsedTransactionWithMeta to allow flexible upstream data
17
+ export interface TransactionWithMeta {
18
+ blockTime: number;
19
+ meta: {
20
+ status: {
21
+ Ok: unknown;
22
+ };
23
+ logMessages?: string[] | null;
24
+ innerInstructions?:
25
+ | {
26
+ index: number;
27
+ instructions: (ParsedInstruction | PartialInstruction)[];
28
+ }[]
29
+ | null;
30
+ } | null;
31
+ transaction: {
32
+ signatures: string[];
33
+ message: {
34
+ accountKeys: { pubkey: PublicKey }[];
35
+ instructions: (ParsedInstruction | PartialInstruction)[];
36
+ };
37
+ };
38
+ }
@@ -0,0 +1,5 @@
1
+ export enum SolanaWeb3Network {
2
+ DEVNET = "devnet",
3
+ TESTNET = "testnet",
4
+ MAINNET = "mainnet-beta",
5
+ }
@@ -1,14 +1,18 @@
1
- import { TransactionResponse } from "@ethersproject/providers";
2
- import { EvmHandlers } from "@src/context";
1
+ import { TransactionResponse as EvmTransactionResponse } from "@ethersproject/providers";
2
+ import { VersionedTransaction as SolanaVersionedTransaction } from "@solana/web3.js";
3
+ import { EvmHandlers, SolanaHandlers } from "@src/context";
3
4
  import { Ecosystem } from "@src/models";
4
5
  import { Dispatch, SetStateAction } from "react";
5
6
 
6
7
  export type EnqueueTxProps = {
7
8
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
- executeTransaction: () => Promise<TransactionResponse> | undefined;
9
+ executeTransaction: () =>
10
+ | Promise<EvmTransactionResponse | undefined>
11
+ | Promise<SolanaVersionedTransaction | undefined>
12
+ | undefined;
9
13
  txStatus: TxStatus;
10
14
  txMsg?: string;
11
- handlers?: EvmHandlers;
15
+ handlers?: EvmHandlers | SolanaHandlers;
12
16
  network?: Ecosystem;
13
17
  showDefaultSuccessMessage?: boolean;
14
18
  };
@@ -11,6 +11,7 @@ export * from "./integrations";
11
11
  export * from "./payloads";
12
12
  export * from "./Protocol";
13
13
  export * from "./Route";
14
+ export * from "./SolanaWeb3Network";
14
15
  export * from "./TokenData";
15
16
  export * from "./TransactionHistory";
16
17
  export * from "./TxUIWrapper";
@@ -1,3 +1,4 @@
1
1
  export type GetTokenBalancesPayload = {
2
- walletAddress: string;
2
+ walletAddress?: string;
3
+ solanaWalletAddress?: string;
3
4
  };
@@ -0,0 +1,8 @@
1
+ export type GetSolanaRoutePayload = {
2
+ integratorId: string;
3
+ fromToken: string;
4
+ toToken: string;
5
+ fromAmount: string;
6
+ fromAddress: string;
7
+ slippage: number;
8
+ };
@@ -2,5 +2,6 @@ export * from "./GetBalancesPayload";
2
2
  export * from "./GetLeaderboardChangePayload";
3
3
  export * from "./GetPricesPayload";
4
4
  export * from "./GetRoutePayload";
5
+ export * from "./GetSolanaRoutePayload";
5
6
  export * from "./ModalIntegrationPayload";
6
7
  export * from "./WidgetIntegrationPayload";
@@ -5,8 +5,10 @@ import {
5
5
  GetLeaderboardChangePayload,
6
6
  GetPricesPayload,
7
7
  GetRoutePayload,
8
+ GetSolanaRoutePayload,
8
9
  GetTokenBalancesPayload,
9
- Route,
10
+ EVMRoute,
11
+ SolanaRoute,
10
12
  Token,
11
13
  TokenPrices,
12
14
  TransactionHistory,
@@ -50,8 +52,24 @@ async function _sendRequest<T>(
50
52
  export async function getRoute(
51
53
  payload: GetRoutePayload,
52
54
  abortSignal?: AbortSignal,
53
- ): Promise<Route> {
54
- return _sendRequest<Route>("/route", {
55
+ ): Promise<EVMRoute> {
56
+ return _sendRequest<EVMRoute>("/route", {
57
+ method: "POST",
58
+ body: JSON.stringify(payload),
59
+ signal: abortSignal,
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Get solana swap route based on provided criteria.
65
+ * @param payload required
66
+ * @param abortSignal
67
+ */
68
+ export async function getSolanaRoute(
69
+ payload: GetSolanaRoutePayload,
70
+ abortSignal?: AbortSignal,
71
+ ): Promise<SolanaRoute> {
72
+ return _sendRequest<SolanaRoute>("/solanaRoute", {
55
73
  method: "POST",
56
74
  body: JSON.stringify(payload),
57
75
  signal: abortSignal,
@@ -0,0 +1,183 @@
1
+ /**
2
+ * This file contains code adapted from the Jupiter Protocol's instruction parser
3
+ * Source: https://github.com/jup-ag/instruction-parser
4
+ * Modified for use in XSwap
5
+ */
6
+ import { Event, Program, Provider } from "@coral-xyz/anchor";
7
+ import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
8
+ import { JUPITER_V6_PROGRAM_ID } from "@src/constants";
9
+ import { IDL, Jupiter } from "@src/contracts/idl/jupiter";
10
+ import { SwapEvent, TransactionWithMeta } from "@src/models/SolanaTransaction";
11
+ import { getEvents } from "@src/services/solana/jupiter/get-events";
12
+ import { InstructionParser } from "@src/services/solana/jupiter/instruction-parser";
13
+ import { BigNumberUtil } from "@src/services/solana/jupiter/utils";
14
+ import BigNumber from "bignumber.js";
15
+ import { unpackMint } from "@solana/spl-token";
16
+ import { Transaction } from "@src/models";
17
+
18
+ export const program = new Program<Jupiter>(
19
+ IDL,
20
+ JUPITER_V6_PROGRAM_ID,
21
+ {} as Provider,
22
+ );
23
+
24
+ type AccountInfoMap = Map<string, AccountInfo<Buffer>>;
25
+
26
+ export type SwapAttributes = Transaction;
27
+
28
+ const reduceEventData = <T>(events: Event[], name: string) =>
29
+ events.reduce((acc, event) => {
30
+ if (event.name === name) {
31
+ acc.push(event.data as T);
32
+ }
33
+ return acc;
34
+ }, new Array<T>());
35
+
36
+ export async function extractSolanaSwapData(
37
+ signature: string,
38
+ connection: Connection,
39
+ tx: TransactionWithMeta,
40
+ ): Promise<SwapAttributes | undefined> {
41
+ const programId = JUPITER_V6_PROGRAM_ID;
42
+ const accountInfosMap: AccountInfoMap = new Map();
43
+
44
+ const logMessages = tx.meta?.logMessages;
45
+ if (!logMessages) {
46
+ throw new Error("Missing log messages...");
47
+ }
48
+
49
+ const parser = new InstructionParser(programId);
50
+ const events = getEvents(program, tx);
51
+
52
+ const swapEvents = reduceEventData<SwapEvent>(events, "SwapEvent");
53
+
54
+ if (swapEvents.length === 0) {
55
+ // Not a swap event, for example: https://solscan.io/tx/5ZSozCHmAFmANaqyjRj614zxQY8HDXKyfAs2aAVjZaadS4DbDwVq8cTbxmM5m5VzDcfhysTSqZgKGV1j2A2Hqz1V
56
+ return;
57
+ }
58
+
59
+ const accountsToBeFetched = new Array<PublicKey>();
60
+ swapEvents.forEach((swapEvent) => {
61
+ accountsToBeFetched.push(swapEvent.inputMint);
62
+ accountsToBeFetched.push(swapEvent.outputMint);
63
+ });
64
+
65
+ const accountInfos = await connection.getMultipleAccountsInfo(
66
+ accountsToBeFetched,
67
+ );
68
+ accountsToBeFetched.forEach((account, index) => {
69
+ const accountInfo = accountInfos[index];
70
+ if (accountInfo) {
71
+ accountInfosMap.set(account.toBase58(), accountInfo);
72
+ }
73
+ });
74
+
75
+ const swapData = await parseSwapEvents(accountInfosMap, swapEvents);
76
+ const instructions = parser.getInstructions(tx);
77
+ const positions = parser.getInitialAndFinalSwapPositions(instructions);
78
+ if (!positions) {
79
+ throw new Error("Failed to get initial and final swap positions");
80
+ }
81
+
82
+ const [initialPositions, finalPositions] = positions as [number[], number[]];
83
+ const initialPosition = initialPositions[0] as number;
84
+ const finalPosition = finalPositions[0] as number;
85
+
86
+ const inMint = swapData[initialPosition]?.inMint as string;
87
+ const inSwapData = swapData.filter(
88
+ (swap, index) =>
89
+ initialPositions?.includes(index) && swap.inMint === inMint,
90
+ );
91
+
92
+ const inAmountInDecimal = inSwapData.reduce((acc, curr) => {
93
+ return acc.plus(curr.inAmountInDecimal || 0);
94
+ }, new BigNumber(0));
95
+
96
+ const outMint = swapData[finalPosition]?.outMint;
97
+ const outSwapData = swapData.filter(
98
+ (swap, index) =>
99
+ finalPositions?.includes(index) && swap.outMint === outMint,
100
+ );
101
+
102
+ const outAmountInDecimal = outSwapData.reduce((acc, curr) => {
103
+ return acc.plus(curr.outAmountInDecimal || 0);
104
+ }, new BigNumber(0));
105
+
106
+ const isSuccessful = tx.meta?.status?.Ok === null;
107
+
108
+ const swap = {} as SwapAttributes;
109
+
110
+ swap.hash = signature;
111
+ swap.timestamp = tx.blockTime ?? 0;
112
+
113
+ swap.amountWei = inAmountInDecimal.toString();
114
+ swap.tokenAddress = inMint;
115
+
116
+ swap.tokenOutAmount = outAmountInDecimal.toString();
117
+ swap.tokenOutAddress = outMint;
118
+
119
+ swap.sourceChainId = "mainnet-beta";
120
+ swap.targetChainId = "mainnet-beta";
121
+
122
+ swap.status = isSuccessful ? "DONE" : "REVERTED";
123
+
124
+ return swap;
125
+ }
126
+
127
+ async function parseSwapEvents(
128
+ accountInfosMap: AccountInfoMap,
129
+ swapEvents: SwapEvent[],
130
+ ) {
131
+ const swapData = await Promise.all(
132
+ swapEvents.map((swapEvent) => extractSwapData(accountInfosMap, swapEvent)),
133
+ );
134
+
135
+ return swapData;
136
+ }
137
+
138
+ async function extractSwapData(
139
+ accountInfosMap: AccountInfoMap,
140
+ swapEvent: SwapEvent,
141
+ ) {
142
+ const inMint = swapEvent.inputMint.toBase58();
143
+ const inAmount = swapEvent.inputAmount.toString();
144
+ const inTokenDecimals = extractMintDecimals(
145
+ accountInfosMap,
146
+ swapEvent.inputMint,
147
+ );
148
+ const inAmountInDecimal = BigNumberUtil.fromBN(
149
+ swapEvent.inputAmount,
150
+ inTokenDecimals,
151
+ );
152
+
153
+ const outMint = swapEvent.outputMint.toBase58();
154
+ const outAmount = swapEvent.outputAmount.toString();
155
+ const outTokenDecimals = extractMintDecimals(
156
+ accountInfosMap,
157
+ swapEvent.outputMint,
158
+ );
159
+ const outAmountInDecimal = BigNumberUtil.fromBN(
160
+ swapEvent.outputAmount,
161
+ outTokenDecimals,
162
+ );
163
+
164
+ return {
165
+ inMint,
166
+ inAmount,
167
+ inAmountInDecimal,
168
+ outMint,
169
+ outAmount,
170
+ outAmountInDecimal,
171
+ };
172
+ }
173
+
174
+ function extractMintDecimals(accountInfosMap: AccountInfoMap, mint: PublicKey) {
175
+ const mintData = accountInfosMap.get(mint.toBase58());
176
+
177
+ if (mintData) {
178
+ const mintInfo = unpackMint(mint, mintData, mintData.owner);
179
+ return mintInfo.decimals;
180
+ }
181
+
182
+ return;
183
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * This file contains code adapted from the Jupiter Protocol's instruction parser
3
+ * Source: https://github.com/jup-ag/instruction-parser
4
+ * Modified for use in XSwap
5
+ */
6
+ import { Event, Program, utils } from "@coral-xyz/anchor";
7
+ import { JUPITER_V6_PROGRAM_ID } from "@src/constants";
8
+ import { Jupiter } from "@src/contracts/idl/jupiter";
9
+ import { TransactionWithMeta } from "@src/models/SolanaTransaction";
10
+
11
+ export function getEvents(
12
+ program: Program<Jupiter>,
13
+ transactionResponse: TransactionWithMeta,
14
+ ) {
15
+ const events: Event[] = [];
16
+
17
+ if (transactionResponse && transactionResponse.meta) {
18
+ const { meta } = transactionResponse;
19
+
20
+ meta.innerInstructions?.map(async (ix) => {
21
+ ix.instructions.map(async (iix) => {
22
+ if (!iix.programId.equals(JUPITER_V6_PROGRAM_ID)) return;
23
+ if (!("data" in iix)) return; // Guard in case it is a parsed decoded instruction
24
+
25
+ const ixData = utils.bytes.bs58.decode(iix.data);
26
+ const eventData = utils.bytes.base64.encode(ixData.subarray(8));
27
+ const event = program.coder.events.decode(eventData);
28
+
29
+ if (!event) return;
30
+
31
+ events.push(event);
32
+ });
33
+ });
34
+ }
35
+
36
+ return events;
37
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * This file contains code adapted from the Jupiter Protocol's instruction parser
3
+ * Source: https://github.com/jup-ag/instruction-parser
4
+ * Modified for use in XSwap
5
+ */
6
+ /* eslint-disable @typescript-eslint/no-explicit-any */
7
+ import { ParsedInstruction, PublicKey } from "@solana/web3.js";
8
+ import { BorshCoder } from "@coral-xyz/anchor";
9
+ import { IDL } from "@src/contracts/idl/jupiter";
10
+ import {
11
+ PartialInstruction,
12
+ RoutePlan,
13
+ TransactionWithMeta,
14
+ } from "@src/models/SolanaTransaction";
15
+
16
+ export class InstructionParser {
17
+ private coder: BorshCoder;
18
+ private programId: PublicKey;
19
+
20
+ constructor(programId: PublicKey) {
21
+ this.programId = programId;
22
+ this.coder = new BorshCoder(IDL);
23
+ }
24
+
25
+ getInstructionNameAndTransferAuthorityAndLastAccount(
26
+ instructions: PartialInstruction[],
27
+ ) {
28
+ for (const instruction of instructions) {
29
+ if (!instruction.programId.equals(this.programId)) {
30
+ continue;
31
+ }
32
+
33
+ const ix = this.coder.instruction.decode(instruction.data, "base58");
34
+
35
+ if (ix && instruction && this.isRouting(ix.name)) {
36
+ const instructionName = ix.name;
37
+ const authorityIndex = this.getTransferAuthorityIndex(instructionName);
38
+ if (authorityIndex === undefined) {
39
+ continue;
40
+ }
41
+ const transferAuthority =
42
+ instruction.accounts[authorityIndex]?.toString() || "";
43
+ const lastAccount =
44
+ instruction.accounts[instruction.accounts.length - 1]?.toString() ||
45
+ "";
46
+
47
+ return [ix.name, transferAuthority, lastAccount];
48
+ }
49
+ }
50
+
51
+ return [];
52
+ }
53
+
54
+ getTransferAuthorityIndex(instructionName: string) {
55
+ switch (instructionName) {
56
+ case "route":
57
+ case "exactOutRoute":
58
+ case "routeWithTokenLedger":
59
+ return 1;
60
+ case "sharedAccountsRoute":
61
+ case "sharedAccountsRouteWithTokenLedger":
62
+ case "sharedAccountsExactOutRoute":
63
+ return 2;
64
+ }
65
+ }
66
+
67
+ getInstructions(tx: TransactionWithMeta): PartialInstruction[] {
68
+ const parsedInstructions: PartialInstruction[] = [];
69
+ for (const instruction of tx.transaction.message.instructions) {
70
+ if (instruction.programId.equals(this.programId)) {
71
+ parsedInstructions.push(instruction as never);
72
+ }
73
+ }
74
+
75
+ for (const instructions of tx.meta?.innerInstructions || []) {
76
+ for (const instruction of instructions.instructions) {
77
+ if (instruction.programId.equals(this.programId)) {
78
+ parsedInstructions.push(instruction as any);
79
+ }
80
+ }
81
+ }
82
+
83
+ return parsedInstructions;
84
+ }
85
+
86
+ // Extract the position of the initial and final swap from the swap array.
87
+ getInitialAndFinalSwapPositions(instructions: PartialInstruction[]) {
88
+ for (const instruction of instructions) {
89
+ if (!instruction.programId.equals(this.programId)) {
90
+ continue;
91
+ }
92
+
93
+ const ix = this.coder.instruction.decode(instruction.data, "base58");
94
+ // This will happen because now event is also an CPI instruction.
95
+ if (!ix) {
96
+ continue;
97
+ }
98
+
99
+ if (this.isRouting(ix.name)) {
100
+ const routePlan = (ix.data as any).routePlan as RoutePlan;
101
+ const inputIndex = 0;
102
+ const outputIndex = routePlan.length;
103
+
104
+ const initialPositions: number[] = [];
105
+ for (let j = 0; j < routePlan.length; j++) {
106
+ if (routePlan[j].inputIndex === inputIndex) {
107
+ initialPositions.push(j);
108
+ }
109
+ }
110
+
111
+ const finalPositions: number[] = [];
112
+ for (let j = 0; j < routePlan.length; j++) {
113
+ if (routePlan[j].outputIndex === outputIndex) {
114
+ finalPositions.push(j);
115
+ }
116
+ }
117
+
118
+ if (
119
+ finalPositions.length === 0 &&
120
+ this.isCircular((ix.data as any).routePlan)
121
+ ) {
122
+ for (let j = 0; j < (ix.data as any).routePlan.length; j++) {
123
+ if ((ix.data as any).routePlan[j].outputIndex === 0) {
124
+ finalPositions.push(j);
125
+ }
126
+ }
127
+ }
128
+
129
+ return [initialPositions, finalPositions];
130
+ }
131
+ }
132
+ }
133
+
134
+ getExactOutAmount(instructions: (ParsedInstruction | PartialInstruction)[]) {
135
+ for (const instruction of instructions) {
136
+ if (!instruction.programId.equals(this.programId)) {
137
+ continue;
138
+ }
139
+ if (!("data" in instruction)) continue; // Guard in case it is a parsed decoded instruction, should be impossible
140
+
141
+ const ix = this.coder.instruction.decode(instruction.data, "base58");
142
+
143
+ if (ix && this.isExactIn(ix.name)) {
144
+ return (ix.data as any).quotedOutAmount.toString();
145
+ }
146
+ }
147
+
148
+ return;
149
+ }
150
+
151
+ getExactInAmount(instructions: (ParsedInstruction | PartialInstruction)[]) {
152
+ for (const instruction of instructions) {
153
+ if (!instruction.programId.equals(this.programId)) {
154
+ continue;
155
+ }
156
+ if (!("data" in instruction)) continue; // Guard in case it is a parsed decoded instruction, should be impossible
157
+
158
+ const ix = this.coder.instruction.decode(instruction.data, "base58");
159
+
160
+ if (ix && this.isExactOut(ix.name)) {
161
+ return (ix.data as any).quotedInAmount.toString();
162
+ }
163
+ }
164
+
165
+ return;
166
+ }
167
+
168
+ isExactIn(name: string) {
169
+ return (
170
+ name === "route" ||
171
+ name === "routeWithTokenLedger" ||
172
+ name === "sharedAccountsRoute" ||
173
+ name === "sharedAccountsRouteWithTokenLedger"
174
+ );
175
+ }
176
+
177
+ isExactOut(name: string) {
178
+ return name === "sharedAccountsExactOutRoute" || name === "exactOutRoute";
179
+ }
180
+
181
+ isRouting(name: string) {
182
+ return (
183
+ name === "route" ||
184
+ name === "routeWithTokenLedger" ||
185
+ name === "sharedAccountsRoute" ||
186
+ name === "sharedAccountsRouteWithTokenLedger" ||
187
+ name === "sharedAccountsExactOutRoute" ||
188
+ name === "exactOutRoute"
189
+ );
190
+ }
191
+
192
+ isCircular(routePlan: RoutePlan) {
193
+ if (!routePlan || routePlan.length === 0) {
194
+ return false;
195
+ }
196
+
197
+ const indexMap = new Map(
198
+ routePlan.map((obj) => [obj.inputIndex, obj.outputIndex]),
199
+ );
200
+ const visited = new Set();
201
+ let currentIndex = routePlan[0].inputIndex;
202
+
203
+ while (!visited.has(currentIndex)) {
204
+ if (visited.has(currentIndex)) {
205
+ return currentIndex === routePlan[0].inputIndex;
206
+ }
207
+
208
+ visited.add(currentIndex);
209
+
210
+ if (!indexMap.has(currentIndex)) {
211
+ return false;
212
+ }
213
+
214
+ currentIndex = indexMap.get(currentIndex);
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,37 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { BN } from "@coral-xyz/anchor";
3
+ import { ParsedTransactionWithMeta } from "@solana/web3.js";
4
+
5
+ export class BigNumberUtil {
6
+ public static fromBigInt(input: bigint, shift = 0): BigNumber {
7
+ return new BigNumber(input.toString()).div(new BigNumber(10).pow(shift));
8
+ }
9
+
10
+ public static fromBN(input: BN, shift = 0): BigNumber {
11
+ return new BigNumber(input.toString()).div(new BigNumber(10).pow(shift));
12
+ }
13
+ }
14
+
15
+ const JUPITER_PROGRAM_IDS = [
16
+ "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4",
17
+ "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB",
18
+ "JUP2jxvXaqu7NQY1GmNF4m1vodw12LVXYxbFL2uJvfo",
19
+ "JUP3c2Uh3WA4Ng34tw6kPd2G4C5BB21Xo36Je1s32Ph",
20
+ ];
21
+
22
+ export const isJupiterTransaction = (
23
+ tx: ParsedTransactionWithMeta,
24
+ ): boolean => {
25
+ if (!tx || !tx.transaction || !tx.transaction.message) return false;
26
+
27
+ for (const innerInstructionSet of tx.meta?.innerInstructions || []) {
28
+ for (const instruction of innerInstructionSet.instructions) {
29
+ const programId = instruction.programId?.toString();
30
+ if (programId && JUPITER_PROGRAM_IDS.includes(programId)) {
31
+ return true;
32
+ }
33
+ }
34
+ }
35
+
36
+ return false;
37
+ };
@@ -1,3 +1,5 @@
1
+ import { PublicKey } from "@solana/web3.js";
2
+
1
3
  export const shortAddress = (address: string): string => {
2
4
  return `${address?.substring(0, 5)}...${address?.substring(
3
5
  address.length - 5,
@@ -8,3 +10,17 @@ export const shortAddress = (address: string): string => {
8
10
  export const isETHAddressValid = (address: string): boolean => {
9
11
  return /^0x[a-fA-F0-9]{40}$/.test(address);
10
12
  };
13
+
14
+ /**
15
+ * Checks if a Solana address is on the Ed25519 curve (has a private key)
16
+ * If true: standard wallet address
17
+ * If false: program-derived address (PDA)
18
+ */
19
+ export const isSolanaAddressValid = (address: string): boolean => {
20
+ try {
21
+ const publicKey = new PublicKey(address);
22
+ return PublicKey.isOnCurve(publicKey);
23
+ } catch (error) {
24
+ return false;
25
+ }
26
+ };