tx-indexer 1.4.1 → 1.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/CHANGELOG.md +40 -0
- package/README.md +28 -14
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.js +404 -3
- package/dist/advanced.js.map +1 -1
- package/dist/index.js +404 -3
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add NFT transfer classification for marketplace and P2P transactions:
|
|
8
|
+
- New transaction types:
|
|
9
|
+
- `nft_purchase` - NFT bought on marketplace
|
|
10
|
+
- `nft_sale` - NFT sold on marketplace
|
|
11
|
+
- `nft_receive` - NFT received via direct transfer
|
|
12
|
+
- `nft_send` - NFT sent via direct transfer
|
|
13
|
+
|
|
14
|
+
- NFT marketplace detection for:
|
|
15
|
+
- Magic Eden (v2, MMM)
|
|
16
|
+
- Tensor (swap, marketplace, AMM)
|
|
17
|
+
- Hadeswap
|
|
18
|
+
- Metaplex Auction House
|
|
19
|
+
- Formfunction
|
|
20
|
+
|
|
21
|
+
- Smart classification logic:
|
|
22
|
+
- Direct NFT transfers detected by decimals=0 token movements
|
|
23
|
+
- Marketplace buy/sell determined by wallet perspective
|
|
24
|
+
- Escrow pattern handling (falls back to SOL flow analysis)
|
|
25
|
+
- Confidence levels: 0.9 for direct, 0.85 for SOL-flow based
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const tx = await indexer.getTransaction(signature);
|
|
29
|
+
|
|
30
|
+
if (tx.classification.primaryType === "nft_sale") {
|
|
31
|
+
console.log(
|
|
32
|
+
"Sold NFT for",
|
|
33
|
+
tx.classification.secondaryAmount?.amountUi,
|
|
34
|
+
"SOL",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (tx.classification.primaryType === "nft_receive") {
|
|
39
|
+
console.log("Received NFT:", tx.classification.metadata?.nft_name);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
3
43
|
## 1.4.0
|
|
4
44
|
|
|
5
45
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -205,20 +205,34 @@ interface ClassifiedTransaction {
|
|
|
205
205
|
|
|
206
206
|
## Transaction Types
|
|
207
207
|
|
|
208
|
-
| Type | Description
|
|
209
|
-
| ---------------- |
|
|
210
|
-
| `transfer` | Wallet-to-wallet transfers
|
|
211
|
-
| `swap` | Token exchanges (Jupiter, Raydium, Orca, etc.)
|
|
212
|
-
| `nft_mint` | NFT minting (Metaplex, Candy Machine, Bubblegum)
|
|
213
|
-
| `nft_purchase` | NFT
|
|
214
|
-
| `nft_sale` | NFT
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
208
|
+
| Type | Description |
|
|
209
|
+
| ---------------- | -------------------------------------------------------- |
|
|
210
|
+
| `transfer` | Wallet-to-wallet transfers |
|
|
211
|
+
| `swap` | Token exchanges (Jupiter, Raydium, Orca, etc.) |
|
|
212
|
+
| `nft_mint` | NFT minting (Metaplex, Candy Machine, Bubblegum) |
|
|
213
|
+
| `nft_purchase` | NFT bought on marketplace (Magic Eden, Tensor, Hadeswap) |
|
|
214
|
+
| `nft_sale` | NFT sold on marketplace |
|
|
215
|
+
| `nft_receive` | NFT received via direct P2P transfer |
|
|
216
|
+
| `nft_send` | NFT sent via direct P2P transfer |
|
|
217
|
+
| `stake_deposit` | SOL staking deposits |
|
|
218
|
+
| `stake_withdraw` | SOL staking withdrawals |
|
|
219
|
+
| `bridge_in` | Receiving from bridge (Wormhole, deBridge) |
|
|
220
|
+
| `bridge_out` | Sending to bridge |
|
|
221
|
+
| `airdrop` | Token distributions |
|
|
222
|
+
| `fee_only` | Transactions with only network fees |
|
|
223
|
+
| `other` | Unclassified transactions |
|
|
224
|
+
|
|
225
|
+
### NFT Marketplace Support
|
|
226
|
+
|
|
227
|
+
The SDK detects and classifies NFT transactions from major Solana marketplaces:
|
|
228
|
+
|
|
229
|
+
- **Magic Eden** - v2 and MMM (Market Making) programs
|
|
230
|
+
- **Tensor** - Swap, Marketplace, and AMM programs
|
|
231
|
+
- **Hadeswap** - AMM-based NFT trading
|
|
232
|
+
- **Metaplex Auction House** - Decentralized auction protocol
|
|
233
|
+
- **Formfunction** - Art-focused marketplace
|
|
234
|
+
|
|
235
|
+
For marketplace transactions, the classifier uses wallet perspective to determine if a transaction is a purchase or sale. When the wallet isn't directly involved in the NFT token movement (common with escrow patterns), it falls back to analyzing SOL/token flow.
|
|
222
236
|
|
|
223
237
|
## Entry Points
|
|
224
238
|
|
package/dist/advanced.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ declare class ClassificationService {
|
|
|
24
24
|
/**
|
|
25
25
|
* Classifies a transaction based on its accounting legs and context.
|
|
26
26
|
*
|
|
27
|
-
* Uses a priority-ordered chain of classifiers (Solana Pay > Privacy Cash > Bridge > NFT Mint > Stake Deposit > Stake Withdraw > Swap > Airdrop > Transfer > Fee-only)
|
|
27
|
+
* Uses a priority-ordered chain of classifiers (Solana Pay > Privacy Cash > Bridge > NFT Mint > NFT Transfer > Stake Deposit > Stake Withdraw > Swap > Airdrop > Transfer > Fee-only)
|
|
28
28
|
* to determine the transaction type, direction, amounts, sender, and receiver.
|
|
29
29
|
*
|
|
30
30
|
* @param legs - Transaction legs representing all balance movements
|
package/dist/advanced.js
CHANGED
|
@@ -70,8 +70,9 @@ var TOKEN_INFO = {
|
|
|
70
70
|
[KNOWN_TOKENS.USDG]: {
|
|
71
71
|
mint: KNOWN_TOKENS.USDG,
|
|
72
72
|
symbol: "USDG",
|
|
73
|
-
name: "
|
|
74
|
-
decimals: 6
|
|
73
|
+
name: "Global Dollar",
|
|
74
|
+
decimals: 6,
|
|
75
|
+
logoURI: "https://arweave.net/dMKl9vRAYD5V1_9aLGXk5K4EuFKNdzRWgN_5FgnPmYY"
|
|
75
76
|
},
|
|
76
77
|
[KNOWN_TOKENS.USDC_BRIDGED]: {
|
|
77
78
|
mint: KNOWN_TOKENS.USDC_BRIDGED,
|
|
@@ -317,6 +318,14 @@ var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR"
|
|
|
317
318
|
var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
|
|
318
319
|
var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
|
|
319
320
|
var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
|
|
321
|
+
var MAGIC_EDEN_V2_PROGRAM_ID = "M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7K";
|
|
322
|
+
var MAGIC_EDEN_MMM_PROGRAM_ID = "mmm3XBJg5gk8XJxEKBvdgptZz6SgK4tXvn36sodowMc";
|
|
323
|
+
var TENSOR_SWAP_PROGRAM_ID = "TSWAPaqyCSx2KABk68Shruf4rp7CxcNi8hAsbdwmHbN";
|
|
324
|
+
var TENSOR_MARKETPLACE_PROGRAM_ID = "TCMPhJdwDryooaGtiocG1u3xcYbRpiJzb283XfCZsDp";
|
|
325
|
+
var TENSOR_AMM_PROGRAM_ID = "TAMM6ub33ij1mbetoMyVBLeKY5iP41i4UPUJQGkhfsg";
|
|
326
|
+
var HADESWAP_PROGRAM_ID = "hadeK9DLv9eA7ya5KCTqSvSvRZeJC3JgD5a9Y3CNbvu";
|
|
327
|
+
var METAPLEX_AUCTION_HOUSE_PROGRAM_ID = "hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk";
|
|
328
|
+
var FORMFUNCTION_PROGRAM_ID = "formn3hJtt8gvVKxpCfzCJGuoz6CNUFcULFZW18iTpC";
|
|
320
329
|
var PRIVACY_CASH_PROGRAM_ID = "9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD";
|
|
321
330
|
var PRIVACY_CASH_SPL_POOL = "2vV7xhCMWRrcLiwGoTaTRgvx98ku98TRJKPXhsS8jvBV";
|
|
322
331
|
var PRIVACY_CASH_FEE_RECIPIENT = "AWexibGxNFKTa1b5R5MN4PJr9HWnWRwf8EW9g8cLx3dM";
|
|
@@ -1446,6 +1455,39 @@ var KNOWN_PROGRAMS = {
|
|
|
1446
1455
|
id: "magic-eden-candy-machine",
|
|
1447
1456
|
name: "Nft Candy Machine Program (Magic Eden)"
|
|
1448
1457
|
},
|
|
1458
|
+
// NFT Marketplaces
|
|
1459
|
+
[MAGIC_EDEN_V2_PROGRAM_ID]: {
|
|
1460
|
+
id: "magic-eden",
|
|
1461
|
+
name: "Magic Eden"
|
|
1462
|
+
},
|
|
1463
|
+
[MAGIC_EDEN_MMM_PROGRAM_ID]: {
|
|
1464
|
+
id: "magic-eden-mmm",
|
|
1465
|
+
name: "Magic Eden MMM"
|
|
1466
|
+
},
|
|
1467
|
+
[TENSOR_SWAP_PROGRAM_ID]: {
|
|
1468
|
+
id: "tensor",
|
|
1469
|
+
name: "Tensor"
|
|
1470
|
+
},
|
|
1471
|
+
[TENSOR_MARKETPLACE_PROGRAM_ID]: {
|
|
1472
|
+
id: "tensor-marketplace",
|
|
1473
|
+
name: "Tensor Marketplace"
|
|
1474
|
+
},
|
|
1475
|
+
[TENSOR_AMM_PROGRAM_ID]: {
|
|
1476
|
+
id: "tensor-amm",
|
|
1477
|
+
name: "Tensor AMM"
|
|
1478
|
+
},
|
|
1479
|
+
[HADESWAP_PROGRAM_ID]: {
|
|
1480
|
+
id: "hadeswap",
|
|
1481
|
+
name: "Hadeswap"
|
|
1482
|
+
},
|
|
1483
|
+
[METAPLEX_AUCTION_HOUSE_PROGRAM_ID]: {
|
|
1484
|
+
id: "auction-house",
|
|
1485
|
+
name: "Metaplex Auction House"
|
|
1486
|
+
},
|
|
1487
|
+
[FORMFUNCTION_PROGRAM_ID]: {
|
|
1488
|
+
id: "formfunction",
|
|
1489
|
+
name: "Formfunction"
|
|
1490
|
+
},
|
|
1449
1491
|
// Staking programs
|
|
1450
1492
|
[STAKE_PROGRAM_ID]: {
|
|
1451
1493
|
id: "stake",
|
|
@@ -1513,12 +1555,21 @@ var PRIORITY_ORDER = [
|
|
|
1513
1555
|
// Stableswap
|
|
1514
1556
|
"saber",
|
|
1515
1557
|
"mercurial",
|
|
1516
|
-
// NFT
|
|
1558
|
+
// NFT Minting
|
|
1517
1559
|
"metaplex",
|
|
1518
1560
|
"candy-guard",
|
|
1519
1561
|
"candy-machine-v3",
|
|
1520
1562
|
"bubblegum",
|
|
1521
1563
|
"magic-eden-candy-machine",
|
|
1564
|
+
// NFT Marketplaces
|
|
1565
|
+
"magic-eden",
|
|
1566
|
+
"magic-eden-mmm",
|
|
1567
|
+
"tensor",
|
|
1568
|
+
"tensor-marketplace",
|
|
1569
|
+
"tensor-amm",
|
|
1570
|
+
"hadeswap",
|
|
1571
|
+
"auction-house",
|
|
1572
|
+
"formfunction",
|
|
1522
1573
|
// Staking
|
|
1523
1574
|
"stake",
|
|
1524
1575
|
"stake-pool",
|
|
@@ -1566,6 +1617,16 @@ var NFT_MINT_PROTOCOL_IDS = /* @__PURE__ */ new Set([
|
|
|
1566
1617
|
"bubblegum",
|
|
1567
1618
|
"magic-eden-candy-machine"
|
|
1568
1619
|
]);
|
|
1620
|
+
var NFT_MARKETPLACE_PROTOCOL_IDS = /* @__PURE__ */ new Set([
|
|
1621
|
+
"magic-eden",
|
|
1622
|
+
"magic-eden-mmm",
|
|
1623
|
+
"tensor",
|
|
1624
|
+
"tensor-marketplace",
|
|
1625
|
+
"tensor-amm",
|
|
1626
|
+
"hadeswap",
|
|
1627
|
+
"auction-house",
|
|
1628
|
+
"formfunction"
|
|
1629
|
+
]);
|
|
1569
1630
|
var STAKE_PROTOCOL_IDS = /* @__PURE__ */ new Set(["stake", "stake-pool"]);
|
|
1570
1631
|
var BRIDGE_PROTOCOL_IDS = /* @__PURE__ */ new Set([
|
|
1571
1632
|
"wormhole",
|
|
@@ -1581,6 +1642,9 @@ function isDexProtocolById(protocolId) {
|
|
|
1581
1642
|
function isNftMintProtocolById(protocolId) {
|
|
1582
1643
|
return protocolId !== void 0 && NFT_MINT_PROTOCOL_IDS.has(protocolId);
|
|
1583
1644
|
}
|
|
1645
|
+
function isNftMarketplaceProtocolById(protocolId) {
|
|
1646
|
+
return protocolId !== void 0 && NFT_MARKETPLACE_PROTOCOL_IDS.has(protocolId);
|
|
1647
|
+
}
|
|
1584
1648
|
function isStakeProtocolById(protocolId) {
|
|
1585
1649
|
return protocolId !== void 0 && STAKE_PROTOCOL_IDS.has(protocolId);
|
|
1586
1650
|
}
|
|
@@ -1894,6 +1958,342 @@ var NftMintClassifier = class {
|
|
|
1894
1958
|
}
|
|
1895
1959
|
};
|
|
1896
1960
|
|
|
1961
|
+
// ../classification/src/classifiers/nft-transfer-classifier.ts
|
|
1962
|
+
function isNftLeg(leg) {
|
|
1963
|
+
return leg.amount.token.decimals === 0 && leg.amount.amountUi >= 1 && leg.accountId.startsWith("external:");
|
|
1964
|
+
}
|
|
1965
|
+
function findNftLegs(legs) {
|
|
1966
|
+
const nftLegs = legs.filter(isNftLeg);
|
|
1967
|
+
return {
|
|
1968
|
+
credits: nftLegs.filter((l) => l.side === "credit"),
|
|
1969
|
+
debits: nftLegs.filter((l) => l.side === "debit")
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
function findWalletNftLegs(legs, walletAddress) {
|
|
1973
|
+
if (!walletAddress) {
|
|
1974
|
+
return { walletCredits: [], walletDebits: [] };
|
|
1975
|
+
}
|
|
1976
|
+
const walletAccountId = `external:${walletAddress}`;
|
|
1977
|
+
const nftLegs = legs.filter(isNftLeg);
|
|
1978
|
+
return {
|
|
1979
|
+
walletCredits: nftLegs.filter(
|
|
1980
|
+
(l) => l.side === "credit" && l.accountId === walletAccountId
|
|
1981
|
+
),
|
|
1982
|
+
walletDebits: nftLegs.filter(
|
|
1983
|
+
(l) => l.side === "debit" && l.accountId === walletAccountId
|
|
1984
|
+
)
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
function findPaymentLeg(legs, side) {
|
|
1988
|
+
return legs.find(
|
|
1989
|
+
(leg) => leg.side === side && leg.amount.token.decimals > 0 && (leg.role === "sent" || leg.role === "received") && leg.accountId.startsWith("external:")
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
function findWalletPaymentLeg(legs, walletAddress, side) {
|
|
1993
|
+
if (!walletAddress) return void 0;
|
|
1994
|
+
const walletAccountId = `external:${walletAddress}`;
|
|
1995
|
+
return legs.find(
|
|
1996
|
+
(leg) => leg.accountId === walletAccountId && leg.side === side && leg.amount.token.decimals > 0 && (leg.role === "sent" || leg.role === "received")
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
var NftTransferClassifier = class {
|
|
2000
|
+
name = "nft-transfer";
|
|
2001
|
+
priority = 80;
|
|
2002
|
+
classify(context) {
|
|
2003
|
+
const { legs, tx, walletAddress } = context;
|
|
2004
|
+
if (isNftMintProtocolById(tx.protocol?.id)) {
|
|
2005
|
+
return null;
|
|
2006
|
+
}
|
|
2007
|
+
if (isDexProtocolById(tx.protocol?.id)) {
|
|
2008
|
+
return null;
|
|
2009
|
+
}
|
|
2010
|
+
const { credits: nftCredits, debits: nftDebits } = findNftLegs(legs);
|
|
2011
|
+
if (nftCredits.length === 0 && nftDebits.length === 0) {
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
const isMarketplace = isNftMarketplaceProtocolById(tx.protocol?.id);
|
|
2015
|
+
if (isMarketplace) {
|
|
2016
|
+
const { walletCredits, walletDebits } = findWalletNftLegs(
|
|
2017
|
+
legs,
|
|
2018
|
+
walletAddress
|
|
2019
|
+
);
|
|
2020
|
+
if (walletCredits.length > 0) {
|
|
2021
|
+
const paymentLeg = findPaymentLeg(legs, "debit");
|
|
2022
|
+
const primaryNft = walletCredits[0];
|
|
2023
|
+
const buyer = primaryNft.accountId.replace("external:", "");
|
|
2024
|
+
return {
|
|
2025
|
+
primaryType: "nft_purchase",
|
|
2026
|
+
primaryAmount: primaryNft.amount,
|
|
2027
|
+
secondaryAmount: paymentLeg?.amount ?? null,
|
|
2028
|
+
sender: null,
|
|
2029
|
+
receiver: buyer,
|
|
2030
|
+
counterparty: {
|
|
2031
|
+
type: "protocol",
|
|
2032
|
+
address: tx.protocol?.id ?? "marketplace",
|
|
2033
|
+
name: tx.protocol?.name ?? "NFT Marketplace"
|
|
2034
|
+
},
|
|
2035
|
+
confidence: 0.9,
|
|
2036
|
+
isRelevant: true,
|
|
2037
|
+
metadata: {
|
|
2038
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2039
|
+
nft_name: primaryNft.amount.token.name,
|
|
2040
|
+
quantity: walletCredits.length,
|
|
2041
|
+
purchase_price: paymentLeg?.amount.amountUi,
|
|
2042
|
+
purchase_token: paymentLeg?.amount.token.symbol,
|
|
2043
|
+
marketplace: tx.protocol?.id
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
if (walletDebits.length > 0) {
|
|
2048
|
+
const proceedsLeg = findPaymentLeg(legs, "credit");
|
|
2049
|
+
const primaryNft = walletDebits[0];
|
|
2050
|
+
const seller = primaryNft.accountId.replace("external:", "");
|
|
2051
|
+
return {
|
|
2052
|
+
primaryType: "nft_sale",
|
|
2053
|
+
primaryAmount: primaryNft.amount,
|
|
2054
|
+
secondaryAmount: proceedsLeg?.amount ?? null,
|
|
2055
|
+
sender: seller,
|
|
2056
|
+
receiver: null,
|
|
2057
|
+
counterparty: {
|
|
2058
|
+
type: "protocol",
|
|
2059
|
+
address: tx.protocol?.id ?? "marketplace",
|
|
2060
|
+
name: tx.protocol?.name ?? "NFT Marketplace"
|
|
2061
|
+
},
|
|
2062
|
+
confidence: 0.9,
|
|
2063
|
+
isRelevant: true,
|
|
2064
|
+
metadata: {
|
|
2065
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2066
|
+
nft_name: primaryNft.amount.token.name,
|
|
2067
|
+
quantity: walletDebits.length,
|
|
2068
|
+
sale_price: proceedsLeg?.amount.amountUi,
|
|
2069
|
+
sale_token: proceedsLeg?.amount.token.symbol,
|
|
2070
|
+
marketplace: tx.protocol?.id
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
const walletPaidLeg = findWalletPaymentLeg(legs, walletAddress, "debit");
|
|
2075
|
+
const walletReceivedLeg = findWalletPaymentLeg(
|
|
2076
|
+
legs,
|
|
2077
|
+
walletAddress,
|
|
2078
|
+
"credit"
|
|
2079
|
+
);
|
|
2080
|
+
if (walletReceivedLeg && (nftDebits.length > 0 || nftCredits.length > 0)) {
|
|
2081
|
+
const primaryNft = nftDebits[0] ?? nftCredits[0];
|
|
2082
|
+
return {
|
|
2083
|
+
primaryType: "nft_sale",
|
|
2084
|
+
primaryAmount: primaryNft.amount,
|
|
2085
|
+
secondaryAmount: walletReceivedLeg.amount,
|
|
2086
|
+
sender: walletAddress ?? null,
|
|
2087
|
+
receiver: nftCredits[0]?.accountId.replace("external:", "") ?? null,
|
|
2088
|
+
counterparty: {
|
|
2089
|
+
type: "protocol",
|
|
2090
|
+
address: tx.protocol?.id ?? "marketplace",
|
|
2091
|
+
name: tx.protocol?.name ?? "NFT Marketplace"
|
|
2092
|
+
},
|
|
2093
|
+
confidence: 0.85,
|
|
2094
|
+
// Good confidence based on SOL flow
|
|
2095
|
+
isRelevant: true,
|
|
2096
|
+
metadata: {
|
|
2097
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2098
|
+
nft_name: primaryNft.amount.token.name,
|
|
2099
|
+
quantity: nftDebits.length || nftCredits.length,
|
|
2100
|
+
sale_price: walletReceivedLeg.amount.amountUi,
|
|
2101
|
+
sale_token: walletReceivedLeg.amount.token.symbol,
|
|
2102
|
+
marketplace: tx.protocol?.id
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
if (walletPaidLeg && (nftCredits.length > 0 || nftDebits.length > 0)) {
|
|
2107
|
+
const primaryNft = nftCredits[0] ?? nftDebits[0];
|
|
2108
|
+
return {
|
|
2109
|
+
primaryType: "nft_purchase",
|
|
2110
|
+
primaryAmount: primaryNft.amount,
|
|
2111
|
+
secondaryAmount: walletPaidLeg.amount,
|
|
2112
|
+
sender: nftDebits[0]?.accountId.replace("external:", "") ?? null,
|
|
2113
|
+
receiver: walletAddress ?? null,
|
|
2114
|
+
counterparty: {
|
|
2115
|
+
type: "protocol",
|
|
2116
|
+
address: tx.protocol?.id ?? "marketplace",
|
|
2117
|
+
name: tx.protocol?.name ?? "NFT Marketplace"
|
|
2118
|
+
},
|
|
2119
|
+
confidence: 0.85,
|
|
2120
|
+
// Good confidence based on SOL flow
|
|
2121
|
+
isRelevant: true,
|
|
2122
|
+
metadata: {
|
|
2123
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2124
|
+
nft_name: primaryNft.amount.token.name,
|
|
2125
|
+
quantity: nftCredits.length || nftDebits.length,
|
|
2126
|
+
purchase_price: walletPaidLeg.amount.amountUi,
|
|
2127
|
+
purchase_token: walletPaidLeg.amount.token.symbol,
|
|
2128
|
+
marketplace: tx.protocol?.id
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
if (walletPaidLeg && nftCredits.length > 0) {
|
|
2133
|
+
const primaryNft = nftCredits[0];
|
|
2134
|
+
return {
|
|
2135
|
+
primaryType: "nft_purchase",
|
|
2136
|
+
primaryAmount: primaryNft.amount,
|
|
2137
|
+
secondaryAmount: walletPaidLeg.amount,
|
|
2138
|
+
sender: null,
|
|
2139
|
+
receiver: walletAddress ?? primaryNft.accountId.replace("external:", ""),
|
|
2140
|
+
counterparty: {
|
|
2141
|
+
type: "protocol",
|
|
2142
|
+
address: tx.protocol?.id ?? "marketplace",
|
|
2143
|
+
name: tx.protocol?.name ?? "NFT Marketplace"
|
|
2144
|
+
},
|
|
2145
|
+
confidence: 0.85,
|
|
2146
|
+
// Good confidence based on SOL flow
|
|
2147
|
+
isRelevant: true,
|
|
2148
|
+
metadata: {
|
|
2149
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2150
|
+
nft_name: primaryNft.amount.token.name,
|
|
2151
|
+
quantity: nftCredits.length,
|
|
2152
|
+
purchase_price: walletPaidLeg.amount.amountUi,
|
|
2153
|
+
purchase_token: walletPaidLeg.amount.token.symbol,
|
|
2154
|
+
marketplace: tx.protocol?.id
|
|
2155
|
+
}
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
if (nftCredits.length > 0) {
|
|
2159
|
+
const primaryNft = nftCredits[0];
|
|
2160
|
+
return {
|
|
2161
|
+
primaryType: "nft_purchase",
|
|
2162
|
+
primaryAmount: primaryNft.amount,
|
|
2163
|
+
secondaryAmount: null,
|
|
2164
|
+
sender: null,
|
|
2165
|
+
receiver: primaryNft.accountId.replace("external:", ""),
|
|
2166
|
+
counterparty: {
|
|
2167
|
+
type: "protocol",
|
|
2168
|
+
address: tx.protocol?.id ?? "marketplace",
|
|
2169
|
+
name: tx.protocol?.name ?? "NFT Marketplace"
|
|
2170
|
+
},
|
|
2171
|
+
confidence: 0.7,
|
|
2172
|
+
// Lower confidence when wallet not directly involved
|
|
2173
|
+
isRelevant: true,
|
|
2174
|
+
metadata: {
|
|
2175
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2176
|
+
nft_name: primaryNft.amount.token.name,
|
|
2177
|
+
quantity: nftCredits.length,
|
|
2178
|
+
marketplace: tx.protocol?.id
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
if (nftDebits.length > 0) {
|
|
2183
|
+
const primaryNft = nftDebits[0];
|
|
2184
|
+
return {
|
|
2185
|
+
primaryType: "nft_sale",
|
|
2186
|
+
primaryAmount: primaryNft.amount,
|
|
2187
|
+
secondaryAmount: null,
|
|
2188
|
+
sender: primaryNft.accountId.replace("external:", ""),
|
|
2189
|
+
receiver: null,
|
|
2190
|
+
counterparty: {
|
|
2191
|
+
type: "protocol",
|
|
2192
|
+
address: tx.protocol?.id ?? "marketplace",
|
|
2193
|
+
name: tx.protocol?.name ?? "NFT Marketplace"
|
|
2194
|
+
},
|
|
2195
|
+
confidence: 0.7,
|
|
2196
|
+
// Lower confidence when wallet not directly involved
|
|
2197
|
+
isRelevant: true,
|
|
2198
|
+
metadata: {
|
|
2199
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2200
|
+
nft_name: primaryNft.amount.token.name,
|
|
2201
|
+
quantity: nftDebits.length,
|
|
2202
|
+
marketplace: tx.protocol?.id
|
|
2203
|
+
}
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
if (nftCredits.length > 0 && nftDebits.length > 0) {
|
|
2208
|
+
const creditAccount = nftCredits[0].accountId.replace("external:", "");
|
|
2209
|
+
const debitAccount = nftDebits[0].accountId.replace("external:", "");
|
|
2210
|
+
const isSending = walletAddress === debitAccount;
|
|
2211
|
+
if (isSending) {
|
|
2212
|
+
const primaryNft = nftDebits[0];
|
|
2213
|
+
return {
|
|
2214
|
+
primaryType: "nft_send",
|
|
2215
|
+
primaryAmount: primaryNft.amount,
|
|
2216
|
+
secondaryAmount: null,
|
|
2217
|
+
sender: debitAccount,
|
|
2218
|
+
receiver: creditAccount,
|
|
2219
|
+
counterparty: {
|
|
2220
|
+
type: "unknown",
|
|
2221
|
+
address: creditAccount,
|
|
2222
|
+
name: `${creditAccount.slice(0, 8)}...`
|
|
2223
|
+
},
|
|
2224
|
+
confidence: 0.85,
|
|
2225
|
+
isRelevant: true,
|
|
2226
|
+
metadata: {
|
|
2227
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2228
|
+
nft_name: primaryNft.amount.token.name,
|
|
2229
|
+
quantity: nftDebits.length
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
} else {
|
|
2233
|
+
const primaryNft = nftCredits[0];
|
|
2234
|
+
return {
|
|
2235
|
+
primaryType: "nft_receive",
|
|
2236
|
+
primaryAmount: primaryNft.amount,
|
|
2237
|
+
secondaryAmount: null,
|
|
2238
|
+
sender: debitAccount,
|
|
2239
|
+
receiver: creditAccount,
|
|
2240
|
+
counterparty: {
|
|
2241
|
+
type: "unknown",
|
|
2242
|
+
address: debitAccount,
|
|
2243
|
+
name: `${debitAccount.slice(0, 8)}...`
|
|
2244
|
+
},
|
|
2245
|
+
confidence: 0.85,
|
|
2246
|
+
isRelevant: true,
|
|
2247
|
+
metadata: {
|
|
2248
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2249
|
+
nft_name: primaryNft.amount.token.name,
|
|
2250
|
+
quantity: nftCredits.length
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (nftCredits.length > 0 && nftDebits.length === 0) {
|
|
2256
|
+
const primaryNft = nftCredits[0];
|
|
2257
|
+
const receiver = primaryNft.accountId.replace("external:", "");
|
|
2258
|
+
return {
|
|
2259
|
+
primaryType: "nft_receive",
|
|
2260
|
+
primaryAmount: primaryNft.amount,
|
|
2261
|
+
secondaryAmount: null,
|
|
2262
|
+
sender: null,
|
|
2263
|
+
receiver,
|
|
2264
|
+
counterparty: null,
|
|
2265
|
+
confidence: 0.85,
|
|
2266
|
+
isRelevant: true,
|
|
2267
|
+
metadata: {
|
|
2268
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2269
|
+
nft_name: primaryNft.amount.token.name,
|
|
2270
|
+
quantity: nftCredits.length
|
|
2271
|
+
}
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
if (nftDebits.length > 0 && nftCredits.length === 0) {
|
|
2275
|
+
const primaryNft = nftDebits[0];
|
|
2276
|
+
const sender = primaryNft.accountId.replace("external:", "");
|
|
2277
|
+
return {
|
|
2278
|
+
primaryType: "nft_send",
|
|
2279
|
+
primaryAmount: primaryNft.amount,
|
|
2280
|
+
secondaryAmount: null,
|
|
2281
|
+
sender,
|
|
2282
|
+
receiver: null,
|
|
2283
|
+
counterparty: null,
|
|
2284
|
+
confidence: 0.85,
|
|
2285
|
+
isRelevant: true,
|
|
2286
|
+
metadata: {
|
|
2287
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
2288
|
+
nft_name: primaryNft.amount.token.name,
|
|
2289
|
+
quantity: nftDebits.length
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
return null;
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
|
|
1897
2297
|
// ../classification/src/classifiers/stake-deposit-classifier.ts
|
|
1898
2298
|
var StakeDepositClassifier = class {
|
|
1899
2299
|
name = "stake-deposit";
|
|
@@ -2160,6 +2560,7 @@ var ClassificationService = class {
|
|
|
2160
2560
|
this.registerClassifier(new PrivacyCashClassifier());
|
|
2161
2561
|
this.registerClassifier(new BridgeClassifier());
|
|
2162
2562
|
this.registerClassifier(new NftMintClassifier());
|
|
2563
|
+
this.registerClassifier(new NftTransferClassifier());
|
|
2163
2564
|
this.registerClassifier(new StakeDepositClassifier());
|
|
2164
2565
|
this.registerClassifier(new StakeWithdrawClassifier());
|
|
2165
2566
|
this.registerClassifier(new SwapClassifier());
|