tx-indexer 0.4.0 → 0.5.0
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 +36 -3
- package/dist/{classification.types-DlJe6bDZ.d.ts → classification.types-Cn9IGtEC.d.ts} +13 -12
- package/dist/{client-yGDWPKKf.d.ts → client-iLW2_DnL.d.ts} +39 -3
- package/dist/client.d.ts +2 -2
- package/dist/client.js +140 -14
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +141 -15
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { C as ClassifiedTransaction, F as FetchTransactionsConfig, G as GetTransactionsOptions, I as IndexerRpcApi, S as SolanaClient,
|
|
2
|
-
import { T as TxLeg, R as RawTransaction, a as TransactionClassification } from './classification.types-
|
|
3
|
-
export { f as RawTransactionSchema, k as TokenBalance, e as TokenBalanceSchema, n as TransactionClassificationSchema, j as TxCategory, d as TxCategorySchema, h as TxDirection, b as TxDirectionSchema, m as TxLegRole, g as TxLegSchema, l as TxLegSide, i as TxPrimaryType, c as TxPrimaryTypeSchema } from './classification.types-
|
|
1
|
+
export { C as ClassifiedTransaction, F as FetchTransactionsConfig, b as GetTransactionOptions, G as GetTransactionsOptions, I as IndexerRpcApi, N as NftMetadata, S as SolanaClient, m as SpamFilterConfig, g as TokenAccountBalance, T as TxIndexer, a as TxIndexerOptions, W as WalletBalance, c as createIndexer, d as createSolanaClient, n as fetchNftMetadata, o as fetchNftMetadataBatch, i as fetchTransaction, j as fetchTransactionsBatch, f as fetchWalletBalance, h as fetchWalletSignatures, l as filterSpamTransactions, k as isSpamTransaction, p as parseAddress, e as parseSignature, t as transactionToLegs } from './client-iLW2_DnL.js';
|
|
2
|
+
import { T as TxLeg, R as RawTransaction, a as TransactionClassification } from './classification.types-Cn9IGtEC.js';
|
|
3
|
+
export { f as RawTransactionSchema, k as TokenBalance, e as TokenBalanceSchema, n as TransactionClassificationSchema, j as TxCategory, d as TxCategorySchema, h as TxDirection, b as TxDirectionSchema, m as TxLegRole, g as TxLegSchema, l as TxLegSide, i as TxPrimaryType, c as TxPrimaryTypeSchema } from './classification.types-Cn9IGtEC.js';
|
|
4
4
|
import { ProtocolInfo, TokenInfo } from './types.js';
|
|
5
5
|
export { Categorization, CategorizationSchema, Counterparty, CounterpartySchema, FiatValue, MoneyAmount, ProtocolInfoSchema } from './types.js';
|
|
6
6
|
import { z } from 'zod';
|
package/dist/index.js
CHANGED
|
@@ -428,6 +428,7 @@ async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
|
|
|
428
428
|
signature: signature2,
|
|
429
429
|
slot: response.slot,
|
|
430
430
|
blockTime: response.blockTime,
|
|
431
|
+
fee: Number(response.meta?.fee ?? 0),
|
|
431
432
|
err: response.meta?.err ?? null,
|
|
432
433
|
programIds: extractProgramIds(response.transaction),
|
|
433
434
|
protocol: null,
|
|
@@ -747,7 +748,7 @@ var TransferClassifier = class {
|
|
|
747
748
|
sender,
|
|
748
749
|
receiver,
|
|
749
750
|
counterparty: {
|
|
750
|
-
type: "
|
|
751
|
+
type: "unknown",
|
|
751
752
|
address: receiver,
|
|
752
753
|
name: `${receiver.slice(0, 8)}...`
|
|
753
754
|
},
|
|
@@ -918,19 +919,19 @@ var SwapClassifier = class {
|
|
|
918
919
|
priority = 80;
|
|
919
920
|
classify(context) {
|
|
920
921
|
const { legs, tx } = context;
|
|
921
|
-
const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
|
|
922
|
-
if (!hasDexProtocol) {
|
|
923
|
-
return null;
|
|
924
|
-
}
|
|
925
922
|
const feeLeg = legs.find(
|
|
926
923
|
(leg) => leg.role === "fee" && leg.side === "debit"
|
|
927
924
|
);
|
|
928
925
|
const initiator = feeLeg?.accountId.replace("external:", "") ?? null;
|
|
926
|
+
if (!initiator) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
const initiatorAccountId = `external:${initiator}`;
|
|
929
930
|
const tokensOut = legs.filter(
|
|
930
|
-
(leg) => leg.accountId
|
|
931
|
+
(leg) => leg.accountId === initiatorAccountId && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
|
|
931
932
|
);
|
|
932
933
|
const tokensIn = legs.filter(
|
|
933
|
-
(leg) => leg.accountId
|
|
934
|
+
(leg) => leg.accountId === initiatorAccountId && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
|
|
934
935
|
);
|
|
935
936
|
if (tokensOut.length === 0 || tokensIn.length === 0) {
|
|
936
937
|
return null;
|
|
@@ -940,6 +941,8 @@ var SwapClassifier = class {
|
|
|
940
941
|
if (tokenOut.amount.token.symbol === tokenIn.amount.token.symbol) {
|
|
941
942
|
return null;
|
|
942
943
|
}
|
|
944
|
+
const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
|
|
945
|
+
const confidence = hasDexProtocol ? 0.95 : 0.75;
|
|
943
946
|
return {
|
|
944
947
|
primaryType: "swap",
|
|
945
948
|
primaryAmount: tokenOut.amount,
|
|
@@ -947,7 +950,7 @@ var SwapClassifier = class {
|
|
|
947
950
|
sender: initiator,
|
|
948
951
|
receiver: initiator,
|
|
949
952
|
counterparty: null,
|
|
950
|
-
confidence
|
|
953
|
+
confidence,
|
|
951
954
|
isRelevant: true,
|
|
952
955
|
metadata: {
|
|
953
956
|
swap_type: "token_to_token",
|
|
@@ -1079,7 +1082,7 @@ var SolanaPayClassifier = class {
|
|
|
1079
1082
|
counterparty: receiver ? {
|
|
1080
1083
|
address: receiver,
|
|
1081
1084
|
name: memo.merchant ?? void 0,
|
|
1082
|
-
type: memo.merchant ? "merchant" : "
|
|
1085
|
+
type: memo.merchant ? "merchant" : "unknown"
|
|
1083
1086
|
} : null,
|
|
1084
1087
|
confidence: 0.98,
|
|
1085
1088
|
isRelevant: true,
|
|
@@ -1356,8 +1359,81 @@ function filterSpamTransactions(transactions, config) {
|
|
|
1356
1359
|
);
|
|
1357
1360
|
}
|
|
1358
1361
|
|
|
1362
|
+
// src/nft.ts
|
|
1363
|
+
async function fetchNftMetadata(rpcUrl, mintAddress) {
|
|
1364
|
+
const response = await fetch(rpcUrl, {
|
|
1365
|
+
method: "POST",
|
|
1366
|
+
headers: { "Content-Type": "application/json" },
|
|
1367
|
+
body: JSON.stringify({
|
|
1368
|
+
jsonrpc: "2.0",
|
|
1369
|
+
id: "get-asset",
|
|
1370
|
+
method: "getAsset",
|
|
1371
|
+
params: { id: mintAddress }
|
|
1372
|
+
})
|
|
1373
|
+
});
|
|
1374
|
+
const data = await response.json();
|
|
1375
|
+
if (data.error || !data.result?.content?.metadata) {
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
const { result } = data;
|
|
1379
|
+
const { content, grouping } = result;
|
|
1380
|
+
return {
|
|
1381
|
+
mint: mintAddress,
|
|
1382
|
+
name: content.metadata.name,
|
|
1383
|
+
symbol: content.metadata.symbol,
|
|
1384
|
+
image: content.links.image ?? content.files?.[0]?.uri ?? "",
|
|
1385
|
+
cdnImage: content.files?.[0]?.cdn_uri,
|
|
1386
|
+
description: content.metadata.description,
|
|
1387
|
+
collection: grouping?.find((g) => g.group_key === "collection")?.group_value,
|
|
1388
|
+
attributes: content.metadata.attributes
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
|
|
1392
|
+
const results = await Promise.all(
|
|
1393
|
+
mintAddresses.map((mint) => fetchNftMetadata(rpcUrl, mint))
|
|
1394
|
+
);
|
|
1395
|
+
const map = /* @__PURE__ */ new Map();
|
|
1396
|
+
results.forEach((metadata, index) => {
|
|
1397
|
+
if (metadata) {
|
|
1398
|
+
map.set(mintAddresses[index], metadata);
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
return map;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1359
1404
|
// src/client.ts
|
|
1405
|
+
var NFT_TRANSACTION_TYPES = ["nft_mint", "nft_purchase", "nft_sale"];
|
|
1406
|
+
async function enrichNftClassification(rpcUrl, classified) {
|
|
1407
|
+
const { classification } = classified;
|
|
1408
|
+
if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
|
|
1409
|
+
return classified;
|
|
1410
|
+
}
|
|
1411
|
+
const nftMint = classification.metadata?.nft_mint;
|
|
1412
|
+
if (!nftMint) {
|
|
1413
|
+
return classified;
|
|
1414
|
+
}
|
|
1415
|
+
const nftData = await fetchNftMetadata(rpcUrl, nftMint);
|
|
1416
|
+
if (!nftData) {
|
|
1417
|
+
return classified;
|
|
1418
|
+
}
|
|
1419
|
+
return {
|
|
1420
|
+
...classified,
|
|
1421
|
+
classification: {
|
|
1422
|
+
...classification,
|
|
1423
|
+
metadata: {
|
|
1424
|
+
...classification.metadata,
|
|
1425
|
+
nft_name: nftData.name,
|
|
1426
|
+
nft_image: nftData.image,
|
|
1427
|
+
nft_cdn_image: nftData.cdnImage,
|
|
1428
|
+
nft_collection: nftData.collection,
|
|
1429
|
+
nft_symbol: nftData.symbol,
|
|
1430
|
+
nft_attributes: nftData.attributes
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1360
1435
|
function createIndexer(options) {
|
|
1436
|
+
const rpcUrl = "client" in options ? "" : options.rpcUrl;
|
|
1361
1437
|
const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
|
|
1362
1438
|
return {
|
|
1363
1439
|
rpc: client.rpc,
|
|
@@ -1365,7 +1441,39 @@ function createIndexer(options) {
|
|
|
1365
1441
|
return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
|
|
1366
1442
|
},
|
|
1367
1443
|
async getTransactions(walletAddress, options2 = {}) {
|
|
1368
|
-
const { limit = 10, before, until, filterSpam = true, spamConfig } = options2;
|
|
1444
|
+
const { limit = 10, before, until, filterSpam = true, spamConfig, enrichNftMetadata = true } = options2;
|
|
1445
|
+
async function enrichBatch(transactions) {
|
|
1446
|
+
if (!enrichNftMetadata || !rpcUrl) {
|
|
1447
|
+
return transactions;
|
|
1448
|
+
}
|
|
1449
|
+
const nftMints = transactions.filter((t) => NFT_TRANSACTION_TYPES.includes(t.classification.primaryType)).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
|
|
1450
|
+
if (nftMints.length === 0) {
|
|
1451
|
+
return transactions;
|
|
1452
|
+
}
|
|
1453
|
+
const nftMetadataMap = await fetchNftMetadataBatch(rpcUrl, nftMints);
|
|
1454
|
+
return transactions.map((t) => {
|
|
1455
|
+
const nftMint = t.classification.metadata?.nft_mint;
|
|
1456
|
+
if (!nftMint || !nftMetadataMap.has(nftMint)) {
|
|
1457
|
+
return t;
|
|
1458
|
+
}
|
|
1459
|
+
const nftData = nftMetadataMap.get(nftMint);
|
|
1460
|
+
return {
|
|
1461
|
+
...t,
|
|
1462
|
+
classification: {
|
|
1463
|
+
...t.classification,
|
|
1464
|
+
metadata: {
|
|
1465
|
+
...t.classification.metadata,
|
|
1466
|
+
nft_name: nftData.name,
|
|
1467
|
+
nft_image: nftData.image,
|
|
1468
|
+
nft_cdn_image: nftData.cdnImage,
|
|
1469
|
+
nft_collection: nftData.collection,
|
|
1470
|
+
nft_symbol: nftData.symbol,
|
|
1471
|
+
nft_attributes: nftData.attributes
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1369
1477
|
if (!filterSpam) {
|
|
1370
1478
|
const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
|
|
1371
1479
|
limit,
|
|
@@ -1388,7 +1496,7 @@ function createIndexer(options) {
|
|
|
1388
1496
|
const classification = classifyTransaction(legs, tx);
|
|
1389
1497
|
return { tx, classification, legs };
|
|
1390
1498
|
});
|
|
1391
|
-
return classified;
|
|
1499
|
+
return enrichBatch(classified);
|
|
1392
1500
|
}
|
|
1393
1501
|
const accumulated = [];
|
|
1394
1502
|
let currentBefore = before;
|
|
@@ -1427,9 +1535,11 @@ function createIndexer(options) {
|
|
|
1427
1535
|
break;
|
|
1428
1536
|
}
|
|
1429
1537
|
}
|
|
1430
|
-
|
|
1538
|
+
const result = accumulated.slice(0, limit);
|
|
1539
|
+
return enrichBatch(result);
|
|
1431
1540
|
},
|
|
1432
|
-
async getTransaction(signature2) {
|
|
1541
|
+
async getTransaction(signature2, options2 = {}) {
|
|
1542
|
+
const { enrichNftMetadata = true } = options2;
|
|
1433
1543
|
const tx = await fetchTransaction(client.rpc, signature2);
|
|
1434
1544
|
if (!tx) {
|
|
1435
1545
|
return null;
|
|
@@ -1437,10 +1547,26 @@ function createIndexer(options) {
|
|
|
1437
1547
|
tx.protocol = detectProtocol(tx.programIds);
|
|
1438
1548
|
const legs = transactionToLegs(tx);
|
|
1439
1549
|
const classification = classifyTransaction(legs, tx);
|
|
1440
|
-
|
|
1550
|
+
let classified = { tx, classification, legs };
|
|
1551
|
+
if (enrichNftMetadata && rpcUrl) {
|
|
1552
|
+
classified = await enrichNftClassification(rpcUrl, classified);
|
|
1553
|
+
}
|
|
1554
|
+
return classified;
|
|
1441
1555
|
},
|
|
1442
1556
|
async getRawTransaction(signature2) {
|
|
1443
1557
|
return fetchTransaction(client.rpc, signature2);
|
|
1558
|
+
},
|
|
1559
|
+
async getNftMetadata(mintAddress) {
|
|
1560
|
+
if (!rpcUrl) {
|
|
1561
|
+
throw new Error("getNftMetadata requires rpcUrl to be set");
|
|
1562
|
+
}
|
|
1563
|
+
return fetchNftMetadata(rpcUrl, mintAddress);
|
|
1564
|
+
},
|
|
1565
|
+
async getNftMetadataBatch(mintAddresses) {
|
|
1566
|
+
if (!rpcUrl) {
|
|
1567
|
+
throw new Error("getNftMetadataBatch requires rpcUrl to be set");
|
|
1568
|
+
}
|
|
1569
|
+
return fetchNftMetadataBatch(rpcUrl, mintAddresses);
|
|
1444
1570
|
}
|
|
1445
1571
|
};
|
|
1446
1572
|
}
|
|
@@ -1492,6 +1618,6 @@ function groupLegsByToken(legs) {
|
|
|
1492
1618
|
return grouped;
|
|
1493
1619
|
}
|
|
1494
1620
|
|
|
1495
|
-
export { JUPITER_V4_PROGRAM_ID, JUPITER_V6_PROGRAM_ID, KNOWN_TOKENS, SPL_MEMO_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_INFO, TOKEN_PROGRAM_ID, buildAccountId, classifyTransaction, createIndexer, createSolanaClient, detectFacilitator, detectProtocol, extractMemo, fetchTransaction, fetchTransactionsBatch, fetchWalletBalance, fetchWalletSignatures, filterSpamTransactions, getTokenInfo, groupLegsByAccount, groupLegsByToken, isSolanaPayTransaction, isSpamTransaction, parseAccountId, parseAddress, parseSignature, parseSolanaPayMemo, transactionToLegs, validateLegsBalance };
|
|
1621
|
+
export { JUPITER_V4_PROGRAM_ID, JUPITER_V6_PROGRAM_ID, KNOWN_TOKENS, SPL_MEMO_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_INFO, TOKEN_PROGRAM_ID, buildAccountId, classifyTransaction, createIndexer, createSolanaClient, detectFacilitator, detectProtocol, extractMemo, fetchNftMetadata, fetchNftMetadataBatch, fetchTransaction, fetchTransactionsBatch, fetchWalletBalance, fetchWalletSignatures, filterSpamTransactions, getTokenInfo, groupLegsByAccount, groupLegsByToken, isSolanaPayTransaction, isSpamTransaction, parseAccountId, parseAddress, parseSignature, parseSolanaPayMemo, transactionToLegs, validateLegsBalance };
|
|
1496
1622
|
//# sourceMappingURL=index.js.map
|
|
1497
1623
|
//# sourceMappingURL=index.js.map
|