@deserialize/multi-vm-wallet 1.2.10 → 1.2.21
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/IChainWallet.d.ts +5 -1
- package/dist/IChainWallet.js.map +1 -1
- package/dist/constant.d.ts +16 -0
- package/dist/constant.js +19 -3
- package/dist/constant.js.map +1 -1
- package/dist/evm/evm.d.ts +6 -1
- package/dist/evm/evm.js +36 -45
- package/dist/evm/evm.js.map +1 -1
- package/dist/evm/transactionParsing.d.ts +3687 -0
- package/dist/evm/transactionParsing.js +441 -0
- package/dist/evm/transactionParsing.js.map +1 -0
- package/dist/evm/utils.d.ts +2 -9
- package/dist/evm/utils.js +17 -16
- package/dist/evm/utils.js.map +1 -1
- package/dist/helpers/index.d.ts +4 -0
- package/dist/helpers/index.js +13 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/svm/constant.d.ts +15 -0
- package/dist/svm/constant.js +25 -0
- package/dist/svm/constant.js.map +1 -0
- package/dist/svm/svm.d.ts +5 -2
- package/dist/svm/svm.js +10 -0
- package/dist/svm/svm.js.map +1 -1
- package/dist/svm/transactionParsing.d.ts +28 -0
- package/dist/svm/transactionParsing.js +207 -0
- package/dist/svm/transactionParsing.js.map +1 -0
- package/dist/svm/utils.d.ts +4 -3
- package/dist/svm/utils.js +82 -9
- package/dist/svm/utils.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/dist/test.js +47 -9
- package/dist/test.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
- package/utils/IChainWallet.ts +6 -2
- package/utils/constant.ts +22 -4
- package/utils/evm/evm.ts +53 -48
- package/utils/evm/transactionParsing.ts +639 -0
- package/utils/evm/utils.ts +26 -25
- package/utils/helpers/index.ts +11 -0
- package/utils/svm/constant.ts +29 -0
- package/utils/svm/svm.ts +14 -2
- package/utils/svm/transactionParsing.ts +294 -0
- package/utils/svm/utils.ts +104 -13
- package/utils/test.ts +56 -6
- package/utils/types.ts +6 -1
package/utils/evm/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Balance, TokenInfo } from '../types'
|
|
1
|
+
import { Balance, ChainWalletConfig, SUPPORTED_VM, UserTokenBalance, TokenInfo } from '../types'
|
|
2
2
|
import { JsonRpcProvider, Contract, Wallet, TransactionRequest, TransactionResponse, TransactionReceipt, parseUnits, formatUnits } from 'ethers'
|
|
3
3
|
import BN from 'bn.js'
|
|
4
|
+
import { HelperAPI } from '../helpers';
|
|
4
5
|
|
|
5
6
|
const KYBER_BASE_URL = 'https://aggregator-api.kyberswap.com';
|
|
6
7
|
|
|
@@ -23,7 +24,7 @@ interface TransactionResult {
|
|
|
23
24
|
effectiveGasPrice?: bigint
|
|
24
25
|
blockNumber?: number
|
|
25
26
|
confirmations: number
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export interface SwapParams {
|
|
@@ -636,6 +637,22 @@ export const safeApprove = async (
|
|
|
636
637
|
}
|
|
637
638
|
}
|
|
638
639
|
|
|
640
|
+
export const discoverTokens = async (wallet: string, chain: ChainWalletConfig): Promise<UserTokenBalance<string>[]> => {
|
|
641
|
+
const balances = await HelperAPI.getUserToken(wallet, chain.vmType ?? "EVM", chain.chainId)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
const formatBalances: UserTokenBalance<string>[] = balances.data.map((token: any) => {
|
|
645
|
+
return {
|
|
646
|
+
address: token.contractAddress,
|
|
647
|
+
name: token.name,
|
|
648
|
+
symbol: token.symbol,
|
|
649
|
+
decimals: token.decimals,
|
|
650
|
+
balance: token.balance,
|
|
651
|
+
owner: wallet
|
|
652
|
+
}
|
|
653
|
+
})
|
|
654
|
+
return formatBalances
|
|
655
|
+
}
|
|
639
656
|
|
|
640
657
|
//swaps
|
|
641
658
|
|
|
@@ -769,9 +786,9 @@ export async function performSwap(params: {
|
|
|
769
786
|
amountIn: params.amountIn,
|
|
770
787
|
chainId: params.chainId
|
|
771
788
|
});
|
|
772
|
-
|
|
789
|
+
|
|
773
790
|
console.log('Fetching best swap route across all DEXs...');
|
|
774
|
-
|
|
791
|
+
|
|
775
792
|
const routeResponse = await getKyberSwapRoute({
|
|
776
793
|
chainId: params.chainId,
|
|
777
794
|
tokenIn: params.tokenIn,
|
|
@@ -792,7 +809,7 @@ export async function performSwap(params: {
|
|
|
792
809
|
}
|
|
793
810
|
|
|
794
811
|
const { routeSummary, routerAddress } = routeResponse.data;
|
|
795
|
-
|
|
812
|
+
|
|
796
813
|
// Debug: Log what we actually received
|
|
797
814
|
console.log('routeSummary keys:', Object.keys(routeSummary));
|
|
798
815
|
console.log('routeSummary.swaps exists:', !!routeSummary.swaps);
|
|
@@ -808,13 +825,13 @@ export async function performSwap(params: {
|
|
|
808
825
|
// Only try to access swaps if it exists
|
|
809
826
|
swapsCount: routeSummary.swaps ? routeSummary.swaps.length : 0,
|
|
810
827
|
// Only extract exchange names if swaps exists and has the expected structure
|
|
811
|
-
dexSources: routeSummary.swaps && Array.isArray(routeSummary.swaps)
|
|
828
|
+
dexSources: routeSummary.swaps && Array.isArray(routeSummary.swaps)
|
|
812
829
|
? routeSummary.swaps.map(swap => swap?.exchange || 'unknown').filter(Boolean)
|
|
813
830
|
: ['unknown']
|
|
814
831
|
});
|
|
815
832
|
|
|
816
833
|
console.log('Building executable transaction...');
|
|
817
|
-
|
|
834
|
+
|
|
818
835
|
const buildResponse = await buildKyberSwapTransaction(
|
|
819
836
|
params.chainId,
|
|
820
837
|
routeSummary,
|
|
@@ -852,7 +869,7 @@ export async function performSwap(params: {
|
|
|
852
869
|
|
|
853
870
|
} catch (error) {
|
|
854
871
|
console.error('❌ KyberSwap aggregation failed:', error);
|
|
855
|
-
|
|
872
|
+
|
|
856
873
|
// More detailed error logging
|
|
857
874
|
if (error instanceof Error) {
|
|
858
875
|
console.error('Error details:', {
|
|
@@ -861,7 +878,7 @@ export async function performSwap(params: {
|
|
|
861
878
|
name: error.name
|
|
862
879
|
});
|
|
863
880
|
}
|
|
864
|
-
|
|
881
|
+
|
|
865
882
|
throw new Error(`Swap preparation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
866
883
|
}
|
|
867
884
|
}
|
|
@@ -915,22 +932,6 @@ export function prepareSwapParams(
|
|
|
915
932
|
};
|
|
916
933
|
}
|
|
917
934
|
|
|
918
|
-
/**
|
|
919
|
-
* Normalize token address for Debonk
|
|
920
|
-
* Converts 'native' or variations to the standard 0xEeeee... address
|
|
921
|
-
*/
|
|
922
|
-
// export function normalizeTokenAddressForDebonk(tokenAddress: string): string {
|
|
923
|
-
// if (tokenAddress === 'native' ||
|
|
924
|
-
// tokenAddress.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee') {
|
|
925
|
-
// return getNativeTokenAddress();
|
|
926
|
-
// }
|
|
927
|
-
// return tokenAddress;
|
|
928
|
-
// }
|
|
929
|
-
|
|
930
|
-
/**
|
|
931
|
-
* Convert slippage from basis points to percentage for Debonk
|
|
932
|
-
* Input: 50 (0.5% in bps) -> Output: 0.5 (percentage)
|
|
933
|
-
*/
|
|
934
935
|
export function convertSlippageForDebonk(slippageBps: number): number {
|
|
935
936
|
return slippageBps / 100;
|
|
936
937
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { vmTypes } from "../types";
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "https://helper.decane.app"
|
|
4
|
+
|
|
5
|
+
export class HelperAPI {
|
|
6
|
+
static async getUserToken(wallet: string, vm: vmTypes, chainId: number) {
|
|
7
|
+
const res = await fetch("" + BASE_URL + `/${vm}/tokens/discover?wallet=${wallet}&chainId=${chainId}`);
|
|
8
|
+
const data = await res.json();
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const SVM_NETWORKS_NAME = {
|
|
2
|
+
SOLANA_MAINNET: "SOLANA_MAINNET",
|
|
3
|
+
SOLANA_TESTNET: "SOLANA_TESTNET",
|
|
4
|
+
ECLIPSE_MAINNET: "ECLIPSE_MAINNET"
|
|
5
|
+
} as const
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const SVM_CHAIN_DETAILS: { [key: string]: SVMNetwork } = {
|
|
11
|
+
"123456789": SVM_NETWORKS_NAME.SOLANA_MAINNET,
|
|
12
|
+
"123456790": SVM_NETWORKS_NAME.SOLANA_TESTNET,
|
|
13
|
+
"123456791": SVM_NETWORKS_NAME.ECLIPSE_MAINNET
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SVMNetwork = typeof SVM_NETWORKS_NAME[keyof typeof SVM_NETWORKS_NAME];
|
|
17
|
+
|
|
18
|
+
export const SVM_CHAIN_CONFIG: { [key in SVMNetwork]: { rpcUrl: string } } = {
|
|
19
|
+
[SVM_NETWORKS_NAME.SOLANA_MAINNET]: {
|
|
20
|
+
rpcUrl: "https://solana-mainnet.g.alchemy.com/v2/vB5mKztdJeFdz9RkW99Qf"
|
|
21
|
+
},
|
|
22
|
+
[SVM_NETWORKS_NAME.SOLANA_TESTNET]: {
|
|
23
|
+
rpcUrl: "https://solana-testnet.g.alchemy.com/v2/vB5mKztdJeFdz9RkW99Qf"
|
|
24
|
+
},
|
|
25
|
+
[SVM_NETWORKS_NAME.ECLIPSE_MAINNET]: {
|
|
26
|
+
rpcUrl: "https://mainnetbeta-rpc.eclipse.xyz"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
package/utils/svm/svm.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
|
|
2
2
|
import { SVMDeriveChildPrivateKey } from "../walletBip32";
|
|
3
3
|
import { VM } from "../vm";
|
|
4
4
|
import { ChainWallet } from "../IChainWallet";
|
|
5
|
-
import { Balance, ChainWalletConfig, TokenInfo, TransactionResult } from "../types";
|
|
5
|
+
import { Balance, ChainWalletConfig, UserTokenBalance, TokenInfo, TransactionResult } from "../types";
|
|
6
6
|
import {
|
|
7
7
|
getSvmNativeBalance,
|
|
8
8
|
getTokenBalance,
|
|
@@ -15,11 +15,14 @@ import {
|
|
|
15
15
|
uiAmountToBaseUnits,
|
|
16
16
|
validateJupiterTokens,
|
|
17
17
|
JupiterQuoteResponse,
|
|
18
|
-
getTokenInfo
|
|
18
|
+
getTokenInfo,
|
|
19
|
+
discoverTokens
|
|
19
20
|
} from "./utils";
|
|
20
21
|
import BN from "bn.js";
|
|
21
22
|
import nacl from "tweetnacl";
|
|
22
23
|
import base58 from "bs58";
|
|
24
|
+
import { getSVMTransactionHistory, SVMTransactionHistoryItem } from "./transactionParsing";
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
export class SVMVM extends VM<PublicKey, Keypair, Connection> {
|
|
25
28
|
getTokenInfo = getTokenInfo
|
|
@@ -92,6 +95,11 @@ export class SVMChainWallet extends ChainWallet<PublicKey, Keypair, Connection>
|
|
|
92
95
|
// Implement token balance retrieval logic here
|
|
93
96
|
return await SVMVM.getTokenBalance(this.address, (tokenAddress), this.connection!);
|
|
94
97
|
}
|
|
98
|
+
async discoverToken(): Promise<UserTokenBalance<PublicKey>[]> {
|
|
99
|
+
// Implement token discovery logic here
|
|
100
|
+
const tokens = await discoverTokens(this.address, this.connection!)
|
|
101
|
+
return tokens
|
|
102
|
+
}
|
|
95
103
|
|
|
96
104
|
async transferNative(to: PublicKey, amount: number): Promise<TransactionResult> {
|
|
97
105
|
// Implement native transfer logic here
|
|
@@ -107,6 +115,10 @@ export class SVMChainWallet extends ChainWallet<PublicKey, Keypair, Connection>
|
|
|
107
115
|
return { success: true, hash }; // Placeholder
|
|
108
116
|
}
|
|
109
117
|
|
|
118
|
+
async getTransactionHistory(): Promise<SVMTransactionHistoryItem[]> {
|
|
119
|
+
const history = await getSVMTransactionHistory(this.connection!, this.address);
|
|
120
|
+
return history;
|
|
121
|
+
}
|
|
110
122
|
|
|
111
123
|
async swap(fromToken: TokenInfo, toToken: PublicKey, amount: number, slippage: number = 50): Promise<TransactionResult> {
|
|
112
124
|
try {
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { Connection, PublicKey, ParsedTransactionWithMeta, ConfirmedSignatureInfo, TokenBalance } from '@solana/web3.js';
|
|
2
|
+
import { TRANSACTION_TYPE, TransactionType } from '../constant';
|
|
3
|
+
|
|
4
|
+
export interface SVMTransactionHistoryItem {
|
|
5
|
+
hash: string;
|
|
6
|
+
timestamp: number | null | undefined;
|
|
7
|
+
status: 'success' | 'failed' | 'pending';
|
|
8
|
+
fee: number;
|
|
9
|
+
type: TransactionType;
|
|
10
|
+
from: string;
|
|
11
|
+
to?: string;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
slot: number;
|
|
16
|
+
amount?: number;
|
|
17
|
+
token?: string;
|
|
18
|
+
memo?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TransactionHistoryOptions {
|
|
22
|
+
limit?: number;
|
|
23
|
+
before?: string;
|
|
24
|
+
until?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fetches and parses transaction history for a Solana wallet address
|
|
29
|
+
* @param connection - Solana RPC connection
|
|
30
|
+
* @param walletAddress - Public key of the wallet (string or PublicKey)
|
|
31
|
+
* @param options - Optional parameters for pagination and filtering
|
|
32
|
+
* @returns Array of parsed transaction history items
|
|
33
|
+
*/
|
|
34
|
+
export async function getSVMTransactionHistory(
|
|
35
|
+
connection: Connection,
|
|
36
|
+
walletAddress: PublicKey,
|
|
37
|
+
options: TransactionHistoryOptions = {}
|
|
38
|
+
): Promise<SVMTransactionHistoryItem[]> {
|
|
39
|
+
const { limit = 50, before, until } = options;
|
|
40
|
+
|
|
41
|
+
const publicKey = typeof walletAddress === 'string'
|
|
42
|
+
? new PublicKey(walletAddress)
|
|
43
|
+
: walletAddress;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// Fetch signature info
|
|
47
|
+
const signatures = await connection.getSignaturesForAddress(publicKey, {
|
|
48
|
+
limit,
|
|
49
|
+
before,
|
|
50
|
+
until,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(`Found ${signatures.length} transactions`);
|
|
54
|
+
|
|
55
|
+
// Fetch and parse transactions in batches to avoid rate limits
|
|
56
|
+
const transactions = await fetchTransactionsInBatches(
|
|
57
|
+
connection,
|
|
58
|
+
signatures,
|
|
59
|
+
5 // batch size
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Parse transactions into a user-friendly format
|
|
63
|
+
const history: SVMTransactionHistoryItem[] = [];
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < transactions.length; i++) {
|
|
66
|
+
const tx = transactions[i];
|
|
67
|
+
const sigInfo = signatures[i];
|
|
68
|
+
|
|
69
|
+
if (!tx) {
|
|
70
|
+
// Transaction might be null if it's not available
|
|
71
|
+
history.push({
|
|
72
|
+
hash: sigInfo.signature,
|
|
73
|
+
timestamp: sigInfo.blockTime,
|
|
74
|
+
slot: sigInfo.slot,
|
|
75
|
+
status: sigInfo.err ? 'failed' : 'success',
|
|
76
|
+
fee: 0,
|
|
77
|
+
type: TRANSACTION_TYPE.UNKNOWN,
|
|
78
|
+
from: walletAddress.toBase58()
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const parsed = parseTransaction(tx, publicKey.toBase58());
|
|
84
|
+
history.push({
|
|
85
|
+
hash: sigInfo.signature,
|
|
86
|
+
timestamp: sigInfo.blockTime,
|
|
87
|
+
slot: sigInfo.slot,
|
|
88
|
+
status: tx.meta?.err ? 'failed' : 'success',
|
|
89
|
+
fee: parsed.fee ?? 0,
|
|
90
|
+
type: parsed.type ?? TRANSACTION_TYPE.UNKNOWN,
|
|
91
|
+
from: parsed.from ?? walletAddress.toBase58(),
|
|
92
|
+
...parsed,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return history;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Error fetching transaction history:', error);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Fetches transactions in batches to avoid overwhelming the RPC
|
|
105
|
+
*/
|
|
106
|
+
async function fetchTransactionsInBatches(
|
|
107
|
+
connection: Connection,
|
|
108
|
+
signatures: ConfirmedSignatureInfo[],
|
|
109
|
+
batchSize: number
|
|
110
|
+
): Promise<(ParsedTransactionWithMeta | null)[]> {
|
|
111
|
+
const transactions: (ParsedTransactionWithMeta | null)[] = [];
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < signatures.length; i += batchSize) {
|
|
114
|
+
const batch = signatures.slice(i, i + batchSize);
|
|
115
|
+
const batchPromises = batch.map(sig =>
|
|
116
|
+
connection.getParsedTransaction(sig.signature, {
|
|
117
|
+
maxSupportedTransactionVersion: 0,
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const batchResults = await Promise.all(batchPromises);
|
|
122
|
+
transactions.push(...batchResults);
|
|
123
|
+
|
|
124
|
+
// Small delay to avoid rate limiting
|
|
125
|
+
if (i + batchSize < signatures.length) {
|
|
126
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return transactions;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Parses a transaction into a simplified format
|
|
135
|
+
*/
|
|
136
|
+
function parseTransaction(
|
|
137
|
+
tx: ParsedTransactionWithMeta,
|
|
138
|
+
walletAddress: string
|
|
139
|
+
): Partial<SVMTransactionHistoryItem> {
|
|
140
|
+
const fee = tx.meta?.fee || 0;
|
|
141
|
+
|
|
142
|
+
// Try to determine transaction type and extract relevant info
|
|
143
|
+
const instructions = tx.transaction.message.instructions;
|
|
144
|
+
|
|
145
|
+
// Check for token transfers
|
|
146
|
+
if (tx.meta?.preTokenBalances && tx.meta?.postTokenBalances) {
|
|
147
|
+
// console.log('tx.meta: ', tx.meta);
|
|
148
|
+
const tokenTransfer = findTokenTransfer(
|
|
149
|
+
tx.meta.preTokenBalances,
|
|
150
|
+
tx.meta.postTokenBalances,
|
|
151
|
+
walletAddress
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (tokenTransfer) {
|
|
155
|
+
return {
|
|
156
|
+
fee,
|
|
157
|
+
type: TRANSACTION_TYPE.TOKEN_TRANSFER,
|
|
158
|
+
from: tokenTransfer.from,
|
|
159
|
+
to: tokenTransfer.to,
|
|
160
|
+
amount: tokenTransfer.amount,
|
|
161
|
+
token: tokenTransfer.mint,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check for SOL transfers
|
|
167
|
+
if (tx.meta?.preBalances && tx.meta?.postBalances) {
|
|
168
|
+
const solTransfer = findSolTransfer(
|
|
169
|
+
tx.meta.preBalances,
|
|
170
|
+
tx.meta.postBalances,
|
|
171
|
+
tx.transaction.message.accountKeys,
|
|
172
|
+
walletAddress
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (solTransfer) {
|
|
176
|
+
return {
|
|
177
|
+
fee,
|
|
178
|
+
type: TRANSACTION_TYPE.NATIVE_TRANSFER,
|
|
179
|
+
from: solTransfer.from,
|
|
180
|
+
to: solTransfer.to,
|
|
181
|
+
amount: solTransfer.amount,
|
|
182
|
+
token: 'SOL',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check for memo
|
|
188
|
+
const memo = extractMemo(instructions);
|
|
189
|
+
|
|
190
|
+
// Determine general type based on instructions
|
|
191
|
+
const type = determineTransactionType(instructions);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
fee,
|
|
195
|
+
type,
|
|
196
|
+
memo,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Finds token transfers in the transaction
|
|
202
|
+
*/
|
|
203
|
+
function findTokenTransfer(
|
|
204
|
+
preBalances: TokenBalance[],
|
|
205
|
+
postBalances: TokenBalance[],
|
|
206
|
+
walletAddress: string
|
|
207
|
+
): { from: string; to: string; amount: number; mint: string } | null {
|
|
208
|
+
for (let i = 0; i < postBalances.length; i++) {
|
|
209
|
+
const post = postBalances[i]
|
|
210
|
+
const pre = preBalances.find(
|
|
211
|
+
p => p.accountIndex === post.accountIndex
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
if (!pre) continue;
|
|
215
|
+
|
|
216
|
+
const preAmount = parseFloat(pre.uiTokenAmount.uiAmountString || "0");
|
|
217
|
+
const postAmount = parseFloat(post.uiTokenAmount.uiAmountString || "0");
|
|
218
|
+
const diff = postAmount - preAmount;
|
|
219
|
+
const from = diff < 0 ? post.owner ?? "unknown" : postBalances[i + 1]?.owner ?? "unknown";
|
|
220
|
+
|
|
221
|
+
let to = diff > 0 ? post.owner ?? "unknown" : postBalances[i + 1]?.owner ?? "unknown";
|
|
222
|
+
if (to === "unknown") {
|
|
223
|
+
to = diff > 0 ? post.owner ?? "unknown" : postBalances[i - 1]?.owner ?? "unknown"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (from && to) {
|
|
227
|
+
|
|
228
|
+
if (Math.abs(diff) > 0) {
|
|
229
|
+
return {
|
|
230
|
+
from,
|
|
231
|
+
to,
|
|
232
|
+
amount: Math.abs(diff),
|
|
233
|
+
mint: post.mint,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Finds SOL transfers in the transaction
|
|
244
|
+
*/
|
|
245
|
+
function findSolTransfer(
|
|
246
|
+
preBalances: number[],
|
|
247
|
+
postBalances: number[],
|
|
248
|
+
accountKeys: any[],
|
|
249
|
+
walletAddress: string
|
|
250
|
+
): { from: string; to: string; amount: number } | null {
|
|
251
|
+
for (let i = 0; i < preBalances.length; i++) {
|
|
252
|
+
const diff = postBalances[i] - preBalances[i];
|
|
253
|
+
const account = accountKeys[i];
|
|
254
|
+
const accountPubkey = typeof account === 'string' ? account : account.pubkey.toBase58();
|
|
255
|
+
|
|
256
|
+
if (Math.abs(diff) > 5000 && accountPubkey === walletAddress) { // Ignore fee-only changes
|
|
257
|
+
return {
|
|
258
|
+
from: diff < 0 ? accountPubkey : 'unknown',
|
|
259
|
+
to: diff > 0 ? accountPubkey : 'unknown',
|
|
260
|
+
amount: Math.abs(diff) / 1e9, // Convert lamports to SOL
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Extracts memo from transaction instructions
|
|
270
|
+
*/
|
|
271
|
+
function extractMemo(instructions: any[]): string | undefined {
|
|
272
|
+
for (const instruction of instructions) {
|
|
273
|
+
if (instruction.program === 'spl-memo' || instruction.programId?.toBase58() === 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr') {
|
|
274
|
+
return instruction.parsed || instruction.data;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Determines the general type of transaction
|
|
282
|
+
*/
|
|
283
|
+
function determineTransactionType(instructions: any[]): TransactionType {
|
|
284
|
+
if (instructions.length === 0) return TRANSACTION_TYPE.UNKNOWN;
|
|
285
|
+
|
|
286
|
+
const programs = instructions.map(i => i.program || i.programId?.toBase58());
|
|
287
|
+
|
|
288
|
+
if (programs.includes('spl-token')) return TRANSACTION_TYPE.TOKEN_INTERACTIONS;
|
|
289
|
+
if (programs.includes('system')) return TRANSACTION_TYPE.SYSTEM;
|
|
290
|
+
if (programs.some(p => p?.includes('Swap'))) return TRANSACTION_TYPE.SWAP;
|
|
291
|
+
if (programs.some(p => p?.includes('Stake'))) return TRANSACTION_TYPE.STAKING;
|
|
292
|
+
|
|
293
|
+
return TRANSACTION_TYPE.PROGRAM_INTERACTION;
|
|
294
|
+
}
|
package/utils/svm/utils.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { Account, createAssociatedTokenAccountIdempotentInstruction, createTransferCheckedInstruction, getAccount, getAssociatedTokenAddress, getAssociatedTokenAddressSync, getMint, Mint, NATIVE_MINT, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
4
4
|
import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js";
|
|
5
|
-
import { TokenInfo } from "../types";
|
|
5
|
+
import { ChainWalletConfig, UserTokenBalance, TokenInfo } from "../types";
|
|
6
6
|
import { transactionSenderAndConfirmationWaiter } from "./transactionSender";
|
|
7
7
|
import { BN } from "bn.js";
|
|
8
|
+
import { Metaplex } from "@metaplex-foundation/js";
|
|
8
9
|
|
|
9
10
|
const JUPITER_BASE_URL = 'https://lite-api.jup.ag';
|
|
10
11
|
|
|
@@ -270,9 +271,25 @@ export const getTransferNativeTransaction = async (from: Keypair, to: PublicKey,
|
|
|
270
271
|
console.log('getTransferNativeTransaction: Completed');
|
|
271
272
|
return transaction;
|
|
272
273
|
}
|
|
274
|
+
const getMetaTokenMetaplexData = (mintAddress: PublicKey, connection: Connection) => {
|
|
275
|
+
const metaplex = Metaplex.make(connection);
|
|
276
|
+
return metaplex.nfts().findByMint({ mintAddress: mintAddress });
|
|
277
|
+
}
|
|
273
278
|
|
|
274
|
-
export const getTokenInfo = async (tokenAddress: PublicKey, connection: Connection): Promise<TokenInfo> => {
|
|
279
|
+
export const getTokenInfo = async (tokenAddress: PublicKey, connection: Connection, programId?: PublicKey): Promise<TokenInfo> => {
|
|
275
280
|
let mint: Mint
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
const metaplexData = await getMetaTokenMetaplexData(tokenAddress, connection).catch(() => null);
|
|
284
|
+
if (programId) {
|
|
285
|
+
const mint = await getMint(connection, tokenAddress, "confirmed", programId)
|
|
286
|
+
return {
|
|
287
|
+
address: tokenAddress.toString(),
|
|
288
|
+
decimals: mint.decimals,
|
|
289
|
+
name: metaplexData?.name || "",
|
|
290
|
+
symbol: metaplexData?.symbol || ""
|
|
291
|
+
}
|
|
292
|
+
}
|
|
276
293
|
try {
|
|
277
294
|
mint = await getMint(connection, tokenAddress, "confirmed", TOKEN_PROGRAM_ID)
|
|
278
295
|
|
|
@@ -286,8 +303,8 @@ export const getTokenInfo = async (tokenAddress: PublicKey, connection: Connecti
|
|
|
286
303
|
return {
|
|
287
304
|
address: tokenAddress.toString(),
|
|
288
305
|
decimals: mint.decimals,
|
|
289
|
-
name: "",
|
|
290
|
-
symbol: ""
|
|
306
|
+
name: metaplexData?.name || "",
|
|
307
|
+
symbol: metaplexData?.symbol || ""
|
|
291
308
|
}
|
|
292
309
|
}
|
|
293
310
|
|
|
@@ -359,6 +376,35 @@ export const signAndSendTransaction = async (transaction: VersionedTransaction,
|
|
|
359
376
|
console.log('Transaction successful, signature:', signature);
|
|
360
377
|
return signature;
|
|
361
378
|
}
|
|
379
|
+
export const discoverTokens = async (ownerAddress: PublicKey, connection: Connection): Promise<UserTokenBalance<PublicKey>[]> => {
|
|
380
|
+
|
|
381
|
+
const owner = new PublicKey(ownerAddress);
|
|
382
|
+
let response = await connection.getParsedTokenAccountsByOwner(owner, {
|
|
383
|
+
programId: TOKEN_PROGRAM_ID
|
|
384
|
+
});
|
|
385
|
+
console.log('response: ', response);
|
|
386
|
+
let tokens2022 = await connection.getParsedTokenAccountsByOwner(owner, {
|
|
387
|
+
programId: TOKEN_2022_PROGRAM_ID
|
|
388
|
+
});
|
|
389
|
+
console.log('tokens2022: ', tokens2022);
|
|
390
|
+
|
|
391
|
+
response.value = response.value.concat(tokens2022.value);
|
|
392
|
+
const tokens = await Promise.all(response.value.map(async (accountInfo) => {
|
|
393
|
+
|
|
394
|
+
const mintAddress = accountInfo.account.data["parsed"]["info"]["mint"]
|
|
395
|
+
const mint = await getTokenInfo(new PublicKey(mintAddress), connection)
|
|
396
|
+
return {
|
|
397
|
+
owner: accountInfo.account.data["parsed"]["info"]["owner"],
|
|
398
|
+
address: accountInfo.account.data["parsed"]["info"]["mint"],
|
|
399
|
+
decimals: accountInfo.account.data["parsed"]["info"]["tokenAmount"]["decimals"],
|
|
400
|
+
symbol: mint.symbol,
|
|
401
|
+
name: mint.name,
|
|
402
|
+
balance: accountInfo.account.data["parsed"]["info"]["tokenAmount"]["amount"]
|
|
403
|
+
}
|
|
404
|
+
}))
|
|
405
|
+
|
|
406
|
+
return tokens;
|
|
407
|
+
}
|
|
362
408
|
|
|
363
409
|
//swap
|
|
364
410
|
//you will. use jupiter for this
|
|
@@ -417,17 +463,21 @@ export const getJupiterQuote = async (
|
|
|
417
463
|
export const buildJupiterSwapTransaction = async (
|
|
418
464
|
quote: JupiterQuoteResponse,
|
|
419
465
|
userPublicKey: string,
|
|
420
|
-
prioritizationFeeLamports?: number
|
|
466
|
+
prioritizationFeeLamports?: number,
|
|
467
|
+
useSharedAccounts: boolean = true
|
|
421
468
|
): Promise<JupiterSwapResponse> => {
|
|
422
469
|
console.log('buildJupiterSwapTransaction: Starting');
|
|
423
470
|
console.log('User public key:', userPublicKey);
|
|
471
|
+
console.log('Use shared accounts:', useSharedAccounts);
|
|
472
|
+
|
|
424
473
|
const priorityFee = prioritizationFeeLamports || 5000;
|
|
425
474
|
console.log('Prioritization fee:', priorityFee);
|
|
475
|
+
|
|
426
476
|
const body = {
|
|
427
477
|
quoteResponse: quote,
|
|
428
478
|
userPublicKey,
|
|
429
479
|
wrapAndUnwrapSol: true,
|
|
430
|
-
useSharedAccounts:
|
|
480
|
+
useSharedAccounts: useSharedAccounts,
|
|
431
481
|
feeAccount: undefined,
|
|
432
482
|
trackingAccount: undefined,
|
|
433
483
|
computeUnitPriceMicroLamports: undefined,
|
|
@@ -459,9 +509,23 @@ export const buildJupiterSwapTransaction = async (
|
|
|
459
509
|
try {
|
|
460
510
|
const error = await response.json();
|
|
461
511
|
console.log('Swap build error details:', error);
|
|
512
|
+
|
|
513
|
+
// Check if this is the shared accounts error
|
|
514
|
+
if (error.errorCode === 'NOT_SUPPORTED' &&
|
|
515
|
+
error.error?.includes('Simple AMMs are not supported with shared accounts')) {
|
|
516
|
+
console.log('Detected shared accounts incompatibility error');
|
|
517
|
+
throw new Error('SHARED_ACCOUNTS_NOT_SUPPORTED');
|
|
518
|
+
}
|
|
519
|
+
|
|
462
520
|
throw new Error(`Jupiter swap transaction build failed: ${error.message || response.statusText}`);
|
|
463
521
|
} catch (parseError) {
|
|
464
522
|
console.log('Failed to parse error response:', parseError);
|
|
523
|
+
|
|
524
|
+
// Re-throw if it's our custom error
|
|
525
|
+
if (parseError instanceof Error && parseError.message === 'SHARED_ACCOUNTS_NOT_SUPPORTED') {
|
|
526
|
+
throw parseError;
|
|
527
|
+
}
|
|
528
|
+
|
|
465
529
|
throw new Error(`Jupiter swap transaction build failed: ${response.statusText}`);
|
|
466
530
|
}
|
|
467
531
|
}
|
|
@@ -500,12 +564,38 @@ export const executeJupiterSwap = async (
|
|
|
500
564
|
priceImpact: quote.priceImpactPct
|
|
501
565
|
});
|
|
502
566
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
quote,
|
|
506
|
-
swapParams.userPublicKey.toString()
|
|
507
|
-
);
|
|
567
|
+
let swapResponse: JupiterSwapResponse;
|
|
568
|
+
let usedSharedAccounts = true;
|
|
508
569
|
|
|
570
|
+
try {
|
|
571
|
+
console.log('Building swap transaction with shared accounts enabled...');
|
|
572
|
+
swapResponse = await buildJupiterSwapTransaction(
|
|
573
|
+
quote,
|
|
574
|
+
swapParams.userPublicKey.toString(),
|
|
575
|
+
undefined,
|
|
576
|
+
true // Try with shared accounts first
|
|
577
|
+
);
|
|
578
|
+
console.log('Successfully built transaction with shared accounts');
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (error instanceof Error && error.message === 'SHARED_ACCOUNTS_NOT_SUPPORTED') {
|
|
581
|
+
console.log('Shared accounts not supported, retrying without shared accounts...');
|
|
582
|
+
|
|
583
|
+
// Retry without shared accounts
|
|
584
|
+
swapResponse = await buildJupiterSwapTransaction(
|
|
585
|
+
quote,
|
|
586
|
+
swapParams.userPublicKey.toString(),
|
|
587
|
+
undefined,
|
|
588
|
+
false // Retry with shared accounts disabled
|
|
589
|
+
);
|
|
590
|
+
usedSharedAccounts = false;
|
|
591
|
+
console.log('Successfully built transaction without shared accounts');
|
|
592
|
+
} else {
|
|
593
|
+
// Re-throw if it's a different error
|
|
594
|
+
throw error;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
console.log('Transaction build method:', usedSharedAccounts ? 'with shared accounts' : 'without shared accounts');
|
|
509
599
|
console.log('Deserializing transaction...');
|
|
510
600
|
const swapTransactionBuf = Buffer.from(swapResponse.swapTransaction, 'base64');
|
|
511
601
|
console.log('Transaction buffer length:', swapTransactionBuf.length);
|
|
@@ -530,7 +620,6 @@ export const executeJupiterSwap = async (
|
|
|
530
620
|
}
|
|
531
621
|
});
|
|
532
622
|
|
|
533
|
-
// console.log('signature: ', signature);
|
|
534
623
|
if (!signature) {
|
|
535
624
|
console.log('Transaction failed to confirm');
|
|
536
625
|
return {
|
|
@@ -541,6 +630,7 @@ export const executeJupiterSwap = async (
|
|
|
541
630
|
|
|
542
631
|
const txSignature = signature.transaction.signatures[0];
|
|
543
632
|
console.log('Swap successful! Signature:', txSignature);
|
|
633
|
+
console.log('Used shared accounts:', usedSharedAccounts);
|
|
544
634
|
|
|
545
635
|
return {
|
|
546
636
|
success: true,
|
|
@@ -631,4 +721,5 @@ export const validateJupiterTokens = async (
|
|
|
631
721
|
console.log('Token validation failed:', error);
|
|
632
722
|
return { valid: false, message: 'Failed to validate tokens' };
|
|
633
723
|
}
|
|
634
|
-
};
|
|
724
|
+
};
|
|
725
|
+
|