@deserialize/multi-vm-wallet 1.2.0 → 1.2.2
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/bip32Old.d.ts +0 -51
- package/dist/bip32Old.js +876 -845
- package/dist/bip32Old.js.map +1 -1
- package/dist/bip32Small.d.ts +0 -9
- package/dist/bip32Small.js +78 -113
- package/dist/bip32Small.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/english.d.ts +1 -0
- package/dist/english.js +2052 -0
- package/dist/english.js.map +1 -0
- 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 +19 -22
- 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 +83 -10
- 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/dist/walletBip32.js +1 -1
- package/dist/walletBip32.js.map +1 -1
- package/package.json +4 -2
- package/utils/IChainWallet.ts +1 -1
- package/utils/bip32Old.ts +988 -988
- package/utils/bip32Small.ts +78 -78
- package/utils/constant.ts +22 -4
- package/utils/english.ts +2048 -0
- package/utils/evm/evm.ts +54 -48
- package/utils/evm/transactionParsing.ts +639 -0
- package/utils/evm/utils.ts +29 -33
- 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 +105 -14
- package/utils/test.ts +56 -6
- package/utils/types.ts +6 -1
- package/utils/walletBip32.ts +1 -1
package/utils/evm/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Balance, TokenInfo } from '../types'
|
|
2
|
-
import { JsonRpcProvider, Contract, Wallet, TransactionRequest, TransactionResponse, TransactionReceipt } from 'ethers'
|
|
1
|
+
import { Balance, ChainWalletConfig, SUPPORTED_VM, UserTokenBalance, TokenInfo } from '../types'
|
|
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
|
}
|
|
@@ -883,15 +900,10 @@ export function getNativeTokenAddress(): string {
|
|
|
883
900
|
}
|
|
884
901
|
|
|
885
902
|
export function formatAmountToWei(amount: string, decimals: number): string {
|
|
886
|
-
|
|
887
|
-
const multiplier = new BN(10).pow(new BN(decimals));
|
|
888
|
-
return amountBN.mul(multiplier).toString();
|
|
903
|
+
return parseUnits(amount, decimals).toString();
|
|
889
904
|
}
|
|
890
|
-
|
|
891
905
|
export function formatAmountFromWei(amountWei: string, decimals: number): string {
|
|
892
|
-
|
|
893
|
-
const divisor = new BN(10).pow(new BN(decimals));
|
|
894
|
-
return amountBN.div(divisor).toString();
|
|
906
|
+
return formatUnits(amountWei, decimals);
|
|
895
907
|
}
|
|
896
908
|
|
|
897
909
|
export function prepareSwapParams(
|
|
@@ -920,22 +932,6 @@ export function prepareSwapParams(
|
|
|
920
932
|
};
|
|
921
933
|
}
|
|
922
934
|
|
|
923
|
-
/**
|
|
924
|
-
* Normalize token address for Debonk
|
|
925
|
-
* Converts 'native' or variations to the standard 0xEeeee... address
|
|
926
|
-
*/
|
|
927
|
-
// export function normalizeTokenAddressForDebonk(tokenAddress: string): string {
|
|
928
|
-
// if (tokenAddress === 'native' ||
|
|
929
|
-
// tokenAddress.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee') {
|
|
930
|
-
// return getNativeTokenAddress();
|
|
931
|
-
// }
|
|
932
|
-
// return tokenAddress;
|
|
933
|
-
// }
|
|
934
|
-
|
|
935
|
-
/**
|
|
936
|
-
* Convert slippage from basis points to percentage for Debonk
|
|
937
|
-
* Input: 50 (0.5% in bps) -> Output: 0.5 (percentage)
|
|
938
|
-
*/
|
|
939
935
|
export function convertSlippageForDebonk(slippageBps: number): number {
|
|
940
936
|
return slippageBps / 100;
|
|
941
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
|
+
}
|