tx-indexer 0.3.0 → 0.4.1
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-w82k4B1F.d.ts → classification.types-Cn9IGtEC.d.ts} +15 -18
- package/dist/{client-BS9KUBU7.d.ts → client-iLW2_DnL.d.ts} +40 -79
- package/dist/client.d.ts +2 -2
- package/dist/client.js +523 -259
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +524 -260
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -149,6 +149,7 @@ function extractProgramIds(transaction) {
|
|
|
149
149
|
var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
|
|
150
150
|
var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
|
|
151
151
|
var STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111";
|
|
152
|
+
var STAKE_POOL_PROGRAM_ID = "SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy";
|
|
152
153
|
var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
153
154
|
var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
154
155
|
var SPL_MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
@@ -158,6 +159,15 @@ var JUPITER_V4_PROGRAM_ID = "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB";
|
|
|
158
159
|
var RAYDIUM_PROGRAM_ID = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
|
|
159
160
|
var ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
|
|
160
161
|
var METAPLEX_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
|
|
162
|
+
var CANDY_MACHINE_V3_PROGRAM_ID = "CndyV3LdqHUfDLmE5naZjVN8rBZz4tqhdefbAnjHG3JR";
|
|
163
|
+
var CANDY_GUARD_PROGRAM_ID = "Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g";
|
|
164
|
+
var BUBBLEGUM_PROGRAM_ID = "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY";
|
|
165
|
+
var MAGIC_EDEN_CANDY_MACHINE_ID = "CMZYPASGWeTz7RNGHaRJfCq2XQ5pYK6nDvVQxzkH51zb";
|
|
166
|
+
var WORMHOLE_PROGRAM_ID = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
|
|
167
|
+
var WORMHOLE_TOKEN_BRIDGE_ID = "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
|
|
168
|
+
var DEGODS_BRIDGE_PROGRAM_ID = "35iLrpYNNR9ygHLcvE1xKFHbHq6paHthrF6wSovdWgGu";
|
|
169
|
+
var DEBRIDGE_PROGRAM_ID = "DEbrdGj3HsRsAzx6uH4MKyREKxVAfBydijLUF3ygsFfh";
|
|
170
|
+
var ALLBRIDGE_PROGRAM_ID = "BrdgN2RPzEMWF96ZbnnJaUtQDQx7VRXYaHHbYCBvceWB";
|
|
161
171
|
var PAYAI_FACILITATOR = "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4";
|
|
162
172
|
var KNOWN_FACILITATORS = [
|
|
163
173
|
PAYAI_FACILITATOR
|
|
@@ -418,6 +428,7 @@ async function fetchTransaction(rpc, signature2, commitment = "confirmed") {
|
|
|
418
428
|
signature: signature2,
|
|
419
429
|
slot: response.slot,
|
|
420
430
|
blockTime: response.blockTime,
|
|
431
|
+
fee: Number(response.meta?.fee ?? 0),
|
|
421
432
|
err: response.meta?.err ?? null,
|
|
422
433
|
programIds: extractProgramIds(response.transaction),
|
|
423
434
|
protocol: null,
|
|
@@ -600,7 +611,7 @@ var DEX_PROTOCOL_IDS = /* @__PURE__ */ new Set([
|
|
|
600
611
|
function isDexProtocol(protocol) {
|
|
601
612
|
return protocol !== null && protocol !== void 0 && DEX_PROTOCOL_IDS.has(protocol.id);
|
|
602
613
|
}
|
|
603
|
-
function transactionToLegs(tx
|
|
614
|
+
function transactionToLegs(tx) {
|
|
604
615
|
const legs = [];
|
|
605
616
|
const feePayer = tx.accountKeys?.[0]?.toLowerCase();
|
|
606
617
|
const solChanges = extractSolBalanceChanges(tx);
|
|
@@ -608,9 +619,8 @@ function transactionToLegs(tx, walletAddress) {
|
|
|
608
619
|
let totalSolCredits = 0n;
|
|
609
620
|
for (const change of solChanges) {
|
|
610
621
|
if (change.change === 0n) continue;
|
|
611
|
-
const isWallet = walletAddress ? change.address.toLowerCase() === walletAddress.toLowerCase() : false;
|
|
612
622
|
const accountId = buildAccountId({
|
|
613
|
-
type:
|
|
623
|
+
type: "external",
|
|
614
624
|
address: change.address
|
|
615
625
|
});
|
|
616
626
|
const solInfo = TOKEN_INFO[KNOWN_TOKENS.SOL];
|
|
@@ -630,7 +640,7 @@ function transactionToLegs(tx, walletAddress) {
|
|
|
630
640
|
amountRaw: change.change.toString().replace("-", ""),
|
|
631
641
|
amountUi: Math.abs(change.changeUi)
|
|
632
642
|
},
|
|
633
|
-
role: determineSolRole(change,
|
|
643
|
+
role: determineSolRole(change, tx, feePayer)
|
|
634
644
|
});
|
|
635
645
|
}
|
|
636
646
|
const networkFee = totalSolDebits - totalSolCredits;
|
|
@@ -652,26 +662,22 @@ function transactionToLegs(tx, walletAddress) {
|
|
|
652
662
|
const tokenChanges = extractTokenBalanceChanges(tx);
|
|
653
663
|
for (const change of tokenChanges) {
|
|
654
664
|
if (change.change.raw === "0") continue;
|
|
655
|
-
const isWallet = walletAddress ? change.owner?.toLowerCase() === walletAddress.toLowerCase() : false;
|
|
656
|
-
const isFeePayer = !walletAddress && feePayer ? change.owner?.toLowerCase() === feePayer : false;
|
|
657
665
|
let accountId;
|
|
658
|
-
if (
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
token: change.tokenInfo.symbol
|
|
674
|
-
});
|
|
666
|
+
if (isDexProtocol(tx.protocol)) {
|
|
667
|
+
const isFeePayer = feePayer && change.owner?.toLowerCase() === feePayer;
|
|
668
|
+
if (isFeePayer) {
|
|
669
|
+
accountId = buildAccountId({
|
|
670
|
+
type: "external",
|
|
671
|
+
address: change.owner || feePayer
|
|
672
|
+
});
|
|
673
|
+
} else {
|
|
674
|
+
accountId = buildAccountId({
|
|
675
|
+
type: "protocol",
|
|
676
|
+
address: change.owner || change.tokenInfo.mint,
|
|
677
|
+
protocol: tx.protocol.id,
|
|
678
|
+
token: change.tokenInfo.symbol
|
|
679
|
+
});
|
|
680
|
+
}
|
|
675
681
|
} else {
|
|
676
682
|
accountId = buildAccountId({
|
|
677
683
|
type: "external",
|
|
@@ -686,41 +692,30 @@ function transactionToLegs(tx, walletAddress) {
|
|
|
686
692
|
amountRaw: change.change.raw.replace("-", ""),
|
|
687
693
|
amountUi: Math.abs(change.change.ui)
|
|
688
694
|
},
|
|
689
|
-
role: determineTokenRole(change,
|
|
695
|
+
role: determineTokenRole(change, tx, feePayer)
|
|
690
696
|
});
|
|
691
697
|
}
|
|
692
698
|
return legs;
|
|
693
699
|
}
|
|
694
|
-
function determineSolRole(change,
|
|
695
|
-
const
|
|
696
|
-
const isFeePayer = !walletAddress && feePayer ? change.address.toLowerCase() === feePayer : false;
|
|
700
|
+
function determineSolRole(change, tx, feePayer) {
|
|
701
|
+
const isFeePayer = feePayer ? change.address.toLowerCase() === feePayer : false;
|
|
697
702
|
const isPositive = change.change > 0n;
|
|
698
703
|
const amountSol = Math.abs(change.changeUi);
|
|
699
|
-
if (isWallet) {
|
|
700
|
-
if (!isPositive && amountSol < 0.01) {
|
|
701
|
-
return "fee";
|
|
702
|
-
}
|
|
703
|
-
if (isPositive) {
|
|
704
|
-
if (tx.protocol?.id === "stake") {
|
|
705
|
-
return "reward";
|
|
706
|
-
}
|
|
707
|
-
return "received";
|
|
708
|
-
}
|
|
709
|
-
return "sent";
|
|
710
|
-
}
|
|
711
704
|
if (isFeePayer && !isPositive && amountSol < 0.01) {
|
|
712
705
|
return "fee";
|
|
713
706
|
}
|
|
714
707
|
if (isPositive) {
|
|
708
|
+
if (tx.protocol?.id === "stake") {
|
|
709
|
+
return "reward";
|
|
710
|
+
}
|
|
715
711
|
return "received";
|
|
716
712
|
}
|
|
717
713
|
return "sent";
|
|
718
714
|
}
|
|
719
|
-
function determineTokenRole(change,
|
|
720
|
-
const
|
|
721
|
-
const isFeePayer = !walletAddress && feePayer ? change.owner?.toLowerCase() === feePayer : false;
|
|
715
|
+
function determineTokenRole(change, tx, feePayer) {
|
|
716
|
+
const isFeePayer = feePayer ? change.owner?.toLowerCase() === feePayer : false;
|
|
722
717
|
const isPositive = change.change.ui > 0;
|
|
723
|
-
if (
|
|
718
|
+
if (isFeePayer) {
|
|
724
719
|
return isPositive ? "received" : "sent";
|
|
725
720
|
}
|
|
726
721
|
if (isDexProtocol(tx.protocol)) {
|
|
@@ -734,85 +729,35 @@ var TransferClassifier = class {
|
|
|
734
729
|
name = "transfer";
|
|
735
730
|
priority = 20;
|
|
736
731
|
classify(context) {
|
|
737
|
-
const { legs,
|
|
732
|
+
const { legs, tx } = context;
|
|
738
733
|
const facilitator = tx.accountKeys ? detectFacilitator(tx.accountKeys) : null;
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
let participantPrefix;
|
|
742
|
-
if (walletAddress) {
|
|
743
|
-
participantAddress = walletAddress.toLowerCase();
|
|
744
|
-
participantPrefix = `wallet:${participantAddress}`;
|
|
745
|
-
} else {
|
|
746
|
-
const feeLeg = legs.find(
|
|
747
|
-
(leg) => leg.role === "fee" && leg.accountId.startsWith("external:")
|
|
748
|
-
);
|
|
749
|
-
if (!feeLeg) return null;
|
|
750
|
-
participantAddress = feeLeg.accountId.replace("external:", "").toLowerCase();
|
|
751
|
-
participantPrefix = `external:${participantAddress}`;
|
|
752
|
-
}
|
|
753
|
-
const participantSent = legs.filter(
|
|
754
|
-
(l) => l.accountId.toLowerCase().startsWith(participantPrefix.toLowerCase()) && l.side === "debit" && l.role === "sent"
|
|
755
|
-
);
|
|
756
|
-
const participantReceived = legs.filter(
|
|
757
|
-
(l) => l.accountId.toLowerCase().startsWith(participantPrefix.toLowerCase()) && l.side === "credit" && l.role === "received"
|
|
758
|
-
);
|
|
759
|
-
const otherReceived = legs.filter(
|
|
760
|
-
(l) => !l.accountId.toLowerCase().startsWith(participantPrefix.toLowerCase()) && l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received"
|
|
734
|
+
const senderLeg = legs.find(
|
|
735
|
+
(l) => l.accountId.startsWith("external:") && l.side === "debit" && l.role === "sent"
|
|
761
736
|
);
|
|
762
|
-
|
|
763
|
-
|
|
737
|
+
if (!senderLeg) return null;
|
|
738
|
+
const sender = senderLeg.accountId.replace("external:", "");
|
|
739
|
+
const receiverLeg = legs.find(
|
|
740
|
+
(l) => l.accountId.startsWith("external:") && l.side === "credit" && l.role === "received" && l.amount.token.mint === senderLeg.amount.token.mint
|
|
764
741
|
);
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
isRelevant: true,
|
|
783
|
-
metadata: {
|
|
784
|
-
...isObserverMode && { observer_mode: true, sender: participantAddress },
|
|
785
|
-
...facilitator && { facilitator, payment_type: "facilitated" }
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
for (const received of participantReceived) {
|
|
791
|
-
const matchingSent = otherSent.find(
|
|
792
|
-
(s) => s.amount.token.mint === received.amount.token.mint
|
|
793
|
-
);
|
|
794
|
-
if (matchingSent) {
|
|
795
|
-
const senderAddress = matchingSent.accountId.replace("external:", "");
|
|
796
|
-
return {
|
|
797
|
-
primaryType: "transfer",
|
|
798
|
-
direction: isObserverMode ? "neutral" : "incoming",
|
|
799
|
-
primaryAmount: received.amount,
|
|
800
|
-
secondaryAmount: null,
|
|
801
|
-
counterparty: {
|
|
802
|
-
type: "unknown",
|
|
803
|
-
address: senderAddress,
|
|
804
|
-
name: `${senderAddress.slice(0, 8)}...`
|
|
805
|
-
},
|
|
806
|
-
confidence: isObserverMode ? 0.9 : 0.95,
|
|
807
|
-
isRelevant: true,
|
|
808
|
-
metadata: {
|
|
809
|
-
...isObserverMode && { observer_mode: true, receiver: participantAddress },
|
|
810
|
-
...facilitator && { facilitator, payment_type: "facilitated" }
|
|
811
|
-
}
|
|
812
|
-
};
|
|
742
|
+
if (!receiverLeg) return null;
|
|
743
|
+
const receiver = receiverLeg.accountId.replace("external:", "");
|
|
744
|
+
return {
|
|
745
|
+
primaryType: "transfer",
|
|
746
|
+
primaryAmount: senderLeg.amount,
|
|
747
|
+
secondaryAmount: null,
|
|
748
|
+
sender,
|
|
749
|
+
receiver,
|
|
750
|
+
counterparty: {
|
|
751
|
+
type: "wallet",
|
|
752
|
+
address: receiver,
|
|
753
|
+
name: `${receiver.slice(0, 8)}...`
|
|
754
|
+
},
|
|
755
|
+
confidence: 0.95,
|
|
756
|
+
isRelevant: true,
|
|
757
|
+
metadata: {
|
|
758
|
+
...facilitator && { facilitator, payment_type: "facilitated" }
|
|
813
759
|
}
|
|
814
|
-
}
|
|
815
|
-
return null;
|
|
760
|
+
};
|
|
816
761
|
}
|
|
817
762
|
};
|
|
818
763
|
|
|
@@ -857,9 +802,54 @@ var KNOWN_PROGRAMS = {
|
|
|
857
802
|
[STAKE_PROGRAM_ID]: {
|
|
858
803
|
id: "stake",
|
|
859
804
|
name: "Stake Program"
|
|
805
|
+
},
|
|
806
|
+
[STAKE_POOL_PROGRAM_ID]: {
|
|
807
|
+
id: "stake-pool",
|
|
808
|
+
name: "Stake Pool Program"
|
|
809
|
+
},
|
|
810
|
+
[CANDY_GUARD_PROGRAM_ID]: {
|
|
811
|
+
id: "candy-guard",
|
|
812
|
+
name: "Metaplex Candy Guard Program"
|
|
813
|
+
},
|
|
814
|
+
[CANDY_MACHINE_V3_PROGRAM_ID]: {
|
|
815
|
+
id: "candy-machine-v3",
|
|
816
|
+
name: "Metaplex Candy Machine Core Program"
|
|
817
|
+
},
|
|
818
|
+
[BUBBLEGUM_PROGRAM_ID]: {
|
|
819
|
+
id: "bubblegum",
|
|
820
|
+
name: "Bubblegum Program"
|
|
821
|
+
},
|
|
822
|
+
[MAGIC_EDEN_CANDY_MACHINE_ID]: {
|
|
823
|
+
id: "magic-eden-candy-machine",
|
|
824
|
+
name: "Nft Candy Machine Program (Magic Eden)"
|
|
825
|
+
},
|
|
826
|
+
[WORMHOLE_PROGRAM_ID]: {
|
|
827
|
+
id: "wormhole",
|
|
828
|
+
name: "Wormhole"
|
|
829
|
+
},
|
|
830
|
+
[WORMHOLE_TOKEN_BRIDGE_ID]: {
|
|
831
|
+
id: "wormhole-token-bridge",
|
|
832
|
+
name: "Wormhole Token Bridge"
|
|
833
|
+
},
|
|
834
|
+
[DEGODS_BRIDGE_PROGRAM_ID]: {
|
|
835
|
+
id: "degods-bridge",
|
|
836
|
+
name: "DeGods Bridge"
|
|
837
|
+
},
|
|
838
|
+
[DEBRIDGE_PROGRAM_ID]: {
|
|
839
|
+
id: "debridge",
|
|
840
|
+
name: "deBridge"
|
|
841
|
+
},
|
|
842
|
+
[ALLBRIDGE_PROGRAM_ID]: {
|
|
843
|
+
id: "allbridge",
|
|
844
|
+
name: "Allbridge"
|
|
860
845
|
}
|
|
861
846
|
};
|
|
862
847
|
var PRIORITY_ORDER = [
|
|
848
|
+
"wormhole",
|
|
849
|
+
"wormhole-token-bridge",
|
|
850
|
+
"degods-bridge",
|
|
851
|
+
"debridge",
|
|
852
|
+
"allbridge",
|
|
863
853
|
"jupiter",
|
|
864
854
|
"jupiter-v4",
|
|
865
855
|
"raydium",
|
|
@@ -877,9 +867,33 @@ var DEX_PROTOCOL_IDS2 = /* @__PURE__ */ new Set([
|
|
|
877
867
|
"raydium",
|
|
878
868
|
"orca-whirlpool"
|
|
879
869
|
]);
|
|
870
|
+
var NFT_MINT_PROTOCOL_IDS = /* @__PURE__ */ new Set([
|
|
871
|
+
"metaplex",
|
|
872
|
+
"candy-machine-v3",
|
|
873
|
+
"candy-guard",
|
|
874
|
+
"bubblegum",
|
|
875
|
+
"magic-eden-candy-machine"
|
|
876
|
+
]);
|
|
877
|
+
var STAKE_PROTOCOL_IDS = /* @__PURE__ */ new Set(["stake", "stake-pool"]);
|
|
878
|
+
var BRIDGE_PROTOCOL_IDS = /* @__PURE__ */ new Set([
|
|
879
|
+
"wormhole",
|
|
880
|
+
"wormhole-token-bridge",
|
|
881
|
+
"degods-bridge",
|
|
882
|
+
"debridge",
|
|
883
|
+
"allbridge"
|
|
884
|
+
]);
|
|
880
885
|
function isDexProtocolById(protocolId) {
|
|
881
886
|
return protocolId !== void 0 && DEX_PROTOCOL_IDS2.has(protocolId);
|
|
882
887
|
}
|
|
888
|
+
function isNftMintProtocolById(protocolId) {
|
|
889
|
+
return protocolId !== void 0 && NFT_MINT_PROTOCOL_IDS.has(protocolId);
|
|
890
|
+
}
|
|
891
|
+
function isStakeProtocolById(protocolId) {
|
|
892
|
+
return protocolId !== void 0 && STAKE_PROTOCOL_IDS.has(protocolId);
|
|
893
|
+
}
|
|
894
|
+
function isBridgeProtocolById(protocolId) {
|
|
895
|
+
return protocolId !== void 0 && BRIDGE_PROTOCOL_IDS.has(protocolId);
|
|
896
|
+
}
|
|
883
897
|
function detectProtocol(programIds) {
|
|
884
898
|
const detectedProtocols = [];
|
|
885
899
|
for (const programId of programIds) {
|
|
@@ -904,65 +918,46 @@ var SwapClassifier = class {
|
|
|
904
918
|
name = "swap";
|
|
905
919
|
priority = 80;
|
|
906
920
|
classify(context) {
|
|
907
|
-
const { legs,
|
|
908
|
-
const protocolLegs = legs.filter(
|
|
909
|
-
(leg) => leg.accountId.startsWith("protocol:")
|
|
910
|
-
);
|
|
921
|
+
const { legs, tx } = context;
|
|
911
922
|
const hasDexProtocol = isDexProtocolById(tx.protocol?.id);
|
|
912
|
-
if (
|
|
923
|
+
if (!hasDexProtocol) {
|
|
913
924
|
return null;
|
|
914
925
|
}
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
if (walletAddress) {
|
|
918
|
-
participantPrefix = `wallet:${walletAddress}`;
|
|
919
|
-
} else {
|
|
920
|
-
const feeLeg = legs.find(
|
|
921
|
-
(leg) => leg.role === "fee" && leg.accountId.startsWith("external:")
|
|
922
|
-
);
|
|
923
|
-
if (!feeLeg) return null;
|
|
924
|
-
const feePayerAddress = feeLeg.accountId.replace("external:", "");
|
|
925
|
-
participantPrefix = `external:${feePayerAddress}`;
|
|
926
|
-
}
|
|
927
|
-
const participantDebits = legs.filter(
|
|
928
|
-
(leg) => leg.accountId.startsWith(participantPrefix) && leg.side === "debit"
|
|
929
|
-
);
|
|
930
|
-
const participantCredits = legs.filter(
|
|
931
|
-
(leg) => leg.accountId.startsWith(participantPrefix) && leg.side === "credit"
|
|
926
|
+
const feeLeg = legs.find(
|
|
927
|
+
(leg) => leg.role === "fee" && leg.side === "debit"
|
|
932
928
|
);
|
|
933
|
-
const
|
|
934
|
-
|
|
929
|
+
const initiator = feeLeg?.accountId.replace("external:", "") ?? null;
|
|
930
|
+
const tokensOut = legs.filter(
|
|
931
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && (leg.role === "sent" || leg.role === "protocol_deposit")
|
|
935
932
|
);
|
|
936
|
-
const tokensIn =
|
|
937
|
-
(leg) => leg.role === "received" || leg.role === "protocol_withdraw"
|
|
933
|
+
const tokensIn = legs.filter(
|
|
934
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
|
|
938
935
|
);
|
|
939
936
|
if (tokensOut.length === 0 || tokensIn.length === 0) {
|
|
940
937
|
return null;
|
|
941
938
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
return {
|
|
947
|
-
primaryType: "swap",
|
|
948
|
-
direction: "neutral",
|
|
949
|
-
primaryAmount: tokenOut.amount,
|
|
950
|
-
secondaryAmount: tokenIn.amount,
|
|
951
|
-
counterparty: null,
|
|
952
|
-
confidence: isObserverMode ? 0.85 : 0.9,
|
|
953
|
-
isRelevant: true,
|
|
954
|
-
metadata: {
|
|
955
|
-
swap_type: "token_to_token",
|
|
956
|
-
from_token: tokenOut.amount.token.symbol,
|
|
957
|
-
to_token: tokenIn.amount.token.symbol,
|
|
958
|
-
from_amount: tokenOut.amount.amountUi,
|
|
959
|
-
to_amount: tokenIn.amount.amountUi,
|
|
960
|
-
...isObserverMode && { observer_mode: true }
|
|
961
|
-
}
|
|
962
|
-
};
|
|
963
|
-
}
|
|
939
|
+
const tokenOut = tokensOut[0];
|
|
940
|
+
const tokenIn = tokensIn[0];
|
|
941
|
+
if (tokenOut.amount.token.symbol === tokenIn.amount.token.symbol) {
|
|
942
|
+
return null;
|
|
964
943
|
}
|
|
965
|
-
return
|
|
944
|
+
return {
|
|
945
|
+
primaryType: "swap",
|
|
946
|
+
primaryAmount: tokenOut.amount,
|
|
947
|
+
secondaryAmount: tokenIn.amount,
|
|
948
|
+
sender: initiator,
|
|
949
|
+
receiver: initiator,
|
|
950
|
+
counterparty: null,
|
|
951
|
+
confidence: 0.9,
|
|
952
|
+
isRelevant: true,
|
|
953
|
+
metadata: {
|
|
954
|
+
swap_type: "token_to_token",
|
|
955
|
+
from_token: tokenOut.amount.token.symbol,
|
|
956
|
+
to_token: tokenIn.amount.token.symbol,
|
|
957
|
+
from_amount: tokenOut.amount.amountUi,
|
|
958
|
+
to_amount: tokenIn.amount.amountUi
|
|
959
|
+
}
|
|
960
|
+
};
|
|
966
961
|
}
|
|
967
962
|
};
|
|
968
963
|
|
|
@@ -971,7 +966,7 @@ var AirdropClassifier = class {
|
|
|
971
966
|
name = "airdrop";
|
|
972
967
|
priority = 70;
|
|
973
968
|
classify(context) {
|
|
974
|
-
const { legs,
|
|
969
|
+
const { legs, tx } = context;
|
|
975
970
|
const facilitator = tx.accountKeys ? detectFacilitator(tx.accountKeys) : null;
|
|
976
971
|
const protocolLegs = legs.filter(
|
|
977
972
|
(leg) => leg.accountId.startsWith("protocol:")
|
|
@@ -979,49 +974,41 @@ var AirdropClassifier = class {
|
|
|
979
974
|
if (protocolLegs.length === 0) {
|
|
980
975
|
return null;
|
|
981
976
|
}
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
const participantCredits = legs.filter(
|
|
985
|
-
(leg) => leg.accountId.startsWith(accountPrefix) && leg.side === "credit"
|
|
986
|
-
);
|
|
987
|
-
const participantDebits = legs.filter(
|
|
988
|
-
(leg) => leg.accountId.startsWith(accountPrefix) && leg.side === "debit"
|
|
989
|
-
);
|
|
990
|
-
const tokenReceived = participantCredits.filter(
|
|
991
|
-
(leg) => leg.role === "received" && leg.amount.token.symbol !== "SOL"
|
|
977
|
+
const tokenReceived = legs.filter(
|
|
978
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && leg.role === "received" && leg.amount.token.symbol !== "SOL"
|
|
992
979
|
);
|
|
993
980
|
if (tokenReceived.length === 0) {
|
|
994
981
|
return null;
|
|
995
982
|
}
|
|
996
|
-
const tokenSent =
|
|
997
|
-
(leg) => leg.role === "sent" && leg.amount.token.symbol !== "SOL"
|
|
983
|
+
const tokenSent = legs.filter(
|
|
984
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.role === "sent" && leg.amount.token.symbol !== "SOL"
|
|
998
985
|
);
|
|
999
986
|
if (tokenSent.length > 0) {
|
|
1000
987
|
return null;
|
|
1001
988
|
}
|
|
1002
989
|
const mainToken = tokenReceived[0];
|
|
1003
|
-
const
|
|
1004
|
-
|
|
990
|
+
const receiver = mainToken.accountId.replace("external:", "");
|
|
991
|
+
const senderLeg = legs.find(
|
|
992
|
+
(leg) => leg.side === "debit" && leg.amount.token.mint === mainToken.amount.token.mint
|
|
1005
993
|
);
|
|
1006
|
-
const sender =
|
|
1007
|
-
const receiverAddress = mainToken.accountId.replace(/^(external:|wallet:)/, "");
|
|
994
|
+
const sender = senderLeg ? senderLeg.accountId.replace(/^(external:|protocol:)/, "") : null;
|
|
1008
995
|
return {
|
|
1009
996
|
primaryType: "airdrop",
|
|
1010
|
-
direction: isObserverMode ? "neutral" : "incoming",
|
|
1011
997
|
primaryAmount: mainToken.amount,
|
|
1012
998
|
secondaryAmount: null,
|
|
999
|
+
sender,
|
|
1000
|
+
receiver,
|
|
1013
1001
|
counterparty: sender ? {
|
|
1014
|
-
type: "
|
|
1015
|
-
address: sender
|
|
1002
|
+
type: "protocol",
|
|
1003
|
+
address: sender
|
|
1016
1004
|
} : null,
|
|
1017
|
-
confidence:
|
|
1005
|
+
confidence: 0.85,
|
|
1018
1006
|
isRelevant: true,
|
|
1019
1007
|
metadata: {
|
|
1020
1008
|
airdrop_type: "token",
|
|
1021
1009
|
token: mainToken.amount.token.symbol,
|
|
1022
1010
|
amount: mainToken.amount.amountUi,
|
|
1023
|
-
...
|
|
1024
|
-
...facilitator && { facilitator, payment_type: "facilitated" }
|
|
1011
|
+
...facilitator && { facilitator }
|
|
1025
1012
|
}
|
|
1026
1013
|
};
|
|
1027
1014
|
}
|
|
@@ -1032,30 +1019,34 @@ var FeeOnlyClassifier = class {
|
|
|
1032
1019
|
name = "fee-only";
|
|
1033
1020
|
priority = 60;
|
|
1034
1021
|
classify(context) {
|
|
1035
|
-
const { legs
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
if (
|
|
1022
|
+
const { legs } = context;
|
|
1023
|
+
const externalLegs = legs.filter(
|
|
1024
|
+
(leg) => leg.accountId.startsWith("external:")
|
|
1025
|
+
);
|
|
1026
|
+
const nonFeeLegs = externalLegs.filter((leg) => leg.role !== "fee");
|
|
1027
|
+
if (nonFeeLegs.length > 0) {
|
|
1041
1028
|
return null;
|
|
1042
1029
|
}
|
|
1043
1030
|
const feeLegs = legs.filter((leg) => leg.role === "fee");
|
|
1044
1031
|
if (feeLegs.length === 0) {
|
|
1045
1032
|
return null;
|
|
1046
1033
|
}
|
|
1034
|
+
const feePayerLeg = feeLegs.find(
|
|
1035
|
+
(leg) => leg.side === "debit" && leg.amount.token.symbol === "SOL"
|
|
1036
|
+
);
|
|
1037
|
+
const feePayer = feePayerLeg?.accountId.replace("external:", "") ?? null;
|
|
1047
1038
|
const totalFee = feeLegs.find((leg) => leg.amount.token.symbol === "SOL");
|
|
1048
1039
|
return {
|
|
1049
1040
|
primaryType: "fee_only",
|
|
1050
|
-
direction: "outgoing",
|
|
1051
1041
|
primaryAmount: totalFee?.amount ?? null,
|
|
1052
1042
|
secondaryAmount: null,
|
|
1043
|
+
sender: feePayer,
|
|
1044
|
+
receiver: null,
|
|
1053
1045
|
counterparty: null,
|
|
1054
|
-
confidence:
|
|
1046
|
+
confidence: 0.95,
|
|
1055
1047
|
isRelevant: false,
|
|
1056
1048
|
metadata: {
|
|
1057
|
-
fee_type: "network"
|
|
1058
|
-
...isObserverMode && { observer_mode: true }
|
|
1049
|
+
fee_type: "network"
|
|
1059
1050
|
}
|
|
1060
1051
|
};
|
|
1061
1052
|
}
|
|
@@ -1066,59 +1057,31 @@ var SolanaPayClassifier = class {
|
|
|
1066
1057
|
name = "solana-pay";
|
|
1067
1058
|
priority = 95;
|
|
1068
1059
|
classify(context) {
|
|
1069
|
-
const { tx,
|
|
1060
|
+
const { tx, legs } = context;
|
|
1070
1061
|
if (!isSolanaPayTransaction(tx.programIds, tx.memo)) {
|
|
1071
1062
|
return null;
|
|
1072
1063
|
}
|
|
1073
1064
|
const memo = parseSolanaPayMemo(tx.memo);
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
const senderLeg = legs.find(
|
|
1077
|
-
(leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.role === "sent"
|
|
1078
|
-
);
|
|
1079
|
-
const receiverLeg = legs.find(
|
|
1080
|
-
(leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && leg.role === "received"
|
|
1081
|
-
);
|
|
1082
|
-
const primaryAmount2 = senderLeg?.amount ?? receiverLeg?.amount ?? null;
|
|
1083
|
-
const senderAddress = senderLeg?.accountId.replace("external:", "");
|
|
1084
|
-
const receiverAddress = receiverLeg?.accountId.replace("external:", "");
|
|
1085
|
-
return {
|
|
1086
|
-
primaryType: "transfer",
|
|
1087
|
-
direction: "neutral",
|
|
1088
|
-
primaryAmount: primaryAmount2,
|
|
1089
|
-
secondaryAmount: null,
|
|
1090
|
-
counterparty: memo.merchant ? { address: receiverAddress ?? "", name: memo.merchant, type: "merchant" } : null,
|
|
1091
|
-
confidence: 0.95,
|
|
1092
|
-
isRelevant: true,
|
|
1093
|
-
metadata: {
|
|
1094
|
-
payment_type: "solana_pay",
|
|
1095
|
-
observer_mode: true,
|
|
1096
|
-
sender: senderAddress,
|
|
1097
|
-
receiver: receiverAddress,
|
|
1098
|
-
memo: memo.raw,
|
|
1099
|
-
merchant: memo.merchant,
|
|
1100
|
-
item: memo.item,
|
|
1101
|
-
reference: memo.reference,
|
|
1102
|
-
label: memo.label,
|
|
1103
|
-
message: memo.message
|
|
1104
|
-
}
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
const walletPrefix = `wallet:${walletAddress}`;
|
|
1108
|
-
const userSent = legs.find(
|
|
1109
|
-
(leg) => leg.accountId.includes(walletPrefix) && leg.side === "debit" && leg.role === "sent"
|
|
1065
|
+
const senderLeg = legs.find(
|
|
1066
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.role === "sent"
|
|
1110
1067
|
);
|
|
1111
|
-
const
|
|
1112
|
-
(leg) => leg.accountId.
|
|
1068
|
+
const receiverLeg = legs.find(
|
|
1069
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && leg.role === "received"
|
|
1113
1070
|
);
|
|
1114
|
-
const
|
|
1115
|
-
const
|
|
1071
|
+
const sender = senderLeg?.accountId.replace("external:", "") ?? null;
|
|
1072
|
+
const receiver = receiverLeg?.accountId.replace("external:", "") ?? null;
|
|
1073
|
+
const primaryAmount = senderLeg?.amount ?? receiverLeg?.amount ?? null;
|
|
1116
1074
|
return {
|
|
1117
1075
|
primaryType: "transfer",
|
|
1118
|
-
direction,
|
|
1119
1076
|
primaryAmount,
|
|
1120
1077
|
secondaryAmount: null,
|
|
1121
|
-
|
|
1078
|
+
sender,
|
|
1079
|
+
receiver,
|
|
1080
|
+
counterparty: receiver ? {
|
|
1081
|
+
address: receiver,
|
|
1082
|
+
name: memo.merchant ?? void 0,
|
|
1083
|
+
type: memo.merchant ? "merchant" : "wallet"
|
|
1084
|
+
} : null,
|
|
1122
1085
|
confidence: 0.98,
|
|
1123
1086
|
isRelevant: true,
|
|
1124
1087
|
metadata: {
|
|
@@ -1134,11 +1097,188 @@ var SolanaPayClassifier = class {
|
|
|
1134
1097
|
}
|
|
1135
1098
|
};
|
|
1136
1099
|
|
|
1100
|
+
// ../classification/src/classifiers/nft-mint-classifier.ts
|
|
1101
|
+
var NftMintClassifier = class {
|
|
1102
|
+
name = "nft-mint";
|
|
1103
|
+
priority = 85;
|
|
1104
|
+
classify(context) {
|
|
1105
|
+
const { legs, tx } = context;
|
|
1106
|
+
const hasNftMintProtocol = isNftMintProtocolById(tx.protocol?.id);
|
|
1107
|
+
if (!hasNftMintProtocol) {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
const nftCredits = legs.filter(
|
|
1111
|
+
(leg) => leg.side === "credit" && leg.amount.token.decimals === 0 && leg.amount.amountUi >= 1 && (leg.role === "received" || leg.role === "protocol_withdraw")
|
|
1112
|
+
);
|
|
1113
|
+
if (nftCredits.length === 0) {
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
const primaryNft = nftCredits[0];
|
|
1117
|
+
const minter = primaryNft.accountId.replace("external:", "");
|
|
1118
|
+
const paymentLeg = legs.find(
|
|
1119
|
+
(leg) => leg.side === "debit" && leg.role === "sent" && leg.amount.token.symbol === "SOL"
|
|
1120
|
+
);
|
|
1121
|
+
const totalMinted = nftCredits.reduce(
|
|
1122
|
+
(sum, leg) => sum + leg.amount.amountUi,
|
|
1123
|
+
0
|
|
1124
|
+
);
|
|
1125
|
+
return {
|
|
1126
|
+
primaryType: "nft_mint",
|
|
1127
|
+
primaryAmount: primaryNft.amount,
|
|
1128
|
+
secondaryAmount: paymentLeg?.amount ?? null,
|
|
1129
|
+
sender: null,
|
|
1130
|
+
receiver: minter,
|
|
1131
|
+
counterparty: null,
|
|
1132
|
+
confidence: 0.9,
|
|
1133
|
+
isRelevant: true,
|
|
1134
|
+
metadata: {
|
|
1135
|
+
nft_mint: primaryNft.amount.token.mint,
|
|
1136
|
+
nft_name: primaryNft.amount.token.name,
|
|
1137
|
+
quantity: totalMinted,
|
|
1138
|
+
mint_price: paymentLeg?.amount.amountUi,
|
|
1139
|
+
protocol: tx.protocol?.id
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
// ../classification/src/classifiers/stake-deposit-classifier.ts
|
|
1146
|
+
var StakeDepositClassifier = class {
|
|
1147
|
+
name = "stake-deposit";
|
|
1148
|
+
priority = 82;
|
|
1149
|
+
classify(context) {
|
|
1150
|
+
const { legs, tx } = context;
|
|
1151
|
+
const hasStakeProtocol = isStakeProtocolById(tx.protocol?.id);
|
|
1152
|
+
if (!hasStakeProtocol) {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
const solDebit = legs.find(
|
|
1156
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.amount.token.symbol === "SOL" && (leg.role === "sent" || leg.role === "protocol_deposit")
|
|
1157
|
+
);
|
|
1158
|
+
if (!solDebit) {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
const staker = solDebit.accountId.replace("external:", "");
|
|
1162
|
+
return {
|
|
1163
|
+
primaryType: "stake_deposit",
|
|
1164
|
+
primaryAmount: solDebit.amount,
|
|
1165
|
+
secondaryAmount: null,
|
|
1166
|
+
sender: staker,
|
|
1167
|
+
receiver: null,
|
|
1168
|
+
counterparty: null,
|
|
1169
|
+
confidence: 0.9,
|
|
1170
|
+
isRelevant: true,
|
|
1171
|
+
metadata: {
|
|
1172
|
+
stake_amount: solDebit.amount.amountUi,
|
|
1173
|
+
protocol: tx.protocol?.id
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
// ../classification/src/classifiers/stake-withdraw-classifier.ts
|
|
1180
|
+
var StakeWithdrawClassifier = class {
|
|
1181
|
+
name = "stake-withdraw";
|
|
1182
|
+
priority = 81;
|
|
1183
|
+
classify(context) {
|
|
1184
|
+
const { legs, tx } = context;
|
|
1185
|
+
const hasStakeProtocol = isStakeProtocolById(tx.protocol?.id);
|
|
1186
|
+
if (!hasStakeProtocol) {
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
const solCredit = legs.find(
|
|
1190
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && leg.amount.token.symbol === "SOL" && (leg.role === "received" || leg.role === "protocol_withdraw")
|
|
1191
|
+
);
|
|
1192
|
+
if (!solCredit) {
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
const solDebit = legs.find(
|
|
1196
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.amount.token.symbol === "SOL" && (leg.role === "sent" || leg.role === "protocol_deposit")
|
|
1197
|
+
);
|
|
1198
|
+
if (solDebit) {
|
|
1199
|
+
return null;
|
|
1200
|
+
}
|
|
1201
|
+
const withdrawer = solCredit.accountId.replace("external:", "");
|
|
1202
|
+
return {
|
|
1203
|
+
primaryType: "stake_withdraw",
|
|
1204
|
+
primaryAmount: solCredit.amount,
|
|
1205
|
+
secondaryAmount: null,
|
|
1206
|
+
sender: null,
|
|
1207
|
+
receiver: withdrawer,
|
|
1208
|
+
counterparty: null,
|
|
1209
|
+
confidence: 0.9,
|
|
1210
|
+
isRelevant: true,
|
|
1211
|
+
metadata: {
|
|
1212
|
+
withdraw_amount: solCredit.amount.amountUi,
|
|
1213
|
+
protocol: tx.protocol?.id
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
// ../classification/src/classifiers/bridge-classifier.ts
|
|
1220
|
+
var BridgeClassifier = class {
|
|
1221
|
+
name = "bridge";
|
|
1222
|
+
priority = 88;
|
|
1223
|
+
classify(context) {
|
|
1224
|
+
const { legs, tx } = context;
|
|
1225
|
+
const hasBridgeProtocol = isBridgeProtocolById(tx.protocol?.id);
|
|
1226
|
+
if (!hasBridgeProtocol) {
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
const tokensOut = legs.filter(
|
|
1230
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "debit" && leg.role !== "fee" && (leg.role === "sent" || leg.role === "protocol_deposit")
|
|
1231
|
+
);
|
|
1232
|
+
const tokensIn = legs.filter(
|
|
1233
|
+
(leg) => leg.accountId.startsWith("external:") && leg.side === "credit" && (leg.role === "received" || leg.role === "protocol_withdraw")
|
|
1234
|
+
);
|
|
1235
|
+
let primaryType;
|
|
1236
|
+
let primaryAmount = null;
|
|
1237
|
+
let participant = null;
|
|
1238
|
+
if (tokensIn.length > 0 && tokensOut.length === 0) {
|
|
1239
|
+
primaryType = "bridge_in";
|
|
1240
|
+
const creditLeg = tokensIn[0];
|
|
1241
|
+
primaryAmount = creditLeg.amount;
|
|
1242
|
+
participant = creditLeg.accountId.replace("external:", "");
|
|
1243
|
+
} else if (tokensOut.length > 0 && tokensIn.length === 0) {
|
|
1244
|
+
primaryType = "bridge_out";
|
|
1245
|
+
const debitLeg = tokensOut[0];
|
|
1246
|
+
primaryAmount = debitLeg.amount;
|
|
1247
|
+
participant = debitLeg.accountId.replace("external:", "");
|
|
1248
|
+
} else if (tokensIn.length > 0 && tokensOut.length > 0) {
|
|
1249
|
+
primaryType = "bridge_in";
|
|
1250
|
+
const creditLeg = tokensIn[0];
|
|
1251
|
+
primaryAmount = creditLeg.amount;
|
|
1252
|
+
participant = creditLeg.accountId.replace("external:", "");
|
|
1253
|
+
} else {
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
return {
|
|
1257
|
+
primaryType,
|
|
1258
|
+
primaryAmount,
|
|
1259
|
+
secondaryAmount: null,
|
|
1260
|
+
sender: primaryType === "bridge_out" ? participant : null,
|
|
1261
|
+
receiver: primaryType === "bridge_in" ? participant : null,
|
|
1262
|
+
counterparty: null,
|
|
1263
|
+
confidence: 0.9,
|
|
1264
|
+
isRelevant: true,
|
|
1265
|
+
metadata: {
|
|
1266
|
+
bridge_protocol: tx.protocol?.id,
|
|
1267
|
+
bridge_name: tx.protocol?.name
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
|
|
1137
1273
|
// ../classification/src/engine/classification-service.ts
|
|
1138
1274
|
var ClassificationService = class {
|
|
1139
1275
|
classifiers = [];
|
|
1140
1276
|
constructor() {
|
|
1141
1277
|
this.registerClassifier(new SolanaPayClassifier());
|
|
1278
|
+
this.registerClassifier(new BridgeClassifier());
|
|
1279
|
+
this.registerClassifier(new NftMintClassifier());
|
|
1280
|
+
this.registerClassifier(new StakeDepositClassifier());
|
|
1281
|
+
this.registerClassifier(new StakeWithdrawClassifier());
|
|
1142
1282
|
this.registerClassifier(new SwapClassifier());
|
|
1143
1283
|
this.registerClassifier(new AirdropClassifier());
|
|
1144
1284
|
this.registerClassifier(new TransferClassifier());
|
|
@@ -1148,8 +1288,8 @@ var ClassificationService = class {
|
|
|
1148
1288
|
this.classifiers.push(classifier);
|
|
1149
1289
|
this.classifiers.sort((a, b) => b.priority - a.priority);
|
|
1150
1290
|
}
|
|
1151
|
-
classify(legs,
|
|
1152
|
-
const context = { legs,
|
|
1291
|
+
classify(legs, tx) {
|
|
1292
|
+
const context = { legs, tx };
|
|
1153
1293
|
for (const classifier of this.classifiers) {
|
|
1154
1294
|
const result = classifier.classify(context);
|
|
1155
1295
|
if (result && result.isRelevant) {
|
|
@@ -1158,9 +1298,10 @@ var ClassificationService = class {
|
|
|
1158
1298
|
}
|
|
1159
1299
|
return {
|
|
1160
1300
|
primaryType: "other",
|
|
1161
|
-
direction: "neutral",
|
|
1162
1301
|
primaryAmount: null,
|
|
1163
1302
|
secondaryAmount: null,
|
|
1303
|
+
sender: null,
|
|
1304
|
+
receiver: null,
|
|
1164
1305
|
counterparty: null,
|
|
1165
1306
|
confidence: 0,
|
|
1166
1307
|
isRelevant: false,
|
|
@@ -1169,8 +1310,8 @@ var ClassificationService = class {
|
|
|
1169
1310
|
}
|
|
1170
1311
|
};
|
|
1171
1312
|
var classificationService = new ClassificationService();
|
|
1172
|
-
function classifyTransaction(legs,
|
|
1173
|
-
return classificationService.classify(legs,
|
|
1313
|
+
function classifyTransaction(legs, tx) {
|
|
1314
|
+
return classificationService.classify(legs, tx);
|
|
1174
1315
|
}
|
|
1175
1316
|
|
|
1176
1317
|
// ../domain/src/tx/spam-filter.ts
|
|
@@ -1216,8 +1357,81 @@ function filterSpamTransactions(transactions, config) {
|
|
|
1216
1357
|
);
|
|
1217
1358
|
}
|
|
1218
1359
|
|
|
1360
|
+
// src/nft.ts
|
|
1361
|
+
async function fetchNftMetadata(rpcUrl, mintAddress) {
|
|
1362
|
+
const response = await fetch(rpcUrl, {
|
|
1363
|
+
method: "POST",
|
|
1364
|
+
headers: { "Content-Type": "application/json" },
|
|
1365
|
+
body: JSON.stringify({
|
|
1366
|
+
jsonrpc: "2.0",
|
|
1367
|
+
id: "get-asset",
|
|
1368
|
+
method: "getAsset",
|
|
1369
|
+
params: { id: mintAddress }
|
|
1370
|
+
})
|
|
1371
|
+
});
|
|
1372
|
+
const data = await response.json();
|
|
1373
|
+
if (data.error || !data.result?.content?.metadata) {
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
const { result } = data;
|
|
1377
|
+
const { content, grouping } = result;
|
|
1378
|
+
return {
|
|
1379
|
+
mint: mintAddress,
|
|
1380
|
+
name: content.metadata.name,
|
|
1381
|
+
symbol: content.metadata.symbol,
|
|
1382
|
+
image: content.links.image ?? content.files?.[0]?.uri ?? "",
|
|
1383
|
+
cdnImage: content.files?.[0]?.cdn_uri,
|
|
1384
|
+
description: content.metadata.description,
|
|
1385
|
+
collection: grouping?.find((g) => g.group_key === "collection")?.group_value,
|
|
1386
|
+
attributes: content.metadata.attributes
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
async function fetchNftMetadataBatch(rpcUrl, mintAddresses) {
|
|
1390
|
+
const results = await Promise.all(
|
|
1391
|
+
mintAddresses.map((mint) => fetchNftMetadata(rpcUrl, mint))
|
|
1392
|
+
);
|
|
1393
|
+
const map = /* @__PURE__ */ new Map();
|
|
1394
|
+
results.forEach((metadata, index) => {
|
|
1395
|
+
if (metadata) {
|
|
1396
|
+
map.set(mintAddresses[index], metadata);
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
return map;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1219
1402
|
// src/client.ts
|
|
1403
|
+
var NFT_TRANSACTION_TYPES = ["nft_mint", "nft_purchase", "nft_sale"];
|
|
1404
|
+
async function enrichNftClassification(rpcUrl, classified) {
|
|
1405
|
+
const { classification } = classified;
|
|
1406
|
+
if (!NFT_TRANSACTION_TYPES.includes(classification.primaryType)) {
|
|
1407
|
+
return classified;
|
|
1408
|
+
}
|
|
1409
|
+
const nftMint = classification.metadata?.nft_mint;
|
|
1410
|
+
if (!nftMint) {
|
|
1411
|
+
return classified;
|
|
1412
|
+
}
|
|
1413
|
+
const nftData = await fetchNftMetadata(rpcUrl, nftMint);
|
|
1414
|
+
if (!nftData) {
|
|
1415
|
+
return classified;
|
|
1416
|
+
}
|
|
1417
|
+
return {
|
|
1418
|
+
...classified,
|
|
1419
|
+
classification: {
|
|
1420
|
+
...classification,
|
|
1421
|
+
metadata: {
|
|
1422
|
+
...classification.metadata,
|
|
1423
|
+
nft_name: nftData.name,
|
|
1424
|
+
nft_image: nftData.image,
|
|
1425
|
+
nft_cdn_image: nftData.cdnImage,
|
|
1426
|
+
nft_collection: nftData.collection,
|
|
1427
|
+
nft_symbol: nftData.symbol,
|
|
1428
|
+
nft_attributes: nftData.attributes
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1220
1433
|
function createIndexer(options) {
|
|
1434
|
+
const rpcUrl = "client" in options ? "" : options.rpcUrl;
|
|
1221
1435
|
const client = "client" in options ? options.client : createSolanaClient(options.rpcUrl, options.wsUrl);
|
|
1222
1436
|
return {
|
|
1223
1437
|
rpc: client.rpc,
|
|
@@ -1225,7 +1439,39 @@ function createIndexer(options) {
|
|
|
1225
1439
|
return fetchWalletBalance(client.rpc, walletAddress, tokenMints);
|
|
1226
1440
|
},
|
|
1227
1441
|
async getTransactions(walletAddress, options2 = {}) {
|
|
1228
|
-
const { limit = 10, before, until, filterSpam = true, spamConfig } = options2;
|
|
1442
|
+
const { limit = 10, before, until, filterSpam = true, spamConfig, enrichNftMetadata = true } = options2;
|
|
1443
|
+
async function enrichBatch(transactions) {
|
|
1444
|
+
if (!enrichNftMetadata || !rpcUrl) {
|
|
1445
|
+
return transactions;
|
|
1446
|
+
}
|
|
1447
|
+
const nftMints = transactions.filter((t) => NFT_TRANSACTION_TYPES.includes(t.classification.primaryType)).map((t) => t.classification.metadata?.nft_mint).filter(Boolean);
|
|
1448
|
+
if (nftMints.length === 0) {
|
|
1449
|
+
return transactions;
|
|
1450
|
+
}
|
|
1451
|
+
const nftMetadataMap = await fetchNftMetadataBatch(rpcUrl, nftMints);
|
|
1452
|
+
return transactions.map((t) => {
|
|
1453
|
+
const nftMint = t.classification.metadata?.nft_mint;
|
|
1454
|
+
if (!nftMint || !nftMetadataMap.has(nftMint)) {
|
|
1455
|
+
return t;
|
|
1456
|
+
}
|
|
1457
|
+
const nftData = nftMetadataMap.get(nftMint);
|
|
1458
|
+
return {
|
|
1459
|
+
...t,
|
|
1460
|
+
classification: {
|
|
1461
|
+
...t.classification,
|
|
1462
|
+
metadata: {
|
|
1463
|
+
...t.classification.metadata,
|
|
1464
|
+
nft_name: nftData.name,
|
|
1465
|
+
nft_image: nftData.image,
|
|
1466
|
+
nft_cdn_image: nftData.cdnImage,
|
|
1467
|
+
nft_collection: nftData.collection,
|
|
1468
|
+
nft_symbol: nftData.symbol,
|
|
1469
|
+
nft_attributes: nftData.attributes
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1229
1475
|
if (!filterSpam) {
|
|
1230
1476
|
const signatures = await fetchWalletSignatures(client.rpc, walletAddress, {
|
|
1231
1477
|
limit,
|
|
@@ -1244,11 +1490,11 @@ function createIndexer(options) {
|
|
|
1244
1490
|
);
|
|
1245
1491
|
const classified = transactions.map((tx) => {
|
|
1246
1492
|
tx.protocol = detectProtocol(tx.programIds);
|
|
1247
|
-
const legs = transactionToLegs(tx
|
|
1248
|
-
const classification = classifyTransaction(legs,
|
|
1493
|
+
const legs = transactionToLegs(tx);
|
|
1494
|
+
const classification = classifyTransaction(legs, tx);
|
|
1249
1495
|
return { tx, classification, legs };
|
|
1250
1496
|
});
|
|
1251
|
-
return classified;
|
|
1497
|
+
return enrichBatch(classified);
|
|
1252
1498
|
}
|
|
1253
1499
|
const accumulated = [];
|
|
1254
1500
|
let currentBefore = before;
|
|
@@ -1274,8 +1520,8 @@ function createIndexer(options) {
|
|
|
1274
1520
|
);
|
|
1275
1521
|
const classified = transactions.map((tx) => {
|
|
1276
1522
|
tx.protocol = detectProtocol(tx.programIds);
|
|
1277
|
-
const legs = transactionToLegs(tx
|
|
1278
|
-
const classification = classifyTransaction(legs,
|
|
1523
|
+
const legs = transactionToLegs(tx);
|
|
1524
|
+
const classification = classifyTransaction(legs, tx);
|
|
1279
1525
|
return { tx, classification, legs };
|
|
1280
1526
|
});
|
|
1281
1527
|
const nonSpam = filterSpamTransactions(classified, spamConfig);
|
|
@@ -1287,20 +1533,38 @@ function createIndexer(options) {
|
|
|
1287
1533
|
break;
|
|
1288
1534
|
}
|
|
1289
1535
|
}
|
|
1290
|
-
|
|
1536
|
+
const result = accumulated.slice(0, limit);
|
|
1537
|
+
return enrichBatch(result);
|
|
1291
1538
|
},
|
|
1292
|
-
async getTransaction(signature2,
|
|
1539
|
+
async getTransaction(signature2, options2 = {}) {
|
|
1540
|
+
const { enrichNftMetadata = true } = options2;
|
|
1293
1541
|
const tx = await fetchTransaction(client.rpc, signature2);
|
|
1294
1542
|
if (!tx) {
|
|
1295
1543
|
return null;
|
|
1296
1544
|
}
|
|
1297
1545
|
tx.protocol = detectProtocol(tx.programIds);
|
|
1298
|
-
const legs = transactionToLegs(tx
|
|
1299
|
-
const classification = classifyTransaction(legs,
|
|
1300
|
-
|
|
1546
|
+
const legs = transactionToLegs(tx);
|
|
1547
|
+
const classification = classifyTransaction(legs, tx);
|
|
1548
|
+
let classified = { tx, classification, legs };
|
|
1549
|
+
if (enrichNftMetadata && rpcUrl) {
|
|
1550
|
+
classified = await enrichNftClassification(rpcUrl, classified);
|
|
1551
|
+
}
|
|
1552
|
+
return classified;
|
|
1301
1553
|
},
|
|
1302
1554
|
async getRawTransaction(signature2) {
|
|
1303
1555
|
return fetchTransaction(client.rpc, signature2);
|
|
1556
|
+
},
|
|
1557
|
+
async getNftMetadata(mintAddress) {
|
|
1558
|
+
if (!rpcUrl) {
|
|
1559
|
+
throw new Error("getNftMetadata requires rpcUrl to be set");
|
|
1560
|
+
}
|
|
1561
|
+
return fetchNftMetadata(rpcUrl, mintAddress);
|
|
1562
|
+
},
|
|
1563
|
+
async getNftMetadataBatch(mintAddresses) {
|
|
1564
|
+
if (!rpcUrl) {
|
|
1565
|
+
throw new Error("getNftMetadataBatch requires rpcUrl to be set");
|
|
1566
|
+
}
|
|
1567
|
+
return fetchNftMetadataBatch(rpcUrl, mintAddresses);
|
|
1304
1568
|
}
|
|
1305
1569
|
};
|
|
1306
1570
|
}
|
|
@@ -1352,6 +1616,6 @@ function groupLegsByToken(legs) {
|
|
|
1352
1616
|
return grouped;
|
|
1353
1617
|
}
|
|
1354
1618
|
|
|
1355
|
-
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 };
|
|
1619
|
+
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 };
|
|
1356
1620
|
//# sourceMappingURL=index.js.map
|
|
1357
1621
|
//# sourceMappingURL=index.js.map
|