tx-indexer 0.5.2 → 0.5.4
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/README.md +5 -5
- package/dist/{client-DdzTiKZ4.d.ts → client-xmDVjOy4.d.ts} +13 -1
- package/dist/client.d.ts +2 -2
- package/dist/client.js +548 -63
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +109 -5
- package/dist/index.js +570 -66
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +4 -4
- package/package.json +4 -2
- package/dist/{classification.types-Cn9IGtEC.d.ts → classification.types-h046WjuF.d.ts} +7 -7
package/dist/client.js
CHANGED
|
@@ -12,12 +12,40 @@ function parseSignature(sig) {
|
|
|
12
12
|
|
|
13
13
|
// ../domain/src/money/token-registry.ts
|
|
14
14
|
var KNOWN_TOKENS = {
|
|
15
|
+
// Native
|
|
15
16
|
SOL: "So11111111111111111111111111111111111111112",
|
|
17
|
+
// Stablecoins
|
|
16
18
|
USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
19
|
+
USDT: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
|
|
20
|
+
PYUSD: "2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo",
|
|
21
|
+
USDG: "2u1tszSeqZ3qBWF3uNGPFc8TzMk2tdiwknnRMWGWjGWH",
|
|
17
22
|
USDC_BRIDGED: "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM",
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
DAI: "EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCCi3Z4dPuFhh",
|
|
24
|
+
// Major tokens
|
|
25
|
+
JUP: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
|
|
26
|
+
JTO: "jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL",
|
|
27
|
+
PYTH: "HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3",
|
|
28
|
+
BONK: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
|
|
29
|
+
WIF: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm",
|
|
30
|
+
RENDER: "rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof",
|
|
31
|
+
HNT: "hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux",
|
|
32
|
+
RAY: "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R",
|
|
33
|
+
ORCA: "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE",
|
|
34
|
+
MNGO: "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac",
|
|
35
|
+
MSOL: "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So",
|
|
36
|
+
JITOSOL: "J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn",
|
|
37
|
+
BSOL: "bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1",
|
|
38
|
+
// Memecoins
|
|
39
|
+
POPCAT: "7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr",
|
|
40
|
+
MEW: "MEW1gQWJ3nEXg2qgERiKu7FAFj79PHvQVREQUzScPP5",
|
|
41
|
+
PNUT: "2qEHjDLDLbuBgRYvsxhc5D6uDWAivNFZGan56P1tpump",
|
|
42
|
+
FARTCOIN: "9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump",
|
|
43
|
+
AI16Z: "HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC",
|
|
44
|
+
// Wrapped tokens
|
|
45
|
+
WBTC: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh",
|
|
46
|
+
WETH: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs"};
|
|
20
47
|
var TOKEN_INFO = {
|
|
48
|
+
// Native SOL
|
|
21
49
|
[KNOWN_TOKENS.SOL]: {
|
|
22
50
|
mint: KNOWN_TOKENS.SOL,
|
|
23
51
|
symbol: "SOL",
|
|
@@ -25,6 +53,7 @@ var TOKEN_INFO = {
|
|
|
25
53
|
decimals: 9,
|
|
26
54
|
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png"
|
|
27
55
|
},
|
|
56
|
+
// Stablecoins
|
|
28
57
|
[KNOWN_TOKENS.USDC]: {
|
|
29
58
|
mint: KNOWN_TOKENS.USDC,
|
|
30
59
|
symbol: "USDC",
|
|
@@ -32,22 +61,194 @@ var TOKEN_INFO = {
|
|
|
32
61
|
decimals: 6,
|
|
33
62
|
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png"
|
|
34
63
|
},
|
|
35
|
-
[KNOWN_TOKENS.
|
|
36
|
-
mint: KNOWN_TOKENS.
|
|
37
|
-
symbol: "
|
|
38
|
-
name: "
|
|
39
|
-
decimals: 6
|
|
64
|
+
[KNOWN_TOKENS.USDT]: {
|
|
65
|
+
mint: KNOWN_TOKENS.USDT,
|
|
66
|
+
symbol: "USDT",
|
|
67
|
+
name: "Tether USD",
|
|
68
|
+
decimals: 6,
|
|
69
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB/logo.svg"
|
|
70
|
+
},
|
|
71
|
+
[KNOWN_TOKENS.PYUSD]: {
|
|
72
|
+
mint: KNOWN_TOKENS.PYUSD,
|
|
73
|
+
symbol: "PYUSD",
|
|
74
|
+
name: "PayPal USD",
|
|
75
|
+
decimals: 6,
|
|
76
|
+
logoURI: "https://img.fotofolio.xyz/?url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolana-labs%2Ftoken-list%2Fmain%2Fassets%2Fmainnet%2F2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo%2Flogo.png"
|
|
40
77
|
},
|
|
41
78
|
[KNOWN_TOKENS.USDG]: {
|
|
42
79
|
mint: KNOWN_TOKENS.USDG,
|
|
43
80
|
symbol: "USDG",
|
|
44
81
|
name: "USD Glitter",
|
|
45
82
|
decimals: 6
|
|
83
|
+
},
|
|
84
|
+
[KNOWN_TOKENS.USDC_BRIDGED]: {
|
|
85
|
+
mint: KNOWN_TOKENS.USDC_BRIDGED,
|
|
86
|
+
symbol: "USDCet",
|
|
87
|
+
name: "USDC (Wormhole)",
|
|
88
|
+
decimals: 6
|
|
89
|
+
},
|
|
90
|
+
[KNOWN_TOKENS.DAI]: {
|
|
91
|
+
mint: KNOWN_TOKENS.DAI,
|
|
92
|
+
symbol: "DAI",
|
|
93
|
+
name: "DAI (Wormhole)",
|
|
94
|
+
decimals: 8
|
|
95
|
+
},
|
|
96
|
+
// Major tokens
|
|
97
|
+
[KNOWN_TOKENS.JUP]: {
|
|
98
|
+
mint: KNOWN_TOKENS.JUP,
|
|
99
|
+
symbol: "JUP",
|
|
100
|
+
name: "Jupiter",
|
|
101
|
+
decimals: 6,
|
|
102
|
+
logoURI: "https://static.jup.ag/jup/icon.png"
|
|
103
|
+
},
|
|
104
|
+
[KNOWN_TOKENS.JTO]: {
|
|
105
|
+
mint: KNOWN_TOKENS.JTO,
|
|
106
|
+
symbol: "JTO",
|
|
107
|
+
name: "Jito",
|
|
108
|
+
decimals: 9,
|
|
109
|
+
logoURI: "https://metadata.jito.network/token/jto/image"
|
|
110
|
+
},
|
|
111
|
+
[KNOWN_TOKENS.PYTH]: {
|
|
112
|
+
mint: KNOWN_TOKENS.PYTH,
|
|
113
|
+
symbol: "PYTH",
|
|
114
|
+
name: "Pyth Network",
|
|
115
|
+
decimals: 6,
|
|
116
|
+
logoURI: "https://pyth.network/token.svg"
|
|
117
|
+
},
|
|
118
|
+
[KNOWN_TOKENS.BONK]: {
|
|
119
|
+
mint: KNOWN_TOKENS.BONK,
|
|
120
|
+
symbol: "BONK",
|
|
121
|
+
name: "Bonk",
|
|
122
|
+
decimals: 5,
|
|
123
|
+
logoURI: "https://arweave.net/hQiPZOsRZXGXBJd_82PhVdlM_hACsT_q6wqwf5cSY7I"
|
|
124
|
+
},
|
|
125
|
+
[KNOWN_TOKENS.WIF]: {
|
|
126
|
+
mint: KNOWN_TOKENS.WIF,
|
|
127
|
+
symbol: "WIF",
|
|
128
|
+
name: "dogwifhat",
|
|
129
|
+
decimals: 6,
|
|
130
|
+
logoURI: "https://bafkreibk3covs5ltyqxa272uodhber6kcclnlgrbwjee4kyqhstwmjqpfa.ipfs.nftstorage.link/"
|
|
131
|
+
},
|
|
132
|
+
[KNOWN_TOKENS.RENDER]: {
|
|
133
|
+
mint: KNOWN_TOKENS.RENDER,
|
|
134
|
+
symbol: "RENDER",
|
|
135
|
+
name: "Render Token",
|
|
136
|
+
decimals: 8,
|
|
137
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/rndrizKT3MK1iimdxRdWabcF7Zg7AR5T4nud4EkHBof/logo.png"
|
|
138
|
+
},
|
|
139
|
+
[KNOWN_TOKENS.HNT]: {
|
|
140
|
+
mint: KNOWN_TOKENS.HNT,
|
|
141
|
+
symbol: "HNT",
|
|
142
|
+
name: "Helium",
|
|
143
|
+
decimals: 8,
|
|
144
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux/logo.png"
|
|
145
|
+
},
|
|
146
|
+
[KNOWN_TOKENS.RAY]: {
|
|
147
|
+
mint: KNOWN_TOKENS.RAY,
|
|
148
|
+
symbol: "RAY",
|
|
149
|
+
name: "Raydium",
|
|
150
|
+
decimals: 6,
|
|
151
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R/logo.png"
|
|
152
|
+
},
|
|
153
|
+
[KNOWN_TOKENS.ORCA]: {
|
|
154
|
+
mint: KNOWN_TOKENS.ORCA,
|
|
155
|
+
symbol: "ORCA",
|
|
156
|
+
name: "Orca",
|
|
157
|
+
decimals: 6,
|
|
158
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE/logo.png"
|
|
159
|
+
},
|
|
160
|
+
[KNOWN_TOKENS.MNGO]: {
|
|
161
|
+
mint: KNOWN_TOKENS.MNGO,
|
|
162
|
+
symbol: "MNGO",
|
|
163
|
+
name: "Mango",
|
|
164
|
+
decimals: 6,
|
|
165
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac/logo.png"
|
|
166
|
+
},
|
|
167
|
+
// Liquid staking tokens
|
|
168
|
+
[KNOWN_TOKENS.MSOL]: {
|
|
169
|
+
mint: KNOWN_TOKENS.MSOL,
|
|
170
|
+
symbol: "mSOL",
|
|
171
|
+
name: "Marinade Staked SOL",
|
|
172
|
+
decimals: 9,
|
|
173
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So/logo.png"
|
|
174
|
+
},
|
|
175
|
+
[KNOWN_TOKENS.JITOSOL]: {
|
|
176
|
+
mint: KNOWN_TOKENS.JITOSOL,
|
|
177
|
+
symbol: "JitoSOL",
|
|
178
|
+
name: "Jito Staked SOL",
|
|
179
|
+
decimals: 9,
|
|
180
|
+
logoURI: "https://storage.googleapis.com/token-metadata/JitoSOL-256.png"
|
|
181
|
+
},
|
|
182
|
+
[KNOWN_TOKENS.BSOL]: {
|
|
183
|
+
mint: KNOWN_TOKENS.BSOL,
|
|
184
|
+
symbol: "bSOL",
|
|
185
|
+
name: "BlazeStake Staked SOL",
|
|
186
|
+
decimals: 9,
|
|
187
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1/logo.png"
|
|
188
|
+
},
|
|
189
|
+
// Memecoins
|
|
190
|
+
[KNOWN_TOKENS.POPCAT]: {
|
|
191
|
+
mint: KNOWN_TOKENS.POPCAT,
|
|
192
|
+
symbol: "POPCAT",
|
|
193
|
+
name: "Popcat",
|
|
194
|
+
decimals: 9,
|
|
195
|
+
logoURI: "https://bafkreidvkvuzyslw5jh5z242lgzwzhbi2kxxnpkm73fkuqzasyr34jj2o4.ipfs.nftstorage.link/"
|
|
196
|
+
},
|
|
197
|
+
[KNOWN_TOKENS.MEW]: {
|
|
198
|
+
mint: KNOWN_TOKENS.MEW,
|
|
199
|
+
symbol: "MEW",
|
|
200
|
+
name: "cat in a dogs world",
|
|
201
|
+
decimals: 5,
|
|
202
|
+
logoURI: "https://bafkreidlwyr565dxtao2ipsze6bmzpszqzybz7sqi2zaet5fs7k53henju.ipfs.nftstorage.link/"
|
|
203
|
+
},
|
|
204
|
+
[KNOWN_TOKENS.PNUT]: {
|
|
205
|
+
mint: KNOWN_TOKENS.PNUT,
|
|
206
|
+
symbol: "Peanut",
|
|
207
|
+
name: "Peanut the Squirrel",
|
|
208
|
+
decimals: 6,
|
|
209
|
+
logoURI: "https://ipfs.io/ipfs/QmNS3Hdb6pMheFzRdwXr3PPCrXcBohzhLrKHqEUV1n3HnJ"
|
|
210
|
+
},
|
|
211
|
+
[KNOWN_TOKENS.FARTCOIN]: {
|
|
212
|
+
mint: KNOWN_TOKENS.FARTCOIN,
|
|
213
|
+
symbol: "FARTCOIN",
|
|
214
|
+
name: "Fartcoin",
|
|
215
|
+
decimals: 6,
|
|
216
|
+
logoURI: "https://ipfs.io/ipfs/QmQHY6t8TxucH3F9LGPBBqqRLbyWx7NxWvrnoZKcq9ErrR"
|
|
217
|
+
},
|
|
218
|
+
[KNOWN_TOKENS.AI16Z]: {
|
|
219
|
+
mint: KNOWN_TOKENS.AI16Z,
|
|
220
|
+
symbol: "ai16z",
|
|
221
|
+
name: "ai16z",
|
|
222
|
+
decimals: 9,
|
|
223
|
+
logoURI: "https://ipfs.io/ipfs/QmRbm1mprqHmJ7PvCTrSNydkquLi5r41wq8kWbHvoRm3FX"
|
|
224
|
+
},
|
|
225
|
+
// Wrapped tokens
|
|
226
|
+
[KNOWN_TOKENS.WBTC]: {
|
|
227
|
+
mint: KNOWN_TOKENS.WBTC,
|
|
228
|
+
symbol: "WBTC",
|
|
229
|
+
name: "Wrapped BTC (Wormhole)",
|
|
230
|
+
decimals: 8,
|
|
231
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh/logo.png"
|
|
232
|
+
},
|
|
233
|
+
[KNOWN_TOKENS.WETH]: {
|
|
234
|
+
mint: KNOWN_TOKENS.WETH,
|
|
235
|
+
symbol: "WETH",
|
|
236
|
+
name: "Wrapped ETH (Wormhole)",
|
|
237
|
+
decimals: 8,
|
|
238
|
+
logoURI: "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs/logo.png"
|
|
46
239
|
}
|
|
47
240
|
};
|
|
48
241
|
function getTokenInfo(mint) {
|
|
49
242
|
return TOKEN_INFO[mint];
|
|
50
243
|
}
|
|
244
|
+
function createUnknownToken(mint, decimals) {
|
|
245
|
+
return {
|
|
246
|
+
mint,
|
|
247
|
+
symbol: mint.slice(0, 8),
|
|
248
|
+
name: `Unknown Token (${mint.slice(0, 8)}...)`,
|
|
249
|
+
decimals
|
|
250
|
+
};
|
|
251
|
+
}
|
|
51
252
|
|
|
52
253
|
// ../solana/src/constants/program-ids.ts
|
|
53
254
|
var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
|
|
@@ -900,21 +1101,38 @@ function determineTokenRole(change, tx, feePayer) {
|
|
|
900
1101
|
}
|
|
901
1102
|
|
|
902
1103
|
// ../classification/src/classifiers/transfer-classifier.ts
|
|
1104
|
+
function findBestTransferPair(legs) {
|
|
1105
|
+
const senderLegs = legs.filter(
|
|
1106
|
+
(l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
|
|
1107
|
+
);
|
|
1108
|
+
if (senderLegs.length === 0) return null;
|
|
1109
|
+
let bestPair = null;
|
|
1110
|
+
let bestAmount = 0;
|
|
1111
|
+
for (const senderLeg of senderLegs) {
|
|
1112
|
+
const receiverLeg = legs.find(
|
|
1113
|
+
(l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint && l.accountId !== senderLeg.accountId
|
|
1114
|
+
// Must be different account
|
|
1115
|
+
);
|
|
1116
|
+
if (receiverLeg) {
|
|
1117
|
+
const amount = senderLeg.amount.amountUi;
|
|
1118
|
+
if (amount > bestAmount) {
|
|
1119
|
+
bestAmount = amount;
|
|
1120
|
+
bestPair = { senderLeg, receiverLeg };
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return bestPair;
|
|
1125
|
+
}
|
|
903
1126
|
var TransferClassifier = class {
|
|
904
1127
|
name = "transfer";
|
|
905
1128
|
priority = 20;
|
|
906
1129
|
classify(context) {
|
|
907
1130
|
const { legs, tx } = context;
|
|
908
1131
|
const facilitator = tx.accountKeys ? detectFacilitator(tx.accountKeys) : null;
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
if (!senderLeg) return null;
|
|
1132
|
+
const pair = findBestTransferPair(legs);
|
|
1133
|
+
if (!pair) return null;
|
|
1134
|
+
const { senderLeg, receiverLeg } = pair;
|
|
913
1135
|
const sender = senderLeg.accountId.replace("external:", "");
|
|
914
|
-
const receiverLeg = legs.find(
|
|
915
|
-
(l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint
|
|
916
|
-
);
|
|
917
|
-
if (!receiverLeg) return null;
|
|
918
1136
|
const receiver = receiverLeg.accountId.replace("external:", "");
|
|
919
1137
|
return {
|
|
920
1138
|
primaryType: "transfer",
|
|
@@ -1393,6 +1611,27 @@ var FeeOnlyClassifier = class {
|
|
|
1393
1611
|
};
|
|
1394
1612
|
|
|
1395
1613
|
// ../classification/src/classifiers/solana-pay-classifier.ts
|
|
1614
|
+
function findBestTransferPair2(legs) {
|
|
1615
|
+
const senderLegs = legs.filter(
|
|
1616
|
+
(l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
|
|
1617
|
+
);
|
|
1618
|
+
if (senderLegs.length === 0) return null;
|
|
1619
|
+
let bestPair = null;
|
|
1620
|
+
let bestAmount = 0;
|
|
1621
|
+
for (const senderLeg of senderLegs) {
|
|
1622
|
+
const receiverLeg = legs.find(
|
|
1623
|
+
(l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint && l.accountId !== senderLeg.accountId
|
|
1624
|
+
);
|
|
1625
|
+
if (receiverLeg) {
|
|
1626
|
+
const amount = senderLeg.amount.amountUi;
|
|
1627
|
+
if (amount > bestAmount) {
|
|
1628
|
+
bestAmount = amount;
|
|
1629
|
+
bestPair = { senderLeg, receiverLeg };
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
return bestPair;
|
|
1634
|
+
}
|
|
1396
1635
|
var SolanaPayClassifier = class {
|
|
1397
1636
|
name = "solana-pay";
|
|
1398
1637
|
priority = 95;
|
|
@@ -1402,12 +1641,9 @@ var SolanaPayClassifier = class {
|
|
|
1402
1641
|
return null;
|
|
1403
1642
|
}
|
|
1404
1643
|
const memo = parseSolanaPayMemo(tx.memo);
|
|
1405
|
-
const
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
const receiverLeg = legs.find(
|
|
1409
|
-
(leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && leg.role === "received"
|
|
1410
|
-
);
|
|
1644
|
+
const pair = findBestTransferPair2(legs);
|
|
1645
|
+
const senderLeg = pair?.senderLeg ?? null;
|
|
1646
|
+
const receiverLeg = pair?.receiverLeg ?? null;
|
|
1411
1647
|
const sender = senderLeg?.accountId.replace("external:", "") ?? null;
|
|
1412
1648
|
const receiver = receiverLeg?.accountId.replace("external:", "") ?? null;
|
|
1413
1649
|
const primaryAmount = senderLeg?.amount ?? receiverLeg?.amount ?? null;
|
|
@@ -1697,6 +1933,118 @@ function filterSpamTransactions(transactions, config) {
|
|
|
1697
1933
|
);
|
|
1698
1934
|
}
|
|
1699
1935
|
|
|
1936
|
+
// ../domain/src/money/token-fetcher.ts
|
|
1937
|
+
var DEFAULT_JUPITER_API_URL = "https://tokens.jup.ag/tokens?tags=verified";
|
|
1938
|
+
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1939
|
+
function createTokenFetcher(options = {}) {
|
|
1940
|
+
const {
|
|
1941
|
+
jupiterApiUrl = DEFAULT_JUPITER_API_URL,
|
|
1942
|
+
cacheTtlMs = DEFAULT_CACHE_TTL_MS,
|
|
1943
|
+
prefetch = false
|
|
1944
|
+
} = options;
|
|
1945
|
+
const jupiterCache = /* @__PURE__ */ new Map();
|
|
1946
|
+
let lastFetchTime = 0;
|
|
1947
|
+
let fetchPromise = null;
|
|
1948
|
+
async function fetchJupiterTokens() {
|
|
1949
|
+
if (fetchPromise) {
|
|
1950
|
+
return fetchPromise;
|
|
1951
|
+
}
|
|
1952
|
+
if (Date.now() - lastFetchTime < cacheTtlMs && jupiterCache.size > 0) {
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
fetchPromise = (async () => {
|
|
1956
|
+
try {
|
|
1957
|
+
const response = await fetch(jupiterApiUrl);
|
|
1958
|
+
if (!response.ok) {
|
|
1959
|
+
console.warn(
|
|
1960
|
+
`Jupiter API returned ${response.status}: ${response.statusText}`
|
|
1961
|
+
);
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
const tokens = await response.json();
|
|
1965
|
+
jupiterCache.clear();
|
|
1966
|
+
for (const token of tokens) {
|
|
1967
|
+
jupiterCache.set(token.address, {
|
|
1968
|
+
mint: token.address,
|
|
1969
|
+
symbol: token.symbol,
|
|
1970
|
+
name: token.name,
|
|
1971
|
+
decimals: token.decimals,
|
|
1972
|
+
logoURI: token.logoURI
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
lastFetchTime = Date.now();
|
|
1976
|
+
} catch (error) {
|
|
1977
|
+
console.warn("Failed to fetch Jupiter tokens:", error);
|
|
1978
|
+
} finally {
|
|
1979
|
+
fetchPromise = null;
|
|
1980
|
+
}
|
|
1981
|
+
})();
|
|
1982
|
+
return fetchPromise;
|
|
1983
|
+
}
|
|
1984
|
+
async function getToken(mint, decimals = 9) {
|
|
1985
|
+
const staticToken = TOKEN_INFO[mint];
|
|
1986
|
+
if (staticToken) {
|
|
1987
|
+
return staticToken;
|
|
1988
|
+
}
|
|
1989
|
+
const cachedToken = jupiterCache.get(mint);
|
|
1990
|
+
if (cachedToken) {
|
|
1991
|
+
return cachedToken;
|
|
1992
|
+
}
|
|
1993
|
+
await fetchJupiterTokens();
|
|
1994
|
+
const fetchedToken = jupiterCache.get(mint);
|
|
1995
|
+
if (fetchedToken) {
|
|
1996
|
+
return fetchedToken;
|
|
1997
|
+
}
|
|
1998
|
+
return createUnknownToken(mint, decimals);
|
|
1999
|
+
}
|
|
2000
|
+
async function getTokens(mints, defaultDecimals = 9) {
|
|
2001
|
+
const result = /* @__PURE__ */ new Map();
|
|
2002
|
+
const missingMints = [];
|
|
2003
|
+
for (const mint of mints) {
|
|
2004
|
+
const staticToken = TOKEN_INFO[mint];
|
|
2005
|
+
if (staticToken) {
|
|
2006
|
+
result.set(mint, staticToken);
|
|
2007
|
+
continue;
|
|
2008
|
+
}
|
|
2009
|
+
const cachedToken = jupiterCache.get(mint);
|
|
2010
|
+
if (cachedToken) {
|
|
2011
|
+
result.set(mint, cachedToken);
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
missingMints.push(mint);
|
|
2015
|
+
}
|
|
2016
|
+
if (missingMints.length > 0) {
|
|
2017
|
+
await fetchJupiterTokens();
|
|
2018
|
+
for (const mint of missingMints) {
|
|
2019
|
+
const fetchedToken = jupiterCache.get(mint);
|
|
2020
|
+
if (fetchedToken) {
|
|
2021
|
+
result.set(mint, fetchedToken);
|
|
2022
|
+
} else {
|
|
2023
|
+
result.set(mint, createUnknownToken(mint, defaultDecimals));
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return result;
|
|
2028
|
+
}
|
|
2029
|
+
async function refresh() {
|
|
2030
|
+
lastFetchTime = 0;
|
|
2031
|
+
await fetchJupiterTokens();
|
|
2032
|
+
}
|
|
2033
|
+
function getCacheSize() {
|
|
2034
|
+
return jupiterCache.size;
|
|
2035
|
+
}
|
|
2036
|
+
if (prefetch) {
|
|
2037
|
+
fetchJupiterTokens().catch(() => {
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
return {
|
|
2041
|
+
getToken,
|
|
2042
|
+
getTokens,
|
|
2043
|
+
refresh,
|
|
2044
|
+
getCacheSize
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
|
|
1700
2048
|
// src/nft.ts
|
|
1701
2049
|
async function fetchNftMetadata(rpcUrl, mintAddress) {
|
|
1702
2050
|
const response = await fetch(rpcUrl, {
|
|
@@ -1741,6 +2089,102 @@ async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
|
|
|
1741
2089
|
|
|
1742
2090
|
// src/client.ts
|
|
1743
2091
|
var NFT_TRANSACTION_TYPES = ["nft_mint", "nft_purchase", "nft_sale"];
|
|
2092
|
+
async function enrichTokenMetadata(tokenFetcher, classified) {
|
|
2093
|
+
const mints = /* @__PURE__ */ new Set();
|
|
2094
|
+
const decimalsMap = /* @__PURE__ */ new Map();
|
|
2095
|
+
for (const leg of classified.legs) {
|
|
2096
|
+
const mint = leg.amount.token.mint;
|
|
2097
|
+
mints.add(mint);
|
|
2098
|
+
decimalsMap.set(mint, leg.amount.token.decimals);
|
|
2099
|
+
}
|
|
2100
|
+
if (classified.classification.primaryAmount?.token.mint) {
|
|
2101
|
+
const mint = classified.classification.primaryAmount.token.mint;
|
|
2102
|
+
mints.add(mint);
|
|
2103
|
+
decimalsMap.set(
|
|
2104
|
+
mint,
|
|
2105
|
+
classified.classification.primaryAmount.token.decimals
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
if (classified.classification.secondaryAmount?.token.mint) {
|
|
2109
|
+
const mint = classified.classification.secondaryAmount.token.mint;
|
|
2110
|
+
mints.add(mint);
|
|
2111
|
+
decimalsMap.set(
|
|
2112
|
+
mint,
|
|
2113
|
+
classified.classification.secondaryAmount.token.decimals
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
if (mints.size === 0) {
|
|
2117
|
+
return classified;
|
|
2118
|
+
}
|
|
2119
|
+
const tokenInfoMap = await tokenFetcher.getTokens(
|
|
2120
|
+
Array.from(mints),
|
|
2121
|
+
9
|
|
2122
|
+
// default decimals
|
|
2123
|
+
);
|
|
2124
|
+
function enrichAmount(amount) {
|
|
2125
|
+
if (!amount) return amount;
|
|
2126
|
+
const enrichedToken = tokenInfoMap.get(amount.token.mint);
|
|
2127
|
+
if (!enrichedToken || enrichedToken.symbol === amount.token.symbol) {
|
|
2128
|
+
return amount;
|
|
2129
|
+
}
|
|
2130
|
+
return {
|
|
2131
|
+
...amount,
|
|
2132
|
+
token: {
|
|
2133
|
+
...enrichedToken,
|
|
2134
|
+
// Keep the decimals from the original (from RPC) as they're authoritative
|
|
2135
|
+
decimals: amount.token.decimals
|
|
2136
|
+
}
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
function enrichLeg(leg) {
|
|
2140
|
+
const enrichedToken = tokenInfoMap.get(leg.amount.token.mint);
|
|
2141
|
+
if (!enrichedToken || enrichedToken.symbol === leg.amount.token.symbol) {
|
|
2142
|
+
return leg;
|
|
2143
|
+
}
|
|
2144
|
+
return {
|
|
2145
|
+
...leg,
|
|
2146
|
+
amount: {
|
|
2147
|
+
...leg.amount,
|
|
2148
|
+
token: {
|
|
2149
|
+
...enrichedToken,
|
|
2150
|
+
decimals: leg.amount.token.decimals
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
const enrichedLegs = classified.legs.map(enrichLeg);
|
|
2156
|
+
const enrichedClassification = {
|
|
2157
|
+
...classified.classification,
|
|
2158
|
+
primaryAmount: enrichAmount(classified.classification.primaryAmount) ?? null,
|
|
2159
|
+
secondaryAmount: enrichAmount(classified.classification.secondaryAmount)
|
|
2160
|
+
};
|
|
2161
|
+
return {
|
|
2162
|
+
tx: classified.tx,
|
|
2163
|
+
legs: enrichedLegs,
|
|
2164
|
+
classification: enrichedClassification
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
async function enrichTokenMetadataBatch(tokenFetcher, transactions) {
|
|
2168
|
+
const mints = /* @__PURE__ */ new Set();
|
|
2169
|
+
for (const classified of transactions) {
|
|
2170
|
+
for (const leg of classified.legs) {
|
|
2171
|
+
mints.add(leg.amount.token.mint);
|
|
2172
|
+
}
|
|
2173
|
+
if (classified.classification.primaryAmount?.token.mint) {
|
|
2174
|
+
mints.add(classified.classification.primaryAmount.token.mint);
|
|
2175
|
+
}
|
|
2176
|
+
if (classified.classification.secondaryAmount?.token.mint) {
|
|
2177
|
+
mints.add(classified.classification.secondaryAmount.token.mint);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
if (mints.size === 0) {
|
|
2181
|
+
return transactions;
|
|
2182
|
+
}
|
|
2183
|
+
await tokenFetcher.getTokens(Array.from(mints));
|
|
2184
|
+
return Promise.all(
|
|
2185
|
+
transactions.map((tx) => enrichTokenMetadata(tokenFetcher, tx))
|
|
2186
|
+
);
|
|
2187
|
+
}
|
|
1744
2188
|
async function enrichNftClassification(rpcUrl, classified) {
|
|
1745
2189
|
const { classification } = classified;
|
|
1746
2190
|
if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
|
|
@@ -1773,51 +2217,74 @@ async function enrichNftClassification(rpcUrl, classified) {
|
|
|
1773
2217
|
function createIndexer(options) {
|
|
1774
2218
|
const rpcUrl = "client" in options ? "" : options.rpcUrl;
|
|
1775
2219
|
const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
|
|
2220
|
+
const tokenFetcher = createTokenFetcher();
|
|
1776
2221
|
return {
|
|
1777
2222
|
rpc: client.rpc,
|
|
1778
2223
|
async getBalance(walletAddress, tokenMints) {
|
|
1779
2224
|
return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
|
|
1780
2225
|
},
|
|
1781
2226
|
async getTransactions(walletAddress, options2 = {}) {
|
|
1782
|
-
const {
|
|
2227
|
+
const {
|
|
2228
|
+
limit = 10,
|
|
2229
|
+
before,
|
|
2230
|
+
until,
|
|
2231
|
+
filterSpam = true,
|
|
2232
|
+
spamConfig,
|
|
2233
|
+
enrichNftMetadata = true,
|
|
2234
|
+
enrichTokenMetadata: enrichTokens = true
|
|
2235
|
+
} = options2;
|
|
1783
2236
|
async function enrichBatch(transactions) {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
const nftMints = transactions.filter((t) => NFT_TRANSACTION_TYPES.includes(t.classification.primaryType)).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
|
|
1788
|
-
if (nftMints.length === 0) {
|
|
1789
|
-
return transactions;
|
|
2237
|
+
let result2 = transactions;
|
|
2238
|
+
if (enrichTokens) {
|
|
2239
|
+
result2 = await enrichTokenMetadataBatch(tokenFetcher, result2);
|
|
1790
2240
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
nft_cdn_image: nftData.cdnImage,
|
|
1807
|
-
nft_collection: nftData.collection,
|
|
1808
|
-
nft_symbol: nftData.symbol,
|
|
1809
|
-
nft_attributes: nftData.attributes
|
|
2241
|
+
if (enrichNftMetadata && rpcUrl) {
|
|
2242
|
+
const nftMints = result2.filter(
|
|
2243
|
+
(t) => NFT_TRANSACTION_TYPES.includes(
|
|
2244
|
+
t.classification.primaryType
|
|
2245
|
+
)
|
|
2246
|
+
).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
|
|
2247
|
+
if (nftMints.length > 0) {
|
|
2248
|
+
const nftMetadataMap = await fetchNftMetadataBatch(
|
|
2249
|
+
rpcUrl,
|
|
2250
|
+
nftMints
|
|
2251
|
+
);
|
|
2252
|
+
result2 = result2.map((t) => {
|
|
2253
|
+
const nftMint = t.classification.metadata?.nft_mint;
|
|
2254
|
+
if (!nftMint || !nftMetadataMap.has(nftMint)) {
|
|
2255
|
+
return t;
|
|
1810
2256
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
2257
|
+
const nftData = nftMetadataMap.get(nftMint);
|
|
2258
|
+
return {
|
|
2259
|
+
...t,
|
|
2260
|
+
classification: {
|
|
2261
|
+
...t.classification,
|
|
2262
|
+
metadata: {
|
|
2263
|
+
...t.classification.metadata,
|
|
2264
|
+
nft_name: nftData.name,
|
|
2265
|
+
nft_image: nftData.image,
|
|
2266
|
+
nft_cdn_image: nftData.cdnImage,
|
|
2267
|
+
nft_collection: nftData.collection,
|
|
2268
|
+
nft_symbol: nftData.symbol,
|
|
2269
|
+
nft_attributes: nftData.attributes
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
};
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
return result2;
|
|
1814
2277
|
}
|
|
1815
2278
|
if (!filterSpam) {
|
|
1816
|
-
const signatures = await fetchWalletSignatures(
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2279
|
+
const signatures = await fetchWalletSignatures(
|
|
2280
|
+
client.rpc,
|
|
2281
|
+
walletAddress,
|
|
2282
|
+
{
|
|
2283
|
+
limit,
|
|
2284
|
+
before,
|
|
2285
|
+
until
|
|
2286
|
+
}
|
|
2287
|
+
);
|
|
1821
2288
|
if (signatures.length === 0) {
|
|
1822
2289
|
return [];
|
|
1823
2290
|
}
|
|
@@ -1832,7 +2299,11 @@ function createIndexer(options) {
|
|
|
1832
2299
|
const classified = transactions.map((tx) => {
|
|
1833
2300
|
tx.protocol = detectProtocol(tx.programIds);
|
|
1834
2301
|
const legs = transactionToLegs(tx);
|
|
1835
|
-
const classification = classifyTransaction(
|
|
2302
|
+
const classification = classifyTransaction(
|
|
2303
|
+
legs,
|
|
2304
|
+
tx,
|
|
2305
|
+
walletAddressStr2
|
|
2306
|
+
);
|
|
1836
2307
|
return { tx, classification, legs };
|
|
1837
2308
|
});
|
|
1838
2309
|
return enrichBatch(classified);
|
|
@@ -1845,11 +2316,15 @@ function createIndexer(options) {
|
|
|
1845
2316
|
while (accumulated.length < limit && iteration < MAX_ITERATIONS) {
|
|
1846
2317
|
iteration++;
|
|
1847
2318
|
const batchSize = iteration === 1 ? limit : limit * 2;
|
|
1848
|
-
const signatures = await fetchWalletSignatures(
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
2319
|
+
const signatures = await fetchWalletSignatures(
|
|
2320
|
+
client.rpc,
|
|
2321
|
+
walletAddress,
|
|
2322
|
+
{
|
|
2323
|
+
limit: batchSize,
|
|
2324
|
+
before: currentBefore,
|
|
2325
|
+
until
|
|
2326
|
+
}
|
|
2327
|
+
);
|
|
1853
2328
|
if (signatures.length === 0) {
|
|
1854
2329
|
break;
|
|
1855
2330
|
}
|
|
@@ -1863,7 +2338,11 @@ function createIndexer(options) {
|
|
|
1863
2338
|
const classified = transactions.map((tx) => {
|
|
1864
2339
|
tx.protocol = detectProtocol(tx.programIds);
|
|
1865
2340
|
const legs = transactionToLegs(tx);
|
|
1866
|
-
const classification = classifyTransaction(
|
|
2341
|
+
const classification = classifyTransaction(
|
|
2342
|
+
legs,
|
|
2343
|
+
tx,
|
|
2344
|
+
walletAddressStr
|
|
2345
|
+
);
|
|
1867
2346
|
return { tx, classification, legs };
|
|
1868
2347
|
});
|
|
1869
2348
|
const nonSpam = filterSpamTransactions(classified, spamConfig);
|
|
@@ -1879,7 +2358,10 @@ function createIndexer(options) {
|
|
|
1879
2358
|
return enrichBatch(result);
|
|
1880
2359
|
},
|
|
1881
2360
|
async getTransaction(signature2, options2 = {}) {
|
|
1882
|
-
const {
|
|
2361
|
+
const {
|
|
2362
|
+
enrichNftMetadata = true,
|
|
2363
|
+
enrichTokenMetadata: enrichTokens = true
|
|
2364
|
+
} = options2;
|
|
1883
2365
|
const tx = await fetchTransaction(client.rpc, signature2);
|
|
1884
2366
|
if (!tx) {
|
|
1885
2367
|
return null;
|
|
@@ -1888,6 +2370,9 @@ function createIndexer(options) {
|
|
|
1888
2370
|
const legs = transactionToLegs(tx);
|
|
1889
2371
|
const classification = classifyTransaction(legs, tx);
|
|
1890
2372
|
let classified = { tx, classification, legs };
|
|
2373
|
+
if (enrichTokens) {
|
|
2374
|
+
classified = await enrichTokenMetadata(tokenFetcher, classified);
|
|
2375
|
+
}
|
|
1891
2376
|
if (enrichNftMetadata && rpcUrl) {
|
|
1892
2377
|
classified = await enrichNftClassification(rpcUrl, classified);
|
|
1893
2378
|
}
|