@deserialize/multi-vm-wallet 1.2.11 → 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 +3 -2
- package/dist/svm/utils.js +45 -4
- 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 +60 -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
|
|
@@ -423,10 +469,10 @@ export const buildJupiterSwapTransaction = async (
|
|
|
423
469
|
console.log('buildJupiterSwapTransaction: Starting');
|
|
424
470
|
console.log('User public key:', userPublicKey);
|
|
425
471
|
console.log('Use shared accounts:', useSharedAccounts);
|
|
426
|
-
|
|
472
|
+
|
|
427
473
|
const priorityFee = prioritizationFeeLamports || 5000;
|
|
428
474
|
console.log('Prioritization fee:', priorityFee);
|
|
429
|
-
|
|
475
|
+
|
|
430
476
|
const body = {
|
|
431
477
|
quoteResponse: quote,
|
|
432
478
|
userPublicKey,
|
|
@@ -463,23 +509,23 @@ export const buildJupiterSwapTransaction = async (
|
|
|
463
509
|
try {
|
|
464
510
|
const error = await response.json();
|
|
465
511
|
console.log('Swap build error details:', error);
|
|
466
|
-
|
|
512
|
+
|
|
467
513
|
// Check if this is the shared accounts error
|
|
468
|
-
if (error.errorCode === 'NOT_SUPPORTED' &&
|
|
514
|
+
if (error.errorCode === 'NOT_SUPPORTED' &&
|
|
469
515
|
error.error?.includes('Simple AMMs are not supported with shared accounts')) {
|
|
470
516
|
console.log('Detected shared accounts incompatibility error');
|
|
471
517
|
throw new Error('SHARED_ACCOUNTS_NOT_SUPPORTED');
|
|
472
518
|
}
|
|
473
|
-
|
|
519
|
+
|
|
474
520
|
throw new Error(`Jupiter swap transaction build failed: ${error.message || response.statusText}`);
|
|
475
521
|
} catch (parseError) {
|
|
476
522
|
console.log('Failed to parse error response:', parseError);
|
|
477
|
-
|
|
523
|
+
|
|
478
524
|
// Re-throw if it's our custom error
|
|
479
525
|
if (parseError instanceof Error && parseError.message === 'SHARED_ACCOUNTS_NOT_SUPPORTED') {
|
|
480
526
|
throw parseError;
|
|
481
527
|
}
|
|
482
|
-
|
|
528
|
+
|
|
483
529
|
throw new Error(`Jupiter swap transaction build failed: ${response.statusText}`);
|
|
484
530
|
}
|
|
485
531
|
}
|
|
@@ -533,7 +579,7 @@ export const executeJupiterSwap = async (
|
|
|
533
579
|
} catch (error) {
|
|
534
580
|
if (error instanceof Error && error.message === 'SHARED_ACCOUNTS_NOT_SUPPORTED') {
|
|
535
581
|
console.log('Shared accounts not supported, retrying without shared accounts...');
|
|
536
|
-
|
|
582
|
+
|
|
537
583
|
// Retry without shared accounts
|
|
538
584
|
swapResponse = await buildJupiterSwapTransaction(
|
|
539
585
|
quote,
|
|
@@ -675,4 +721,5 @@ export const validateJupiterTokens = async (
|
|
|
675
721
|
console.log('Token validation failed:', error);
|
|
676
722
|
return { valid: false, message: 'Failed to validate tokens' };
|
|
677
723
|
}
|
|
678
|
-
};
|
|
724
|
+
};
|
|
725
|
+
|