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 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 purchase |
214
- | `nft_sale` | NFT sale |
215
- | `stake_deposit` | SOL staking deposits |
216
- | `stake_withdraw` | SOL staking withdrawals |
217
- | `bridge_in` | Receiving from bridge (Wormhole, deBridge) |
218
- | `bridge_out` | Sending to bridge |
219
- | `airdrop` | Token distributions |
220
- | `fee_only` | Transactions with only network fees |
221
- | `other` | Unclassified transactions |
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
 
@@ -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: "USD Glitter",
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());