@clayno-club/asset-flow 0.1.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 ADDED
@@ -0,0 +1,149 @@
1
+ # @clayno-club/asset-flow
2
+
3
+ Deterministic Solana NFT asset-flow classification and normalization.
4
+
5
+ This package turns transaction data into a canonical ownership history for a mint. It is designed for consumers that want consistent classification results across services, codebases, or audit pipelines.
6
+
7
+ ## What it does
8
+
9
+ - classify enhanced Solana transactions for a tracked mint
10
+ - merge raw ownership evidence into enhanced transactions when needed
11
+ - normalize a mint's transaction history into canonical flows
12
+ - derive ownership periods from normalized flows
13
+ - explain why a transaction matched a given classification path
14
+ - analyze provided transaction sets for review candidates
15
+
16
+ ## What it does not do
17
+
18
+ - fetch transaction history from RPC providers or indexers
19
+ - persist results to a database
20
+ - manage queues, jobs, or orchestration
21
+
22
+ You provide the transaction data. The package provides deterministic interpretation.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pnpm add @clayno-club/asset-flow
28
+ ```
29
+
30
+ ## Core API
31
+
32
+ - `classifyEnhancedTransactionForMint(tx, mint)`
33
+ - `augmentEnhancedTransactionWithRaw(tx, rawTx)`
34
+ - `normalizeMintHistory(transactions, mint)`
35
+ - `explainEnhancedTransactionForMint(tx, mint)`
36
+ - `collectCoverageReviewCandidates(transactions, mint)`
37
+ - `clusterCoverageReviewCandidates(candidates)`
38
+ - `scanTransactionsForCoverage(transactions, mint)`
39
+
40
+ ## Example: classify one transaction
41
+
42
+ ```ts
43
+ import { classifyEnhancedTransactionForMint } from "@clayno-club/asset-flow";
44
+
45
+ const classification = classifyEnhancedTransactionForMint(tx, mint);
46
+
47
+ console.log(classification.family);
48
+ console.log(classification.derivedType);
49
+ console.log(classification.materialization);
50
+ ```
51
+
52
+ ## Example: augment enhanced data with raw ownership evidence
53
+
54
+ ```ts
55
+ import { augmentEnhancedTransactionWithRaw } from "@clayno-club/asset-flow";
56
+
57
+ const augmented = augmentEnhancedTransactionWithRaw(enhancedTx, rawTx);
58
+ ```
59
+
60
+ ## Example: normalize mint history
61
+
62
+ ```ts
63
+ import { normalizeMintHistory } from "@clayno-club/asset-flow";
64
+
65
+ const normalized = normalizeMintHistory(transactions, mint);
66
+
67
+ console.log(normalized.flows);
68
+ console.log(normalized.valueMovements);
69
+ console.log(normalized.periods);
70
+ ```
71
+
72
+ ## Example: explain a classification decision
73
+
74
+ ```ts
75
+ import { explainEnhancedTransactionForMint } from "@clayno-club/asset-flow";
76
+
77
+ const explanation = explainEnhancedTransactionForMint(tx, mint);
78
+
79
+ console.log(explanation.facts);
80
+ console.log(explanation.candidates);
81
+ console.log(explanation.selection);
82
+ ```
83
+
84
+ ## Concepts
85
+
86
+ The package is structured around three steps:
87
+
88
+ 1. Facts
89
+ Convert transaction evidence into normalized facts about ownership change, settlement, program signals, and participants.
90
+
91
+ 2. Classification
92
+ Match the transaction to a known family and determine whether it should materialize as a canonical flow, be skipped as non-settlement activity, or remain review-worthy.
93
+
94
+ 3. Normalization
95
+ Turn classified transaction history into canonical mint flows, value movements, and ownership periods.
96
+
97
+ ## Output model
98
+
99
+ The normalized history contains:
100
+
101
+ - `flows`
102
+ Canonical mint ownership events such as `MINT`, `SALE`, `TRANSFER`, `SWAP`, `LIST`, `DELIST`, `LOCK`, `UNLOCK`, and `FORECLOSURE`
103
+
104
+ - `valueMovements`
105
+ Associated native or fungible settlement movements with roles such as `SALE_GROSS`, `SELLER_PROCEEDS`, `ROYALTY`, `MARKETPLACE_FEE`, `RENT`, and `SWAP_CONSIDERATION`
106
+
107
+ - `periods`
108
+ Reconstructed ownership periods derived from normalized beneficial flows
109
+
110
+ ## When to use raw data
111
+
112
+ Some Solana transactions do not expose enough ownership detail in enhanced/indexed form alone. When raw transaction data is available, use:
113
+
114
+ - `shouldFetchRawForMintTx()`
115
+ - `augmentEnhancedTransactionWithRaw()`
116
+
117
+ This lets you keep the classification path deterministic while improving owner resolution for custody-heavy or protocol-heavy transactions.
118
+
119
+ ## Review analysis helpers
120
+
121
+ If you already have a set of transactions and want to identify unresolved patterns:
122
+
123
+ - `collectCoverageReviewCandidates()` returns review-worthy transactions
124
+ - `clusterCoverageReviewCandidates()` groups them into stable fingerprints
125
+ - `scanTransactionsForCoverage()` combines both into a mint-scoped summary
126
+
127
+ These helpers are pure over provided transactions and do not fetch data.
128
+
129
+ ## Extending the matcher set
130
+
131
+ When adding support for a new marketplace or protocol family:
132
+
133
+ 1. identify the reusable evidence
134
+ 2. add or reuse fact-building signals
135
+ 3. add a narrow matcher for the family
136
+ 4. register it with the appropriate priority
137
+ 5. add fixture coverage and regression tests
138
+
139
+ Prefer adding a matcher for a real transaction family over expanding generic fallback behavior.
140
+
141
+ ## Testing
142
+
143
+ Typical verification:
144
+
145
+ ```bash
146
+ pnpm --filter @clayno-club/asset-flow test
147
+ pnpm --filter @clayno-club/asset-flow build
148
+ pnpm --filter @clayno-club/asset-flow type-check
149
+ ```
@@ -0,0 +1,274 @@
1
+ interface HeliusTokenTransfer {
2
+ mint?: string;
3
+ fromUserAccount?: string;
4
+ toUserAccount?: string;
5
+ tokenAmount?: number | string;
6
+ tokenStandard?: string;
7
+ }
8
+ interface HeliusNativeTransfer {
9
+ fromUserAccount?: string;
10
+ toUserAccount?: string;
11
+ amount?: number | string;
12
+ }
13
+ interface HeliusEnhancedAction {
14
+ actionType?: string;
15
+ from?: string;
16
+ to?: string;
17
+ amount?: number | string;
18
+ mint?: string;
19
+ decimals?: number;
20
+ }
21
+ interface HeliusEnhancedInstruction {
22
+ programId?: string;
23
+ name?: string;
24
+ innerInstructions?: HeliusEnhancedInstruction[];
25
+ }
26
+ interface HeliusEnhancedTx {
27
+ signature: string;
28
+ slot?: number;
29
+ timestamp?: number;
30
+ type?: string;
31
+ source?: string;
32
+ description?: string;
33
+ fee?: number;
34
+ transactionError?: unknown | null;
35
+ programIds?: string[];
36
+ instructionLabels?: string[];
37
+ instructions?: HeliusEnhancedInstruction[];
38
+ actions?: HeliusEnhancedAction[];
39
+ tokenTransfers?: HeliusTokenTransfer[];
40
+ nativeTransfers?: HeliusNativeTransfer[];
41
+ }
42
+ type RawAssetFlowAccountKey = string | {
43
+ pubkey: string;
44
+ writable?: boolean;
45
+ signer?: boolean;
46
+ source?: string;
47
+ };
48
+ interface RawAssetFlowInstruction {
49
+ programId?: string;
50
+ programIdIndex?: number;
51
+ accounts?: (string | number)[];
52
+ data?: string;
53
+ }
54
+ interface RawAssetFlowTokenBalance {
55
+ accountIndex: number;
56
+ mint: string;
57
+ owner?: string;
58
+ uiTokenAmount: {
59
+ amount: string;
60
+ decimals?: number;
61
+ uiAmount?: number | null;
62
+ };
63
+ }
64
+ interface RawAssetFlowTransaction {
65
+ signature?: string;
66
+ blockTime?: number;
67
+ slot?: number;
68
+ transaction?: {
69
+ message?: {
70
+ accountKeys?: RawAssetFlowAccountKey[];
71
+ instructions?: RawAssetFlowInstruction[];
72
+ };
73
+ signatures?: string[];
74
+ };
75
+ meta?: {
76
+ preBalances?: number[];
77
+ postBalances?: number[];
78
+ preTokenBalances?: RawAssetFlowTokenBalance[];
79
+ postTokenBalances?: RawAssetFlowTokenBalance[];
80
+ logMessages?: string[];
81
+ };
82
+ }
83
+ type AssetFlowFamily = "NFT_MINT" | "EXPLICIT_NFT_SALE" | "TENSOR_UNKNOWN_SALE" | "MAGIC_EDEN_LUCKY_BUY" | "LOAN_PROGRAM_ACTIVITY" | "MARKETPLACE_LISTING" | "MARKETPLACE_BID" | "MAGIC_EDEN_BUY_INTENT" | "BUBBLEGUM_COMPRESSED_MINT" | "BET_PROGRAM_ACTIVITY" | "METAPLEX_METADATA_UPDATE" | "TLOCK_LEGACY_CLAIM" | "GENERIC_SWAP" | "GENERIC_UNKNOWN_SALE" | "GENERIC_TRANSFER" | "GENERIC_NON_SETTLEMENT" | "UNKNOWN";
84
+ type AssetFlowInterpreterConfidence = "high" | "medium" | "low";
85
+ type AssetFlowEffect = "OWNERSHIP_CHANGE_AND_SETTLEMENT" | "OWNERSHIP_CHANGE_ONLY" | "NON_SETTLEMENT_MARKETPLACE_ACTION" | "NO_MINT_EFFECT" | "UNKNOWN";
86
+ type AssetFlowMaterialization = "PERSIST_FLOW" | "SKIP_FLOW" | "REVIEW";
87
+ type AssetFlowDerivedType = "SALE" | "SWAP" | "TRANSFER" | "MINT" | "LIST" | "DELIST" | "LOCK" | "UNLOCK" | "FORECLOSURE" | "NON_SETTLEMENT" | "UNKNOWN";
88
+ interface AssetFlowObservation {
89
+ mint: string;
90
+ tx: HeliusEnhancedTx;
91
+ normalizedType: string;
92
+ normalizedSource: string;
93
+ nftTransfers: HeliusTokenTransfer[];
94
+ hasOwnershipChange: boolean;
95
+ hasMeaningfulSettlement: boolean;
96
+ primaryBuyer: string | null;
97
+ primarySeller: string | null;
98
+ programIds: string[];
99
+ instructionLabels: string[];
100
+ }
101
+ interface AssetFlowTxClassification {
102
+ family: AssetFlowFamily;
103
+ effect: AssetFlowEffect;
104
+ materialization: AssetFlowMaterialization;
105
+ normalizedType: string;
106
+ matcherId: string;
107
+ confidence: AssetFlowInterpreterConfidence;
108
+ derivedType: AssetFlowDerivedType;
109
+ hasOwnershipChange: boolean;
110
+ shouldExtractValueMovements: boolean;
111
+ reason: string;
112
+ }
113
+ interface ExtractedMintFlow {
114
+ signature: string;
115
+ slot: number | null;
116
+ timestamp: number;
117
+ heliusType: string;
118
+ source: string;
119
+ fromAddress: string | null;
120
+ toAddress: string | null;
121
+ transferIndex: number;
122
+ tokenStandard: string | null;
123
+ derivedType: "SALE" | "SWAP" | "TRANSFER" | "MINT" | "LIST" | "DELIST" | "LOCK" | "UNLOCK" | "FORECLOSURE" | "UNKNOWN";
124
+ }
125
+ interface ExtractedValueMovement {
126
+ signature: string;
127
+ slot: number | null;
128
+ timestamp: number;
129
+ heliusType: string;
130
+ source: string;
131
+ movementKind: "NATIVE" | "TOKEN";
132
+ currency: string;
133
+ amount: number;
134
+ rawAmount: string | null;
135
+ decimals: number | null;
136
+ fromAddress: string | null;
137
+ toAddress: string | null;
138
+ movementIndex: number;
139
+ tokenStandard: string | null;
140
+ role: "SALE_GROSS" | "SELLER_PROCEEDS" | "ROYALTY" | "MARKETPLACE_FEE" | "OTHER_FEE" | "RENT" | "REFUND" | "SWAP_CONSIDERATION" | "UNKNOWN";
141
+ confidence: "HIGH" | "MEDIUM" | "LOW";
142
+ isSynthetic: boolean;
143
+ }
144
+ interface ReconstructedOwnershipPeriod {
145
+ ownerAddress: string;
146
+ startTimestamp: number;
147
+ endTimestamp: number | null;
148
+ }
149
+ interface NormalizedMintHistory {
150
+ flows: ExtractedMintFlow[];
151
+ valueMovements: ExtractedValueMovement[];
152
+ periods: ReconstructedOwnershipPeriod[];
153
+ }
154
+
155
+ type AssetOwnershipKind = "NONE" | "DIRECT" | "CUSTODY" | "SAME_OWNER" | "AMBIGUOUS";
156
+ type AssetSettlementKind = "NONE" | "NATIVE_SALE" | "POTENTIAL_SALE" | "AMBIGUOUS";
157
+ type AssetNftTouchKind = "NONE" | "SAME_OWNER" | "OWNERSHIP_CHANGE";
158
+ interface AssetFlowTransactionFacts {
159
+ mint: string;
160
+ tx: HeliusEnhancedTx;
161
+ normalizedType: string;
162
+ normalizedSource: string;
163
+ nftTransfers: HeliusTokenTransfer[];
164
+ nftTouchKind: AssetNftTouchKind;
165
+ ownershipKind: AssetOwnershipKind;
166
+ settlementKind: AssetSettlementKind;
167
+ hasOwnershipChange: boolean;
168
+ hasMeaningfulSettlement: boolean;
169
+ primaryBuyer: string | null;
170
+ primarySeller: string | null;
171
+ custodyEndpoint: string | null;
172
+ hasProgramEvidence: boolean;
173
+ isNoisyOwnershipChange: boolean;
174
+ programIds: string[];
175
+ instructionLabels: string[];
176
+ }
177
+
178
+ declare function buildTransactionFacts(tx: HeliusEnhancedTx, mint: string): AssetFlowTransactionFacts;
179
+
180
+ declare function normalizeEnhancedType(value: string | undefined): string;
181
+ declare function toNullableNumber(value: unknown): number | null;
182
+ declare function isNonFungibleTokenStandard(tokenStandard: string | null | undefined): boolean;
183
+ declare function isMintNftTransfer(transfer: HeliusTokenTransfer, mint: string): boolean;
184
+ declare function getMintNftTransfers(tx: HeliusEnhancedTx, mint: string): HeliusTokenTransfer[];
185
+ declare function hasMeaningfulNativeSettlement(tx: HeliusEnhancedTx, mint: string): boolean;
186
+
187
+ declare function observationFromFacts(facts: AssetFlowTransactionFacts): AssetFlowObservation;
188
+ declare function observeEnhancedTransactionForMint(tx: HeliusEnhancedTx, mint: string): AssetFlowObservation;
189
+
190
+ declare function classifyEnhancedTransactionForMint(tx: HeliusEnhancedTx, mint: string): AssetFlowTxClassification;
191
+
192
+ interface AssetFlowClassificationMatcher {
193
+ id: string;
194
+ matches(observation: AssetFlowObservation): boolean;
195
+ classify(observation: AssetFlowObservation): AssetFlowTxClassification;
196
+ }
197
+ interface RegisteredAssetFlowClassificationMatcher extends AssetFlowClassificationMatcher {
198
+ priority: number;
199
+ }
200
+
201
+ interface AssetFlowMatcherExplanation {
202
+ matcher: RegisteredAssetFlowClassificationMatcher;
203
+ classification: AssetFlowTxClassification;
204
+ }
205
+ interface AssetFlowClassificationExplanation {
206
+ facts: AssetFlowTransactionFacts;
207
+ observation: AssetFlowObservation;
208
+ candidates: AssetFlowMatcherExplanation[];
209
+ selected: AssetFlowMatcherExplanation;
210
+ classification: AssetFlowTxClassification;
211
+ }
212
+ declare function explainEnhancedTransactionForMint(tx: HeliusEnhancedTx, mint: string): AssetFlowClassificationExplanation;
213
+
214
+ interface AssetFlowCoverageReviewCandidate {
215
+ mint: string;
216
+ signature: string;
217
+ timestamp: number | null;
218
+ source: string;
219
+ normalizedType: string;
220
+ matcherId: string;
221
+ family: AssetFlowTxClassification["family"];
222
+ effect: AssetFlowTxClassification["effect"];
223
+ materialization: AssetFlowTxClassification["materialization"];
224
+ confidence: AssetFlowTxClassification["confidence"];
225
+ reason: AssetFlowTxClassification["reason"];
226
+ hasOwnershipChange: boolean;
227
+ hasMeaningfulSettlement: boolean;
228
+ programIds: string[];
229
+ instructionLabels: string[];
230
+ }
231
+ interface AssetFlowCoverageCluster {
232
+ fingerprint: string;
233
+ matcherId: string;
234
+ family: AssetFlowTxClassification["family"];
235
+ effect: AssetFlowTxClassification["effect"];
236
+ materialization: AssetFlowTxClassification["materialization"];
237
+ confidence: AssetFlowTxClassification["confidence"];
238
+ normalizedType: string;
239
+ source: string;
240
+ reason: AssetFlowTxClassification["reason"];
241
+ hasOwnershipChange: boolean;
242
+ hasMeaningfulSettlement: boolean;
243
+ programIds: string[];
244
+ instructionLabels: string[];
245
+ count: number;
246
+ sampleSignatures: string[];
247
+ sampleMints: string[];
248
+ }
249
+ interface AssetFlowCoverageScanResult {
250
+ mint: string;
251
+ transactionsScanned: number;
252
+ reviewCandidates: AssetFlowCoverageReviewCandidate[];
253
+ clusters: AssetFlowCoverageCluster[];
254
+ }
255
+ declare function collectCoverageReviewCandidates(transactions: HeliusEnhancedTx[], mint: string): AssetFlowCoverageReviewCandidate[];
256
+ declare function clusterCoverageReviewCandidates(candidates: AssetFlowCoverageReviewCandidate[], sampleLimit?: number): AssetFlowCoverageCluster[];
257
+ declare function scanTransactionsForCoverage(transactions: HeliusEnhancedTx[], mint: string): AssetFlowCoverageScanResult;
258
+
259
+ declare function reconstructOwnershipPeriods(flows: ExtractedMintFlow[]): ReconstructedOwnershipPeriod[];
260
+
261
+ declare function extractCandidateMintsFromEnhancedTx(tx: HeliusEnhancedTx): string[];
262
+ declare function extractMintFlows(transactions: HeliusEnhancedTx[], mint: string, classificationCache?: Map<string, AssetFlowTxClassification>): ExtractedMintFlow[];
263
+ declare function extractValueMovements(transactions: HeliusEnhancedTx[], mint: string, classificationCache?: Map<string, AssetFlowTxClassification>): ExtractedValueMovement[];
264
+ declare function normalizeMintHistory(transactions: HeliusEnhancedTx[], mint: string): NormalizedMintHistory;
265
+
266
+ declare function extractCandidateMintsFromRawTransaction(tx: RawAssetFlowTransaction): string[];
267
+ declare function synthesizeEnhancedTransactionsFromRaw(tx: RawAssetFlowTransaction): HeliusEnhancedTx[];
268
+ declare function mergeRawProgramEvidence(tx: HeliusEnhancedTx, rawTx: RawAssetFlowTransaction): HeliusEnhancedTx;
269
+ declare function shouldApplyRawOwnerOverrides(tx: HeliusEnhancedTx, rawTx: RawAssetFlowTransaction): boolean;
270
+ declare function augmentEnhancedTransactionWithRaw(tx: HeliusEnhancedTx, rawTx: RawAssetFlowTransaction): HeliusEnhancedTx;
271
+ declare function applyRawOwnerOverridesToEnhanced(tx: HeliusEnhancedTx, rawTx: RawAssetFlowTransaction): HeliusEnhancedTx;
272
+ declare function shouldFetchRawForMintTx(tx: HeliusEnhancedTx, mint: string): boolean;
273
+
274
+ export { type AssetFlowClassificationExplanation, type AssetFlowCoverageCluster, type AssetFlowCoverageReviewCandidate, type AssetFlowCoverageScanResult, type AssetFlowDerivedType, type AssetFlowEffect, type AssetFlowFamily, type AssetFlowInterpreterConfidence, type AssetFlowMatcherExplanation, type AssetFlowMaterialization, type AssetFlowObservation, type AssetFlowTransactionFacts, type AssetFlowTxClassification, type AssetNftTouchKind, type AssetOwnershipKind, type AssetSettlementKind, type ExtractedMintFlow, type ExtractedValueMovement, type HeliusEnhancedAction, type HeliusEnhancedInstruction, type HeliusEnhancedTx, type HeliusNativeTransfer, type HeliusTokenTransfer, type NormalizedMintHistory, type RawAssetFlowAccountKey, type RawAssetFlowInstruction, type RawAssetFlowTokenBalance, type RawAssetFlowTransaction, type ReconstructedOwnershipPeriod, applyRawOwnerOverridesToEnhanced, augmentEnhancedTransactionWithRaw, buildTransactionFacts, classifyEnhancedTransactionForMint, clusterCoverageReviewCandidates, collectCoverageReviewCandidates, explainEnhancedTransactionForMint, extractCandidateMintsFromEnhancedTx, extractCandidateMintsFromRawTransaction, extractMintFlows, extractValueMovements, getMintNftTransfers, hasMeaningfulNativeSettlement, isMintNftTransfer, isNonFungibleTokenStandard, mergeRawProgramEvidence, normalizeEnhancedType, normalizeMintHistory, observationFromFacts, observeEnhancedTransactionForMint, reconstructOwnershipPeriods, scanTransactionsForCoverage, shouldApplyRawOwnerOverrides, shouldFetchRawForMintTx, synthesizeEnhancedTransactionsFromRaw, toNullableNumber };