@clayno-club/asset-flow 0.2.2 → 0.3.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/dist/index.js CHANGED
@@ -32,6 +32,7 @@ __export(solana_exports, {
32
32
  extractValueMovements: () => extractValueMovements,
33
33
  getMatchingAssetFlowMatchers: () => getMatchingAssetFlowMatchers,
34
34
  getMintNftTransfers: () => getMintNftTransfers,
35
+ hasLoanProgramSettlement: () => hasLoanProgramSettlement,
35
36
  hasMeaningfulNativeSettlement: () => hasMeaningfulNativeSettlement,
36
37
  interpretMintFlowProtocolEvent: () => interpretMintFlowProtocolEvent,
37
38
  isMintNftTransfer: () => isMintNftTransfer,
@@ -107,6 +108,15 @@ function hasMeaningfulNativeSettlement(tx, mint) {
107
108
  }
108
109
  return (tx.actions ?? []).some((action) => isMeaningfulPaidAction(action, primaryBuyer, primarySeller));
109
110
  }
111
+ function hasLoanProgramSettlement(tx) {
112
+ for (const entry of tx.accountData ?? []) {
113
+ const delta = toNullableNumber(entry.nativeBalanceChange);
114
+ if (delta !== null && Math.abs(delta) >= MEANINGFUL_SETTLEMENT_LAMPORTS) {
115
+ return true;
116
+ }
117
+ }
118
+ return false;
119
+ }
110
120
 
111
121
  // src/chains/solana/facts/build-facts.ts
112
122
  var NOISY_OWNERSHIP_CHANGE_SOURCES = /* @__PURE__ */ new Set([
@@ -157,6 +167,7 @@ function buildTransactionFacts(tx, mint) {
157
167
  const nftTouchKind = !hasTransferTouch ? "NONE" : hasOwnershipChange ? "OWNERSHIP_CHANGE" : primarySeller === primaryBuyer ? "SAME_OWNER" : "SAME_OWNER";
158
168
  const ownershipKind = !hasTransferTouch ? "NONE" : isNoisyOwnershipChange ? "AMBIGUOUS" : hasOwnershipChange ? "DIRECT" : primarySeller && primaryBuyer && primarySeller === primaryBuyer ? "SAME_OWNER" : "AMBIGUOUS";
159
169
  const settlementKind = hasMeaningfulSettlement ? primaryBuyer && primarySeller ? "NATIVE_SALE" : "POTENTIAL_SALE" : hasOwnershipChange && !hasProgramEvidence ? "AMBIGUOUS" : "NONE";
170
+ const mintInAccountData = (tx.accountData ?? []).some((entry) => entry.account === mint);
160
171
  return {
161
172
  mint,
162
173
  tx,
@@ -173,6 +184,7 @@ function buildTransactionFacts(tx, mint) {
173
184
  custodyEndpoint: hasTransferTouch ? primaryBuyer : null,
174
185
  hasProgramEvidence,
175
186
  isNoisyOwnershipChange,
187
+ mintInAccountData,
176
188
  programIds: [...programIds],
177
189
  instructionLabels: [...instructionLabels]
178
190
  };
@@ -294,6 +306,7 @@ var HADESWAP_SOURCE = "HADESWAP";
294
306
  var MAGIC_EDEN_M2_PROGRAM_ID = "M2mx93ekt1fmXSVkTrUL9xVFHkmME8HTUi5Cyc5aF7K";
295
307
  var BUY_INTENT_INSTRUCTION_LABEL = "BuyV2";
296
308
  var MEANINGFUL_SETTLEMENT_LAMPORTS2 = 1e7;
309
+ var RAW_MARKETPLACE_LISTING_LABELS = /* @__PURE__ */ new Set(["MIP1SELL", "LISTLEGACY"]);
297
310
  function isPureNonSettlementObservation(observation) {
298
311
  return observation.nftTransfers.length === 0 && !observation.hasOwnershipChange && !observation.hasMeaningfulSettlement;
299
312
  }
@@ -301,7 +314,9 @@ function isUnknownSaleLikeMarketplaceObservation(observation, source) {
301
314
  return observation.normalizedType === "UNKNOWN" && observation.normalizedSource === source && observation.hasOwnershipChange && observation.hasMeaningfulSettlement;
302
315
  }
303
316
  function isStandardMarketplaceListing(observation) {
304
- return !observation.hasMeaningfulSettlement && (MARKETPLACE_SOURCES.has(observation.normalizedSource) && LISTING_TYPES.has(observation.normalizedType) || observation.programIds.includes(TENSOR_LEGACY_MARKETPLACE_PROGRAM_ID) || observation.programIds.includes(MAGIC_EDEN_MMM_PROGRAM_ID));
317
+ return !observation.hasMeaningfulSettlement && (MARKETPLACE_SOURCES.has(observation.normalizedSource) && LISTING_TYPES.has(observation.normalizedType) || observation.normalizedSource === "RAW_WEBHOOK" && observation.instructionLabels.some(
318
+ (label) => RAW_MARKETPLACE_LISTING_LABELS.has(label.replace(/[^a-z0-9]+/gi, "").toUpperCase())
319
+ ) || observation.programIds.includes(TENSOR_LEGACY_MARKETPLACE_PROGRAM_ID) || observation.programIds.includes(MAGIC_EDEN_MMM_PROGRAM_ID));
305
320
  }
306
321
  function isFoxyRaffleListing(observation) {
307
322
  return observation.normalizedSource === FOXY_RAFFLE_SOURCE && observation.hasOwnershipChange && (observation.normalizedType === FOXY_RAFFLE_CREATE_TYPE || observation.normalizedType === FOXY_RAFFLE_UNWIND_TYPE && !observation.hasMeaningfulSettlement && !observation.programIds.includes(ASSOCIATED_TOKEN_PROGRAM_ID));
@@ -394,6 +409,7 @@ function resolveRoutedSettlement(observation, intermediary, buyer) {
394
409
  intermediary,
395
410
  buyer,
396
411
  counterparty: to,
412
+ amount,
397
413
  matchedBy: "INTERMEDIARY_OUTBOUND_SETTLEMENT"
398
414
  };
399
415
  }
@@ -402,6 +418,7 @@ function resolveRoutedSettlement(observation, intermediary, buyer) {
402
418
  intermediary,
403
419
  buyer,
404
420
  counterparty: from,
421
+ amount,
405
422
  matchedBy: "INTERMEDIARY_INBOUND_SETTLEMENT"
406
423
  };
407
424
  }
@@ -728,10 +745,14 @@ var foxyCancelSwapMatcher = {
728
745
  };
729
746
 
730
747
  // src/chains/solana/matchers/protocol/foxy-init-swap.ts
748
+ function hasRawFoxyInitSwapDeposit(labels) {
749
+ const normalized = new Set(labels.map((label) => label.replace(/[^a-z0-9]+/gi, "").toUpperCase()));
750
+ return [...normalized].some((label) => label === "INITSWAP" || label === "INITSWAP2") && normalized.has("DEPOSITPNFT");
751
+ }
731
752
  var foxyInitSwapMatcher = {
732
753
  id: "solana.foxy-init-swap",
733
754
  matches(observation) {
734
- return observation.normalizedType === "INIT_SWAP" && observation.normalizedSource === "FOXY";
755
+ return observation.normalizedType === "INIT_SWAP" && observation.normalizedSource === "FOXY" || observation.normalizedSource === "RAW_WEBHOOK" && observation.normalizedType === "NFT_TRANSFER" && hasRawFoxyInitSwapDeposit(observation.instructionLabels);
735
756
  },
736
757
  classify(observation) {
737
758
  const trackedMintMoves = observation.nftTransfers.length > 0 && observation.hasOwnershipChange;
@@ -846,8 +867,24 @@ var hadeswapActivityMatcher = {
846
867
  };
847
868
 
848
869
  // src/semantics/loan.ts
870
+ var BENEFICIAL_OWNERSHIP_DERIVED_TYPES = /* @__PURE__ */ new Set([
871
+ "MINT",
872
+ "SALE",
873
+ "TRANSFER",
874
+ "SWAP",
875
+ "FORECLOSURE"
876
+ ]);
849
877
  var LOAN_PROGRAM_SOURCES = /* @__PURE__ */ new Set(["SHARKY_FI", "CITRUS", "RAINFI", "CARDINAL_RENT", "FRAKT"]);
850
- var LOAN_LOCK_TYPES = /* @__PURE__ */ new Set(["TAKE_LOAN", "BORROW_SOL_FOR_NFT", "REBORROW_SOL_FOR_NFT", "BORROW_PERPETUAL"]);
878
+ var LOAN_LOCK_TYPES = /* @__PURE__ */ new Set([
879
+ "TAKE_LOAN",
880
+ "BORROW_SOL_FOR_NFT",
881
+ "REBORROW_SOL_FOR_NFT",
882
+ "BORROW_PERPETUAL",
883
+ "FREEZE",
884
+ // RainFi: collateral deposit
885
+ "EXTEND_LOAN"
886
+ // RainFi: loan rollover (treated as a fresh lock for chain integrity)
887
+ ]);
851
888
  var LOAN_UNLOCK_TYPES = /* @__PURE__ */ new Set(["REPAY_LOAN", "REPAY_PERPETUAL_LOAN", "REPAY", "UNFREEZE"]);
852
889
  var LOAN_UNLOCK_INSTRUCTION_LABELS = /* @__PURE__ */ new Set([
853
890
  "DelistCollateral",
@@ -896,7 +933,10 @@ function resolveLoanProgramActivity(observation) {
896
933
  if (!isLoanProgramSource(observation.normalizedSource) || !isLoanActivityType(observation.normalizedType) || observation.hasOwnershipChange && observation.hasMeaningfulSettlement) {
897
934
  return null;
898
935
  }
899
- const hasTrackedMintTouch = observation.nftTransfers.length > 0;
936
+ const hasExplicitLockType = isLoanLockType(observation.normalizedType) || isLoanUnlockType(observation.normalizedType);
937
+ const baseDelegationGate = observation.nftTransfers.length === 0 && observation.mintInAccountData;
938
+ const isDelegationLifecycle = baseDelegationGate && (hasExplicitLockType || observation.normalizedType === "UNKNOWN" && hasLoanProgramSettlement(observation.tx));
939
+ const hasTrackedMintTouch = observation.nftTransfers.length > 0 || isDelegationLifecycle;
900
940
  const lifecycleDirection = inferLoanLifecycleDirection(observation);
901
941
  return {
902
942
  lifecycleDirection,
@@ -944,6 +984,31 @@ function resolveLoanCollateralTransfer(observation) {
944
984
  }
945
985
  return { derivedType: "TRANSFER" };
946
986
  }
987
+ function repairForeclosureFromAddresses(flows) {
988
+ const result = [];
989
+ let currentHolder = null;
990
+ const activeEscrows = /* @__PURE__ */ new Set();
991
+ for (const flow of flows) {
992
+ const repaired = flow.derivedType === "FORECLOSURE" && currentHolder !== null && flow.fromAddress !== currentHolder ? { ...flow, fromAddress: currentHolder } : flow;
993
+ result.push(repaired);
994
+ if (repaired.derivedType === "LIST" || repaired.derivedType === "LOCK") {
995
+ if (repaired.toAddress) activeEscrows.add(repaired.toAddress);
996
+ continue;
997
+ }
998
+ if (repaired.derivedType === "DELIST" || repaired.derivedType === "UNLOCK") {
999
+ if (repaired.fromAddress) activeEscrows.delete(repaired.fromAddress);
1000
+ if (repaired.toAddress && repaired.toAddress !== currentHolder && !activeEscrows.has(repaired.toAddress)) {
1001
+ currentHolder = repaired.toAddress;
1002
+ }
1003
+ continue;
1004
+ }
1005
+ if (BENEFICIAL_OWNERSHIP_DERIVED_TYPES.has(repaired.derivedType)) {
1006
+ if (repaired.fromAddress) activeEscrows.delete(repaired.fromAddress);
1007
+ currentHolder = repaired.toAddress;
1008
+ }
1009
+ }
1010
+ return result;
1011
+ }
947
1012
 
948
1013
  // src/chains/solana/matchers/protocol/loan-collateral-transfer.ts
949
1014
  var loanCollateralTransferMatcher = {
@@ -1054,12 +1119,15 @@ var metaplexMetadataUpdateMatcher = {
1054
1119
  }
1055
1120
  };
1056
1121
 
1057
- // src/chains/solana/matchers/protocol/sharky-repay-loan-sale.ts
1058
- var SHARKY_REPAY_TYPES = /* @__PURE__ */ new Set(["REPAY_LOAN", "UNKNOWN"]);
1059
- var sharkyRepayLoanSaleMatcher = {
1060
- id: "solana.sharky-repay-loan-sale",
1122
+ // src/chains/solana/matchers/protocol/loan-repay-with-settlement.ts
1123
+ var loanRepayWithSettlementMatcher = {
1124
+ id: "solana.loan-repay-with-settlement",
1061
1125
  matches(observation) {
1062
- return observation.normalizedSource === "SHARKY_FI" && SHARKY_REPAY_TYPES.has(observation.normalizedType) && observation.hasOwnershipChange && observation.hasMeaningfulSettlement && observation.nftTransfers.length > 0;
1126
+ if (!isLoanProgramSource(observation.normalizedSource)) return false;
1127
+ if (!observation.hasOwnershipChange || !observation.hasMeaningfulSettlement) return false;
1128
+ if (observation.nftTransfers.length === 0) return false;
1129
+ if (isLoanUnlockType(observation.normalizedType)) return true;
1130
+ return observation.normalizedSource === "SHARKY_FI" && observation.normalizedType === "UNKNOWN";
1063
1131
  },
1064
1132
  classify(observation) {
1065
1133
  return classification({
@@ -1069,7 +1137,6 @@ var sharkyRepayLoanSaleMatcher = {
1069
1137
  reason: "matched_loan_collateral_transfer_family",
1070
1138
  observation,
1071
1139
  overrides: {
1072
- normalizedType: "REPAY_LOAN",
1073
1140
  effect: "OWNERSHIP_CHANGE_AND_SETTLEMENT",
1074
1141
  shouldExtractValueMovements: true
1075
1142
  }
@@ -1079,19 +1146,19 @@ var sharkyRepayLoanSaleMatcher = {
1079
1146
 
1080
1147
  // src/chains/solana/matchers/protocol/sharky-take-loan-transfer.ts
1081
1148
  var sharkyTakeLoanTransferMatcher = {
1082
- id: "solana.sharky-take-loan-transfer",
1149
+ id: "solana.loan-take-with-transfer",
1083
1150
  matches(observation) {
1084
- return observation.normalizedSource === "SHARKY_FI" && observation.normalizedType === "TAKE_LOAN" && observation.hasOwnershipChange && observation.hasMeaningfulSettlement && observation.nftTransfers.length > 0;
1151
+ return isLoanProgramSource(observation.normalizedSource) && isLoanLockType(observation.normalizedType) && observation.hasOwnershipChange && observation.hasMeaningfulSettlement && observation.nftTransfers.length > 0;
1085
1152
  },
1086
1153
  classify(observation) {
1087
1154
  return classification({
1088
1155
  family: "LOAN_PROGRAM_ACTIVITY",
1089
1156
  derivedType: "TRANSFER",
1090
1157
  matcherId: this.id,
1091
- reason: "sharky_take_loan_with_settlement",
1158
+ reason: "loan_take_with_settlement",
1092
1159
  observation,
1093
1160
  overrides: {
1094
- normalizedType: "TAKE_LOAN",
1161
+ normalizedType: observation.normalizedType,
1095
1162
  effect: "OWNERSHIP_CHANGE_AND_SETTLEMENT",
1096
1163
  shouldExtractValueMovements: false
1097
1164
  }
@@ -1271,7 +1338,7 @@ var protocolMatchers = registerMatchers([
1271
1338
  [loanProgramActivityMatcher, 870],
1272
1339
  [migrateToPnftMatcher, 860],
1273
1340
  [sharkyTakeLoanTransferMatcher, 855],
1274
- [sharkyRepayLoanSaleMatcher, 850],
1341
+ [loanRepayWithSettlementMatcher, 850],
1275
1342
  [metaplexResizeActivityMatcher, 840],
1276
1343
  [metaplexMetadataUpdateMatcher, 830]
1277
1344
  ]);
@@ -1325,6 +1392,7 @@ function observationFromFacts(facts) {
1325
1392
  hasMeaningfulSettlement: facts.hasMeaningfulSettlement,
1326
1393
  primaryBuyer: facts.primaryBuyer,
1327
1394
  primarySeller: facts.primarySeller,
1395
+ mintInAccountData: facts.mintInAccountData,
1328
1396
  programIds: facts.programIds,
1329
1397
  instructionLabels: facts.instructionLabels
1330
1398
  };
@@ -1771,7 +1839,27 @@ function interpretMintFlowProtocolEvent(params) {
1771
1839
  const derivedType = deriveMintFlowType(classification2, normalizedType);
1772
1840
  if (normalizedType !== "NFT_MINT" && derivedType === "UNKNOWN") return null;
1773
1841
  const mintTransfers = getMintTransfers(tx, mint);
1774
- if (mintTransfers.length === 0) return null;
1842
+ if (mintTransfers.length === 0) {
1843
+ if (classification2 && classification2.family === "LOAN_PROGRAM_ACTIVITY" && (derivedType === "LOCK" || derivedType === "UNLOCK") && tx.feePayer) {
1844
+ const borrower = tx.feePayer;
1845
+ return {
1846
+ kind: "DIRECT_FLOW",
1847
+ tx,
1848
+ timestamp,
1849
+ classification: classification2,
1850
+ flows: [
1851
+ {
1852
+ fromAddress: borrower,
1853
+ toAddress: borrower,
1854
+ transferIndex: 0,
1855
+ tokenStandard: null,
1856
+ derivedType
1857
+ }
1858
+ ]
1859
+ };
1860
+ }
1861
+ return null;
1862
+ }
1775
1863
  const { firstTransfer, lastTransfer, fromAddress, toAddress, tokenStandard, intermediaries } = getTransferEndpoints(mintTransfers);
1776
1864
  if (derivedType !== "MINT" && derivedType !== "LOCK" && derivedType !== "UNLOCK" && fromAddress === toAddress) {
1777
1865
  return null;
@@ -1805,6 +1893,31 @@ function interpretMintFlowProtocolEvent(params) {
1805
1893
  }
1806
1894
  }
1807
1895
  }
1896
+ const isCitrusComboBuy = (derivedType === "UNLOCK" || derivedType === "SALE") && normalizeEnhancedType(tx.source) === "CITRUS" && fromAddress !== null && toAddress !== null && fromAddress !== toAddress;
1897
+ if (isCitrusComboBuy && fromAddress && toAddress) {
1898
+ return {
1899
+ kind: "DIRECT_FLOW",
1900
+ tx,
1901
+ timestamp,
1902
+ classification: classification2,
1903
+ flows: [
1904
+ {
1905
+ fromAddress,
1906
+ toAddress,
1907
+ transferIndex: 0,
1908
+ tokenStandard,
1909
+ derivedType
1910
+ },
1911
+ {
1912
+ fromAddress: toAddress,
1913
+ toAddress,
1914
+ transferIndex: 1,
1915
+ tokenStandard,
1916
+ derivedType: "LOCK"
1917
+ }
1918
+ ]
1919
+ };
1920
+ }
1808
1921
  if (derivedType === "SALE") {
1809
1922
  const resolution = resolveSaleProtocolSemantics({
1810
1923
  tx,
@@ -2193,6 +2306,13 @@ function extractCandidateMintsFromEnhancedTx(tx) {
2193
2306
  mints.add(action.mint);
2194
2307
  }
2195
2308
  }
2309
+ for (const account of tx.accountData ?? []) {
2310
+ for (const change of account.tokenBalanceChanges ?? []) {
2311
+ if (change.mint) {
2312
+ mints.add(change.mint);
2313
+ }
2314
+ }
2315
+ }
2196
2316
  return [...mints];
2197
2317
  }
2198
2318
  function buildFlow(tx, timestamp, params) {
@@ -2258,7 +2378,9 @@ function extractValueMovements(transactions, mint, classificationCache = /* @__P
2258
2378
  }
2259
2379
  function normalizeMintHistory(transactions, mint) {
2260
2380
  const classificationCache = /* @__PURE__ */ new Map();
2261
- const flows = collapseRedundantCustodyFlows(extractMintFlows(transactions, mint, classificationCache));
2381
+ const flows = repairForeclosureFromAddresses(
2382
+ collapseRedundantCustodyFlows(extractMintFlows(transactions, mint, classificationCache))
2383
+ );
2262
2384
  const valueMovements = extractValueMovements(transactions, mint, classificationCache);
2263
2385
  const periods = reconstructOwnershipPeriods(flows);
2264
2386
  return {
@@ -2648,6 +2770,7 @@ function shouldFetchRawForMintTx(tx, mint) {
2648
2770
  var sui_exports = {};
2649
2771
  __export(sui_exports, {
2650
2772
  KIOSK_BIDDING_CLAIM_EVENT_SUFFIX: () => KIOSK_BIDDING_CLAIM_EVENT_SUFFIX,
2773
+ KIOSK_BIDDING_MATCH_COLLECTION_BID_EVENT_SUFFIX: () => KIOSK_BIDDING_MATCH_COLLECTION_BID_EVENT_SUFFIX,
2651
2774
  KIOSK_CLAIM_EVENT_SUFFIX: () => KIOSK_CLAIM_EVENT_SUFFIX,
2652
2775
  KIOSK_LIST_EVENT_SUFFIX: () => KIOSK_LIST_EVENT_SUFFIX,
2653
2776
  KIOSK_TRANSFER_EVENT_SUFFIX: () => KIOSK_TRANSFER_EVENT_SUFFIX,
@@ -2660,6 +2783,7 @@ __export(sui_exports, {
2660
2783
  TRADEPORT_ADD_LISTING_EVENT_SUFFIX: () => TRADEPORT_ADD_LISTING_EVENT_SUFFIX,
2661
2784
  TRADEPORT_BUY_SIMPLE_EVENT_SUFFIX: () => TRADEPORT_BUY_SIMPLE_EVENT_SUFFIX,
2662
2785
  TRADEPORT_CANCEL_SIMPLE_EVENT_SUFFIX: () => TRADEPORT_CANCEL_SIMPLE_EVENT_SUFFIX,
2786
+ TRADEPORT_CREATE_SIMPLE_LISTING_EVENT_SUFFIX: () => TRADEPORT_CREATE_SIMPLE_LISTING_EVENT_SUFFIX,
2663
2787
  TRADEPORT_CREATE_SINGLE_BID_EVENT_SUFFIX: () => TRADEPORT_CREATE_SINGLE_BID_EVENT_SUFFIX,
2664
2788
  TRADEPORT_KIOSK_BUY_EVENT_SUFFIX: () => TRADEPORT_KIOSK_BUY_EVENT_SUFFIX,
2665
2789
  TRADEPORT_MATCH_SINGLE_BID_EVENT_SUFFIX: () => TRADEPORT_MATCH_SINGLE_BID_EVENT_SUFFIX,
@@ -2668,17 +2792,20 @@ __export(sui_exports, {
2668
2792
  buildAssetHistory: () => normalizeAssetHistory,
2669
2793
  buildFlows: () => extractAssetFlows,
2670
2794
  buildOwnershipPeriods: () => reconstructOwnershipPeriods,
2795
+ buildValueMovements: () => extractSuiValueMovements,
2671
2796
  classify: () => classifySuiTransactionForAsset,
2672
2797
  classifySuiTransactionForAsset: () => classifySuiTransactionForAsset,
2673
2798
  eventTouchesAsset: () => eventTouchesAsset,
2674
2799
  eventTypeMatches: () => eventTypeMatches,
2675
2800
  extractAssetFlows: () => extractAssetFlows,
2801
+ extractSuiValueMovements: () => extractSuiValueMovements,
2676
2802
  findFirstEvent: () => findFirstEvent,
2677
2803
  getAssetEvents: () => getAssetEvents,
2678
2804
  getAssetObjectChange: () => getAssetObjectChange,
2679
2805
  getAssetOwnerChange: () => getAssetOwnerChange,
2680
2806
  getAssetOwnerChangeDetails: () => getAssetOwnerChangeDetails,
2681
2807
  getEventTypes: () => getEventTypes,
2808
+ getKioskOwnerCapAddressOwnerChange: () => getKioskOwnerCapAddressOwnerChange,
2682
2809
  getMarket: () => getMarket,
2683
2810
  getPriceRaw: () => getPriceRaw,
2684
2811
  getRecordString: () => getRecordString,
@@ -2700,10 +2827,12 @@ var TRADEPORT_BUY_SIMPLE_EVENT_SUFFIX = "::tradeport_listings::BuySimpleListingE
2700
2827
  var TRADEPORT_MATCH_SINGLE_BID_EVENT_SUFFIX = "::tradeport_biddings::MatchSingleBidEvent";
2701
2828
  var TRADEPORT_KIOSK_BUY_EVENT_SUFFIX = "::kiosk_listings::BuyEvent";
2702
2829
  var TRADEPORT_ADD_LISTING_EVENT_SUFFIX = "::tradeport_orderbook::AddListingEvent";
2830
+ var TRADEPORT_CREATE_SIMPLE_LISTING_EVENT_SUFFIX = "::tradeport_listings::CreateSimpleListingEvent";
2703
2831
  var TRADEPORT_RELIST_SIMPLE_EVENT_SUFFIX = "::tradeport_listings::RelistSimpleListingEvent";
2704
2832
  var TRADEPORT_REMOVE_LISTING_EVENT_SUFFIX = "::tradeport_orderbook::RemoveListingEvent";
2705
2833
  var TRADEPORT_CANCEL_SIMPLE_EVENT_SUFFIX = "::tradeport_listings::CancelSimpleListingEvent";
2706
2834
  var TRADEPORT_CREATE_SINGLE_BID_EVENT_SUFFIX = "::tradeport_biddings::CreateSingleBidEvent";
2835
+ var KIOSK_BIDDING_MATCH_COLLECTION_BID_EVENT_SUFFIX = "::kiosk_biddings::MatchCollectionBidWithPurchaseCapEvent";
2707
2836
  var KIOSK_LIST_EVENT_SUFFIX = "::kiosk_listings::ListEvent";
2708
2837
  var KIOSK_UNLIST_EVENT_SUFFIX = "::kiosk_listings::UnlistEvent";
2709
2838
  var KIOSK_TRANSFER_EVENT_SUFFIX = "::kiosk_transfers::TransferWithPurchaseCapEvent";
@@ -2778,6 +2907,31 @@ function getAssetOwnerChange(tx, assetId) {
2778
2907
  objectType: ownerChange.objectType
2779
2908
  };
2780
2909
  }
2910
+ function getKioskOwnerCapAddressOwnerChange(tx) {
2911
+ const ownerCapChanges = (tx.objectChanges ?? []).filter(
2912
+ (change) => getString(change.objectType)?.endsWith("::kiosk::KioskOwnerCap") && getString(change.type) !== "created"
2913
+ );
2914
+ const addressOwners = [
2915
+ ...new Set(
2916
+ ownerCapChanges.flatMap((change) => {
2917
+ const owner = normalizeOwnerRef(change.owner ?? change.recipient);
2918
+ return owner?.kind === "address" ? [owner.value] : [];
2919
+ })
2920
+ )
2921
+ ];
2922
+ if (addressOwners.length !== 1) {
2923
+ return null;
2924
+ }
2925
+ const sender = getString(ownerCapChanges[0]?.sender) ?? getString(tx.transaction?.data?.sender);
2926
+ if (sender === addressOwners[0]) {
2927
+ return null;
2928
+ }
2929
+ return {
2930
+ sender,
2931
+ nextOwner: { value: addressOwners[0], kind: "address" },
2932
+ objectType: getString(ownerCapChanges[0]?.objectType)
2933
+ };
2934
+ }
2781
2935
  function eventTouchesAsset(event, assetId) {
2782
2936
  if (!isRecord(event.parsedJson)) {
2783
2937
  return false;
@@ -2859,6 +3013,7 @@ function classifySuiTransactionForAsset(tx, assetId) {
2859
3013
  const saleEvent = findFirstEvent(assetEvents, [
2860
3014
  TRADEPORT_BUY_SIMPLE_EVENT_SUFFIX,
2861
3015
  TRADEPORT_MATCH_SINGLE_BID_EVENT_SUFFIX,
3016
+ KIOSK_BIDDING_MATCH_COLLECTION_BID_EVENT_SUFFIX,
2862
3017
  TRADEPORT_KIOSK_BUY_EVENT_SUFFIX
2863
3018
  ]);
2864
3019
  if (saleEvent) {
@@ -2960,7 +3115,10 @@ function classifySuiTransactionForAsset(tx, assetId) {
2960
3115
  ownerChange
2961
3116
  };
2962
3117
  }
2963
- const listingEvent = findFirstEvent(assetEvents, [TRADEPORT_ADD_LISTING_EVENT_SUFFIX]);
3118
+ const listingEvent = findFirstEvent(assetEvents, [
3119
+ TRADEPORT_ADD_LISTING_EVENT_SUFFIX,
3120
+ TRADEPORT_CREATE_SIMPLE_LISTING_EVENT_SUFFIX
3121
+ ]);
2964
3122
  if (listingEvent) {
2965
3123
  return {
2966
3124
  assetId,
@@ -3143,6 +3301,31 @@ function classifySuiTransactionForAsset(tx, assetId) {
3143
3301
  ownerChange
3144
3302
  };
3145
3303
  }
3304
+ const kioskOwnerCapChange = getKioskOwnerCapAddressOwnerChange(tx);
3305
+ if ((tx.events?.length ?? 0) === 0 && kioskOwnerCapChange?.nextOwner?.kind === "address") {
3306
+ return {
3307
+ assetId,
3308
+ family: "GENERIC_OBJECT_TRANSFER",
3309
+ effect: "OWNERSHIP_CHANGE_ONLY",
3310
+ materialization: "PERSIST_FLOW",
3311
+ confidence: "medium",
3312
+ derivedType: "TRANSFER",
3313
+ matcherId: "sui.kiosk-owner-cap-transfer",
3314
+ reason: "matched_kiosk_owner_cap_address_transfer",
3315
+ hasOwnershipChange: true,
3316
+ shouldExtractValueMovements: false,
3317
+ eventTypes,
3318
+ market: null,
3319
+ priceRaw: null,
3320
+ currency: null,
3321
+ ownerChange: {
3322
+ sender: kioskOwnerCapChange.sender,
3323
+ nextOwner: kioskOwnerCapChange.nextOwner.value,
3324
+ nextOwnerKind: kioskOwnerCapChange.nextOwner.kind,
3325
+ objectType: kioskOwnerCapChange.objectType
3326
+ }
3327
+ };
3328
+ }
3146
3329
  return buildClassification(tx, assetId, {
3147
3330
  family: "UNKNOWN",
3148
3331
  effect: "UNKNOWN",
@@ -3160,6 +3343,7 @@ function classifySuiTransactionForAsset(tx, assetId) {
3160
3343
  }
3161
3344
 
3162
3345
  // src/chains/sui/normalize.ts
3346
+ var MIST_PER_SUI = 1e9;
3163
3347
  function parseSaleParticipants(saleEvent) {
3164
3348
  if (!saleEvent || typeof saleEvent.parsedJson !== "object" || saleEvent.parsedJson === null) {
3165
3349
  return {
@@ -3326,6 +3510,114 @@ function buildFlow2(tx, timestamp, params) {
3326
3510
  derivedType: params.derivedType
3327
3511
  };
3328
3512
  }
3513
+ function parsePositiveRawAmount(value) {
3514
+ const raw = typeof value === "string" ? value : null;
3515
+ if (!raw) {
3516
+ return null;
3517
+ }
3518
+ try {
3519
+ return BigInt(raw) > 0n ? raw : null;
3520
+ } catch {
3521
+ return null;
3522
+ }
3523
+ }
3524
+ function toSuiAmount(rawAmount) {
3525
+ try {
3526
+ const amount = Number(rawAmount);
3527
+ if (!Number.isFinite(amount) || amount <= 0) {
3528
+ return null;
3529
+ }
3530
+ return amount / MIST_PER_SUI;
3531
+ } catch {
3532
+ return null;
3533
+ }
3534
+ }
3535
+ function buildValueMovement(tx, timestamp, params) {
3536
+ const amount = toSuiAmount(params.rawAmount);
3537
+ if (!amount) {
3538
+ return null;
3539
+ }
3540
+ return {
3541
+ signature: tx.digest,
3542
+ slot: resolveSlot(tx),
3543
+ timestamp,
3544
+ heliusType: "SUI",
3545
+ source: "sui",
3546
+ movementKind: "NATIVE",
3547
+ currency: "SUI",
3548
+ amount,
3549
+ rawAmount: params.rawAmount,
3550
+ decimals: 9,
3551
+ fromAddress: params.fromAddress,
3552
+ toAddress: params.toAddress,
3553
+ movementIndex: params.movementIndex,
3554
+ tokenStandard: null,
3555
+ role: params.role,
3556
+ confidence: "HIGH",
3557
+ isSynthetic: true
3558
+ };
3559
+ }
3560
+ function buildSaleValueMovements(tx, assetId, timestamp) {
3561
+ const saleEvent = findFirstEvent(getAssetEvents(tx, assetId), [
3562
+ TRADEPORT_BUY_SIMPLE_EVENT_SUFFIX,
3563
+ TRADEPORT_MATCH_SINGLE_BID_EVENT_SUFFIX,
3564
+ KIOSK_BIDDING_MATCH_COLLECTION_BID_EVENT_SUFFIX,
3565
+ TRADEPORT_KIOSK_BUY_EVENT_SUFFIX
3566
+ ]);
3567
+ if (!saleEvent || !isRecord(saleEvent.parsedJson)) {
3568
+ return [];
3569
+ }
3570
+ const parsedJson = saleEvent.parsedJson;
3571
+ const rawAmount = parsePositiveRawAmount(parsedJson.price);
3572
+ if (!rawAmount) {
3573
+ return [];
3574
+ }
3575
+ const movement = buildValueMovement(tx, timestamp, {
3576
+ movementIndex: 0,
3577
+ rawAmount,
3578
+ fromAddress: getString(parsedJson.buyer),
3579
+ toAddress: getString(parsedJson.seller),
3580
+ role: "SALE_GROSS"
3581
+ });
3582
+ return movement ? [movement] : [];
3583
+ }
3584
+ function buildSwapValueMovements(tx, assetId, timestamp) {
3585
+ const swapEvent = getAssetEvents(tx, assetId).find((event) => event.type.includes("SwapExecuted")) ?? null;
3586
+ if (!swapEvent || !isRecord(swapEvent.parsedJson)) {
3587
+ return [];
3588
+ }
3589
+ const parsedJson = swapEvent.parsedJson;
3590
+ const proposer = getString(parsedJson.proposer);
3591
+ const counterparty = getString(parsedJson.counterparty);
3592
+ const movements = [];
3593
+ const proposerRawAmount = parsePositiveRawAmount(parsedJson.proposer_sui_amount);
3594
+ const counterpartyRawAmount = parsePositiveRawAmount(parsedJson.counterparty_sui_amount);
3595
+ if (proposerRawAmount) {
3596
+ const movement = buildValueMovement(tx, timestamp, {
3597
+ movementIndex: movements.length,
3598
+ rawAmount: proposerRawAmount,
3599
+ fromAddress: proposer,
3600
+ toAddress: counterparty,
3601
+ role: "SWAP_CONSIDERATION"
3602
+ });
3603
+ if (movement) {
3604
+ movements.push(movement);
3605
+ }
3606
+ }
3607
+ if (counterpartyRawAmount) {
3608
+ const movement = buildValueMovement(tx, timestamp, {
3609
+ movementIndex: movements.length,
3610
+ rawAmount: counterpartyRawAmount,
3611
+ fromAddress: counterparty,
3612
+ toAddress: proposer,
3613
+ role: "SWAP_CONSIDERATION"
3614
+ });
3615
+ if (movement) {
3616
+ movements.push(movement);
3617
+ }
3618
+ }
3619
+ return movements;
3620
+ }
3329
3621
  function buildSuiFlows(tx, assetId, classification2, currentBeneficialOwner) {
3330
3622
  if (classification2.materialization !== "PERSIST_FLOW") {
3331
3623
  return [];
@@ -3333,8 +3625,10 @@ function buildSuiFlows(tx, assetId, classification2, currentBeneficialOwner) {
3333
3625
  const timestamp = resolveTimestamp(tx);
3334
3626
  const assetEvents = getAssetEvents(tx, assetId);
3335
3627
  const ownerChange = getAssetOwnerChangeDetails(tx, assetId);
3628
+ const kioskOwnerCapChange = getKioskOwnerCapAddressOwnerChange(tx);
3336
3629
  const sender = getString(tx.transaction?.data?.sender) ?? ownerChange?.sender ?? null;
3337
3630
  const addressOwner = (ownerChange?.nextOwner?.kind === "address" ? ownerChange.nextOwner.value : null) ?? resolveNextBeneficialOwner(tx, ownerChange);
3631
+ const kioskOwnerCapAddressOwner = kioskOwnerCapChange?.nextOwner?.kind === "address" ? kioskOwnerCapChange.nextOwner.value : null;
3338
3632
  let fromAddress = null;
3339
3633
  let toAddress = null;
3340
3634
  switch (classification2.derivedType) {
@@ -3345,6 +3639,7 @@ function buildSuiFlows(tx, assetId, classification2, currentBeneficialOwner) {
3345
3639
  const saleEvent = findFirstEvent(assetEvents, [
3346
3640
  TRADEPORT_BUY_SIMPLE_EVENT_SUFFIX,
3347
3641
  TRADEPORT_MATCH_SINGLE_BID_EVENT_SUFFIX,
3642
+ KIOSK_BIDDING_MATCH_COLLECTION_BID_EVENT_SUFFIX,
3348
3643
  TRADEPORT_KIOSK_BUY_EVENT_SUFFIX
3349
3644
  ]);
3350
3645
  const sale = parseSaleParticipants(saleEvent);
@@ -3361,10 +3656,14 @@ function buildSuiFlows(tx, assetId, classification2, currentBeneficialOwner) {
3361
3656
  }
3362
3657
  case "TRANSFER":
3363
3658
  fromAddress = currentBeneficialOwner ?? sender ?? null;
3364
- toAddress = addressOwner ?? (ownerChange?.nextOwner?.kind === "object" ? sender ?? currentBeneficialOwner ?? null : sender ?? null);
3659
+ toAddress = (classification2.matcherId === "sui.kiosk-owner-cap-transfer" ? kioskOwnerCapAddressOwner : null) ?? addressOwner ?? (ownerChange?.nextOwner?.kind === "object" ? sender ?? currentBeneficialOwner ?? null : sender ?? null);
3365
3660
  break;
3366
3661
  case "LIST": {
3367
- const listingEvent = findFirstEvent(assetEvents, [TRADEPORT_ADD_LISTING_EVENT_SUFFIX, KIOSK_LIST_EVENT_SUFFIX]);
3662
+ const listingEvent = findFirstEvent(assetEvents, [
3663
+ TRADEPORT_ADD_LISTING_EVENT_SUFFIX,
3664
+ TRADEPORT_CREATE_SIMPLE_LISTING_EVENT_SUFFIX,
3665
+ KIOSK_LIST_EVENT_SUFFIX
3666
+ ]);
3368
3667
  const owner = parseListingSeller(listingEvent) ?? currentBeneficialOwner ?? sender ?? addressOwner;
3369
3668
  fromAddress = owner;
3370
3669
  toAddress = owner;
@@ -3420,17 +3719,39 @@ function extractAssetFlows(transactions, assetId, classificationCache = /* @__PU
3420
3719
  }
3421
3720
  return collapseRedundantCustodyFlows(canonicalizeMintFlows(flows));
3422
3721
  }
3722
+ function extractSuiValueMovements(transactions, assetId, classificationCache = /* @__PURE__ */ new Map()) {
3723
+ const movements = [];
3724
+ for (const tx of sortTransactions(transactions)) {
3725
+ const classification2 = classificationCache.get(tx.digest) ?? classifySuiTransactionForAsset(tx, assetId);
3726
+ classificationCache.set(tx.digest, classification2);
3727
+ if (classification2.materialization !== "PERSIST_FLOW" || !classification2.shouldExtractValueMovements) {
3728
+ continue;
3729
+ }
3730
+ const timestamp = resolveTimestamp(tx);
3731
+ if (classification2.derivedType === "SALE") {
3732
+ movements.push(...buildSaleValueMovements(tx, assetId, timestamp));
3733
+ continue;
3734
+ }
3735
+ if (classification2.derivedType === "SWAP") {
3736
+ movements.push(...buildSwapValueMovements(tx, assetId, timestamp));
3737
+ }
3738
+ }
3739
+ return canonicalizeValueMovements(movements);
3740
+ }
3423
3741
  function normalizeAssetHistory(transactions, assetId) {
3424
3742
  const classificationCache = /* @__PURE__ */ new Map();
3425
3743
  const flows = extractAssetFlows(transactions, assetId, classificationCache);
3744
+ const valueMovements = extractSuiValueMovements(transactions, assetId, classificationCache);
3426
3745
  const periods = reconstructOwnershipPeriods(flows);
3427
3746
  return {
3428
3747
  flows,
3748
+ valueMovements,
3429
3749
  periods
3430
3750
  };
3431
3751
  }
3432
3752
  export {
3433
3753
  KIOSK_BIDDING_CLAIM_EVENT_SUFFIX,
3754
+ KIOSK_BIDDING_MATCH_COLLECTION_BID_EVENT_SUFFIX,
3434
3755
  KIOSK_CLAIM_EVENT_SUFFIX,
3435
3756
  KIOSK_LIST_EVENT_SUFFIX,
3436
3757
  KIOSK_TRANSFER_EVENT_SUFFIX,
@@ -3444,6 +3765,7 @@ export {
3444
3765
  TRADEPORT_ADD_LISTING_EVENT_SUFFIX,
3445
3766
  TRADEPORT_BUY_SIMPLE_EVENT_SUFFIX,
3446
3767
  TRADEPORT_CANCEL_SIMPLE_EVENT_SUFFIX,
3768
+ TRADEPORT_CREATE_SIMPLE_LISTING_EVENT_SUFFIX,
3447
3769
  TRADEPORT_CREATE_SINGLE_BID_EVENT_SUFFIX,
3448
3770
  TRADEPORT_KIOSK_BUY_EVENT_SUFFIX,
3449
3771
  TRADEPORT_MATCH_SINGLE_BID_EVENT_SUFFIX,
@@ -3474,6 +3796,7 @@ export {
3474
3796
  extractCandidateMintsFromRawTransaction,
3475
3797
  extractMintFlows,
3476
3798
  extractCandidateMintsFromEnhancedTx as extractMints,
3799
+ extractSuiValueMovements,
3477
3800
  extractValueMovements,
3478
3801
  findFirstEvent,
3479
3802
  getAssetEvents,
@@ -3481,6 +3804,7 @@ export {
3481
3804
  getAssetOwnerChange,
3482
3805
  getAssetOwnerChangeDetails,
3483
3806
  getEventTypes,
3807
+ getKioskOwnerCapAddressOwnerChange,
3484
3808
  getMarket,
3485
3809
  getMatchingAssetFlowMatchers,
3486
3810
  getMintNftTransfers,
@@ -3488,6 +3812,7 @@ export {
3488
3812
  getRecordString,
3489
3813
  getRecordStringArray,
3490
3814
  getString,
3815
+ hasLoanProgramSettlement,
3491
3816
  hasMeaningfulNativeSettlement,
3492
3817
  hasMeaningfulSuiSettlement,
3493
3818
  interpretMintFlowProtocolEvent,