@indexing/jiti 0.1.18 → 0.1.20

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/main.js CHANGED
@@ -695,85 +695,82 @@ const $25d5bdd23cba31eb$export$ace043a4f2efe476 = {
695
695
  if (!tx?.events || !Array.isArray(tx.events)) continue;
696
696
  const timestamp = tx.timestamp ? new Date(parseInt(tx.timestamp, 10) / 1000).toISOString() : null;
697
697
  const gasUsed = BigInt(tx.gas_used || "0");
698
- const transfersByKey = {};
699
- const sender = tx.sender;
700
- const accountAddressMap = {};
701
- const changes = tx.changes;
702
- for(let ci = 0; ci < changes.length; ci += 1){
703
- if (!changes[ci]?.data) continue;
704
- const { address: accountAddress, data: { data: changeData, type: changeType } } = changes[ci];
705
- const { balance: balance, metadata: metadata, owner: owner } = changeData || {};
706
- if (changeType === "0x1::fungible_asset::FungibleStore") {
707
- if (!accountAddressMap[accountAddress]) accountAddressMap[accountAddress] = changes[ci + 1]?.data?.data?.owner;
708
- const to = accountAddressMap[accountAddress];
709
- const tokenAddress = metadata?.inner;
710
- if (!to || !tokenAddress || to === sender) continue;
698
+ const legs = {};
699
+ const changes = tx.changes || [];
700
+ // Fungible-asset Deposit/Withdraw events carry only the fungible-store
701
+ // object address, not its owner (guid.account_address is 0x0 and older
702
+ // events omit store_owner). The owner is recoverable from the store's
703
+ // 0x1::object::ObjectCore change, present in the same tx — build a
704
+ // store -> owner map so the events pass below resolves real accounts.
705
+ const storeOwner = {};
706
+ for (const change of changes)if (change?.data?.type === "0x1::object::ObjectCore" && change.data.data?.owner) storeOwner[change.address] = change.data.data.owner;
707
+ // Group canonical Withdraw + Deposit half-legs by (token, amount). Only
708
+ // the framework's own 0x1::fungible_asset / 0x1::coin events are
709
+ // authoritative an asset module may ALSO emit its own
710
+ // <module>::{Withdraw,Deposit} for the same movement (e.g. a stablecoin),
711
+ // which must not be double-counted. Excluding those non-framework
712
+ // duplicates here (rather than relying on the (token, amount) bucket to
713
+ // collapse them) means each bucket holds one entry per REAL half-leg, so N
714
+ // distinct transfers of the same asset + amount in one tx are preserved
715
+ // instead of collapsed into one.
716
+ const canonical = /^0x1::(fungible_asset::(Withdraw|Deposit)|coin::(Withdraw|Deposit)Event)$/;
717
+ for (const evt of tx.events){
718
+ const evtType = evt.type;
719
+ if (!canonical.test(evtType)) continue;
720
+ const data = evt.data;
721
+ const amount = data.amount;
722
+ const accountAddr = data.store_owner || (data.store ? storeOwner[data.store] : undefined) || evt.guid?.account_address || "";
723
+ // Resolve the asset id. Fungible-asset events carry a `store` whose
724
+ // metadata object is the asset; legacy coin events carry no store — the
725
+ // coin type is the <T> of the owner's 0x1::coin::CoinStore<T> change.
726
+ // Defaulting to APT only when neither resolves avoids mislabeling a
727
+ // non-APT legacy coin as native.
728
+ let tokenAddr = "0x1::aptos_coin::AptosCoin";
729
+ if (data.store) tokenAddr = changes.find((c)=>c.address === data.store && c.data.type === "0x1::fungible_asset::FungibleStore")?.data?.data?.metadata?.inner || data.store;
730
+ else {
731
+ const coinType = changes.find((c)=>c.address === accountAddr && /^0x1::coin::CoinStore<.+>$/.test(c.data?.type ?? ""))?.data?.type?.match(/^0x1::coin::CoinStore<(.+)>$/)?.[1];
732
+ if (coinType) tokenAddr = coinType;
733
+ }
734
+ const key = `${tokenAddr}-${amount}`;
735
+ if (!legs[key]) legs[key] = {
736
+ token: tokenAddr,
737
+ amount: amount,
738
+ froms: [],
739
+ tos: []
740
+ };
741
+ if (evtType.includes("Withdraw")) legs[key].froms.push(accountAddr);
742
+ else legs[key].tos.push(accountAddr);
743
+ }
744
+ for (const leg of Object.values(legs)){
745
+ // Native APT is the legacy coin (0x1::aptos_coin::AptosCoin) and its
746
+ // migrated fungible-asset metadata object (0xa / its zero-padded long
747
+ // form): both normalize to a null token with tokenType NATIVE. Every
748
+ // other asset keeps its (lowercased) address.
749
+ const tokenAddrLower = leg.token.toLowerCase();
750
+ const isNative = tokenAddrLower.includes("aptos_coin") || tokenAddrLower.replace(/^0x/, "").replace(/^0+/, "") === "a";
751
+ // Pair each withdrawal with a deposit of the same (token, amount), in
752
+ // event order. A balanced transfer has one of each; a one-sender→many or
753
+ // many→one-recipient batch pairs index-wise. Half-legs with no
754
+ // counterpart (mints, burns, fee-only legs) fall outside the paired
755
+ // range and are dropped.
756
+ const pairCount = Math.min(leg.froms.length, leg.tos.length);
757
+ for(let i = 0; i < pairCount; i += 1){
758
+ const from = leg.froms[i];
759
+ const to = leg.tos[i];
760
+ if (!from || !to || to === "0x00") continue;
711
761
  transfers.push({
712
- amount: BigInt(balance),
762
+ amount: BigInt(leg.amount || 0),
713
763
  blockNumber: parseInt(typedBlock.block_height, 10),
714
- from: sender,
764
+ from: from,
715
765
  to: to,
716
766
  timestamp: timestamp,
717
- token: tokenAddress,
718
- tokenType: "TOKEN",
767
+ token: isNative ? null : tokenAddrLower || null,
768
+ tokenType: isNative ? "NATIVE" : "TOKEN",
719
769
  transactionGasFee: gasUsed,
720
770
  transactionHash: tx.hash
721
771
  });
722
- } else if (owner?.length > 4) {
723
- const payload = tx.payload;
724
- if (payload && payload.function === "0x1::aptos_account::transfer_coins") {
725
- const coinType = payload.type_arguments?.[0];
726
- const isNative = !coinType || coinType.toLowerCase().includes("aptos_coin");
727
- transfers.push({
728
- amount: BigInt(payload.arguments[1]),
729
- blockNumber: parseInt(typedBlock.block_height, 10),
730
- from: sender,
731
- to: owner,
732
- timestamp: timestamp,
733
- token: isNative ? null : coinType.toLowerCase(),
734
- tokenType: isNative ? "NATIVE" : "TOKEN",
735
- transactionGasFee: gasUsed,
736
- transactionHash: tx.hash
737
- });
738
- }
739
- }
740
- }
741
- for (const evt of tx.events){
742
- const evtType = evt.type;
743
- if (/::(Withdraw|Deposit)[^:]*/.test(evtType)) {
744
- const data = evt.data;
745
- const amount = data.amount;
746
- const accountAddr = data.store_owner || evt.guid?.account_address || "";
747
- let tokenAddr = "0x1::aptos_coin::AptosCoin";
748
- if (data.store) tokenAddr = tx.changes.find((c)=>c.address === data.store && c.data.type === "0x1::fungible_asset::FungibleStore")?.data?.data?.metadata?.inner || data.store;
749
- const compositeKey = `${tx.hash}-${tokenAddr}-${amount}`;
750
- if (!transfersByKey[compositeKey]) transfersByKey[compositeKey] = {
751
- amount: amount,
752
- tokenAddress: tokenAddr
753
- };
754
- if (/::Withdraw[^:]*/.test(evtType)) transfersByKey[compositeKey].from = accountAddr;
755
- else transfersByKey[compositeKey].to = accountAddr;
756
772
  }
757
773
  }
758
- for (const partial of Object.values(transfersByKey)){
759
- if (!partial.from || !partial.to) continue;
760
- if (partial.to === "0x00") continue;
761
- let finalToken = null;
762
- let finalTokenType = "TOKEN";
763
- if (partial.tokenAddress?.toLowerCase().includes("aptos_coin")) finalToken = null;
764
- else finalToken = partial.tokenAddress?.toLowerCase();
765
- transfers.push({
766
- amount: BigInt(partial.amount || 0),
767
- blockNumber: parseInt(typedBlock.block_height, 10),
768
- from: partial.from,
769
- to: partial.to,
770
- timestamp: timestamp,
771
- token: finalToken,
772
- tokenType: finalTokenType,
773
- transactionGasFee: gasUsed,
774
- transactionHash: tx.hash
775
- });
776
- }
777
774
  }
778
775
  return transfers.filter((txfer)=>txfer.to?.length > 4).map((txfer)=>{
779
776
  const fromAddr = txfer.from.length < 66 ? `0x${txfer.from.slice(2).padStart(64, "0")}` : txfer.from;
@@ -800,7 +797,7 @@ const $25d5bdd23cba31eb$export$ace043a4f2efe476 = {
800
797
  to: "0x3b5d2e7e8da86903beb19d5a7135764aac812e18af193895d75f3a8f6a066cb0",
801
798
  timestamp: "2025-03-02T21:07:06.002Z",
802
799
  token: null,
803
- tokenType: "TOKEN",
800
+ tokenType: "NATIVE",
804
801
  transactionGasFee: 13n,
805
802
  transactionHash: "0xfbdef795d11df124cca264f3370b09fb04fb1c1d24a2d2e1df0693c096a76d13"
806
803
  },
@@ -811,7 +808,7 @@ const $25d5bdd23cba31eb$export$ace043a4f2efe476 = {
811
808
  to: "0x04b2b6bc8c2c5794c51607c962f482593f9b5ea09373a8ce249a1f799cca7a1e",
812
809
  timestamp: "2025-03-02T21:07:06.002Z",
813
810
  token: null,
814
- tokenType: "TOKEN",
811
+ tokenType: "NATIVE",
815
812
  transactionGasFee: 13n,
816
813
  transactionHash: "0xfbdef795d11df124cca264f3370b09fb04fb1c1d24a2d2e1df0693c096a76d13"
817
814
  }
@@ -824,17 +821,6 @@ const $25d5bdd23cba31eb$export$ace043a4f2efe476 = {
824
821
  },
825
822
  payload: "https://jiti.indexing.co/networks/aptos/303623631",
826
823
  output: [
827
- {
828
- amount: 3000160n,
829
- blockNumber: 303623631,
830
- from: "0xa4e7455d27731ab857e9701b1e6ed72591132b909fe6e4fd99b66c1d6318d9e8",
831
- timestamp: "2025-03-14T15:39:49.845Z",
832
- to: "0x9317336bfc9ba6987d40492ddea8d41e11b7c2e473f3556a9c82309d326e79ce",
833
- token: "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b",
834
- tokenType: "TOKEN",
835
- transactionGasFee: 16n,
836
- transactionHash: "0x24b8854bad1f6543b35069eacd6ec40a583ca7fa452b422b04d747d24b65279c"
837
- },
838
824
  {
839
825
  amount: 1000060n,
840
826
  blockNumber: 303623631,
@@ -877,17 +863,6 @@ const $25d5bdd23cba31eb$export$ace043a4f2efe476 = {
877
863
  },
878
864
  payload: "https://jiti.indexing.co/networks/aptos/403873552",
879
865
  output: [
880
- {
881
- amount: 19500000n,
882
- blockNumber: 403873552,
883
- from: "0x8509aa39bc09ea530b0481c573c0215781c01fa363c135996614bb11cf337703",
884
- timestamp: "2025-08-11T11:07:54.286Z",
885
- to: "0x089556578008574ed3fddda6bc2ea6bee475b042e237bbb2f447c263086edcc5",
886
- token: "0xa",
887
- tokenType: "TOKEN",
888
- transactionGasFee: 18n,
889
- transactionHash: "0xa7d2d682f6940c1023d95ae8950b8d1dc0604abd34a4a2cc654f1be6f330ca44"
890
- },
891
866
  {
892
867
  amount: 9500000n,
893
868
  blockNumber: 403873552,
@@ -901,9 +876,12 @@ const $25d5bdd23cba31eb$export$ace043a4f2efe476 = {
901
876
  }
902
877
  ]
903
878
  },
904
- // transfer_coins with non-native token (PROPS) should NOT produce token: null
905
- // Bug: the transfer_coins code path blindly sets token: null, tokenType: 'NATIVE'
906
- // even when type_arguments indicates a non-native coin
879
+ // Migrated coin (PROPS) sent via aptos_account::transfer_coins. One real
880
+ // movement 18_740_000_000_000 from the sender to 0xf520…, reported once by
881
+ // the asset's fungible-asset metadata object (0x6dba…). Previously this
882
+ // produced three rows: a spurious self-transfer (to === sender), a
883
+ // post-balance amount (53_458_526_229_170 = the recipient's resulting
884
+ // balance), and a coin-type-named duplicate.
907
885
  {
908
886
  params: {
909
887
  network: "APTOS",
@@ -915,34 +893,451 @@ const $25d5bdd23cba31eb$export$ace043a4f2efe476 = {
915
893
  amount: 18740000000000n,
916
894
  blockNumber: 661127894,
917
895
  from: "0xaa0090c74e4976834ff1b9b9ef945e1c4b6cdb49cccf37c2554ef026081312f1",
918
- to: "0xaa0090c74e4976834ff1b9b9ef945e1c4b6cdb49cccf37c2554ef026081312f1",
896
+ to: "0xf520886f20b097e2e2e4116ab66d943f13a3f107d2ba09f6f1abc38e872b234c",
919
897
  timestamp: "2026-03-13T15:33:48.659Z",
920
- token: "0xe50684a338db732d8fb8a3ac71c4b8633878bd0193bca5de2ebc852a83b35099::propbase_coin::props",
898
+ token: "0x6dba1728c73363be1bdd4d504844c40fbb893e368ccbeff1d1bd83497dbc756d",
921
899
  tokenType: "TOKEN",
922
900
  transactionGasFee: 16n,
923
901
  transactionHash: "0xaaec78039e7392b430c554bc33291c2786a1c92669e8f8f88280f419b0792d29"
902
+ }
903
+ ]
904
+ },
905
+ // Legacy (unpaired) coin moved via 0x1::coin Withdraw/Deposit events: the
906
+ // events carry no fungible-store, so the asset is the <T> of the owner's
907
+ // 0x1::coin::CoinStore<T> change. A non-APT coin must surface as that TOKEN,
908
+ // not be mislabeled native. (Synthetic block — these are increasingly rare
909
+ // on mainnet as coins migrate to the fungible-asset standard.)
910
+ {
911
+ params: {
912
+ network: "APTOS"
913
+ },
914
+ payload: {
915
+ _network: "APTOS",
916
+ block_height: "999000001",
917
+ transactions: [
918
+ {
919
+ type: "user_transaction",
920
+ hash: "0x1111111111111111111111111111111111111111111111111111111111111111",
921
+ timestamp: "1700000000000000",
922
+ gas_used: "7",
923
+ sender: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
924
+ events: [
925
+ {
926
+ type: "0x1::coin::WithdrawEvent",
927
+ guid: {
928
+ account_address: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
929
+ },
930
+ data: {
931
+ amount: "500"
932
+ }
933
+ },
934
+ {
935
+ type: "0x1::coin::DepositEvent",
936
+ guid: {
937
+ account_address: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2"
938
+ },
939
+ data: {
940
+ amount: "500"
941
+ }
942
+ }
943
+ ],
944
+ changes: [
945
+ {
946
+ address: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
947
+ data: {
948
+ type: "0x1::coin::CoinStore<0xe5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5::usdc::USDC>",
949
+ data: {}
950
+ }
951
+ },
952
+ {
953
+ address: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
954
+ data: {
955
+ type: "0x1::coin::CoinStore<0xe5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5::usdc::USDC>",
956
+ data: {}
957
+ }
958
+ }
959
+ ]
960
+ }
961
+ ]
962
+ },
963
+ output: [
964
+ {
965
+ amount: 500n,
966
+ blockNumber: 999000001,
967
+ from: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
968
+ to: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
969
+ timestamp: "2023-11-14T22:13:20.000Z",
970
+ token: "0xe5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5::usdc::usdc",
971
+ tokenType: "TOKEN",
972
+ transactionGasFee: 7n,
973
+ transactionHash: "0x1111111111111111111111111111111111111111111111111111111111111111"
974
+ }
975
+ ]
976
+ },
977
+ // Legacy APT moved via 0x1::coin events resolves to native (token null,
978
+ // tokenType NATIVE) — the CoinStore<T> is 0x1::aptos_coin::AptosCoin.
979
+ {
980
+ params: {
981
+ network: "APTOS"
982
+ },
983
+ payload: {
984
+ _network: "APTOS",
985
+ block_height: "999000002",
986
+ transactions: [
987
+ {
988
+ type: "user_transaction",
989
+ hash: "0x2222222222222222222222222222222222222222222222222222222222222222",
990
+ timestamp: "1700000000000000",
991
+ gas_used: "3",
992
+ sender: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
993
+ events: [
994
+ {
995
+ type: "0x1::coin::WithdrawEvent",
996
+ guid: {
997
+ account_address: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
998
+ },
999
+ data: {
1000
+ amount: "900"
1001
+ }
1002
+ },
1003
+ {
1004
+ type: "0x1::coin::DepositEvent",
1005
+ guid: {
1006
+ account_address: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2"
1007
+ },
1008
+ data: {
1009
+ amount: "900"
1010
+ }
1011
+ }
1012
+ ],
1013
+ changes: [
1014
+ {
1015
+ address: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
1016
+ data: {
1017
+ type: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>",
1018
+ data: {}
1019
+ }
1020
+ },
1021
+ {
1022
+ address: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
1023
+ data: {
1024
+ type: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>",
1025
+ data: {}
1026
+ }
1027
+ }
1028
+ ]
1029
+ }
1030
+ ]
1031
+ },
1032
+ output: [
1033
+ {
1034
+ amount: 900n,
1035
+ blockNumber: 999000002,
1036
+ from: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
1037
+ to: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
1038
+ timestamp: "2023-11-14T22:13:20.000Z",
1039
+ token: null,
1040
+ tokenType: "NATIVE",
1041
+ transactionGasFee: 3n,
1042
+ transactionHash: "0x2222222222222222222222222222222222222222222222222222222222222222"
1043
+ }
1044
+ ]
1045
+ },
1046
+ // One sender → two recipients of the same fungible asset with DISTINCT
1047
+ // amounts in a single tx: each movement is its own row (no collapse, no
1048
+ // dropped transfer), with owners resolved from the stores' ObjectCore.
1049
+ {
1050
+ params: {
1051
+ network: "APTOS"
1052
+ },
1053
+ payload: {
1054
+ _network: "APTOS",
1055
+ block_height: "999000003",
1056
+ transactions: [
1057
+ {
1058
+ type: "user_transaction",
1059
+ hash: "0x3333333333333333333333333333333333333333333333333333333333333333",
1060
+ timestamp: "1700000000000000",
1061
+ gas_used: "5",
1062
+ sender: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
1063
+ events: [
1064
+ {
1065
+ type: "0x1::fungible_asset::Withdraw",
1066
+ guid: {
1067
+ account_address: "0x0"
1068
+ },
1069
+ data: {
1070
+ store: "0x5151515151515151515151515151515151515151515151515151515151515151",
1071
+ amount: "100"
1072
+ }
1073
+ },
1074
+ {
1075
+ type: "0x1::fungible_asset::Deposit",
1076
+ guid: {
1077
+ account_address: "0x0"
1078
+ },
1079
+ data: {
1080
+ store: "0x5252525252525252525252525252525252525252525252525252525252525252",
1081
+ amount: "100"
1082
+ }
1083
+ },
1084
+ {
1085
+ type: "0x1::fungible_asset::Withdraw",
1086
+ guid: {
1087
+ account_address: "0x0"
1088
+ },
1089
+ data: {
1090
+ store: "0x5151515151515151515151515151515151515151515151515151515151515151",
1091
+ amount: "200"
1092
+ }
1093
+ },
1094
+ {
1095
+ type: "0x1::fungible_asset::Deposit",
1096
+ guid: {
1097
+ account_address: "0x0"
1098
+ },
1099
+ data: {
1100
+ store: "0x5353535353535353535353535353535353535353535353535353535353535353",
1101
+ amount: "200"
1102
+ }
1103
+ }
1104
+ ],
1105
+ changes: [
1106
+ {
1107
+ address: "0x5151515151515151515151515151515151515151515151515151515151515151",
1108
+ data: {
1109
+ type: "0x1::fungible_asset::FungibleStore",
1110
+ data: {
1111
+ metadata: {
1112
+ inner: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4"
1113
+ }
1114
+ }
1115
+ }
1116
+ },
1117
+ {
1118
+ address: "0x5252525252525252525252525252525252525252525252525252525252525252",
1119
+ data: {
1120
+ type: "0x1::fungible_asset::FungibleStore",
1121
+ data: {
1122
+ metadata: {
1123
+ inner: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4"
1124
+ }
1125
+ }
1126
+ }
1127
+ },
1128
+ {
1129
+ address: "0x5353535353535353535353535353535353535353535353535353535353535353",
1130
+ data: {
1131
+ type: "0x1::fungible_asset::FungibleStore",
1132
+ data: {
1133
+ metadata: {
1134
+ inner: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4"
1135
+ }
1136
+ }
1137
+ }
1138
+ },
1139
+ {
1140
+ address: "0x5151515151515151515151515151515151515151515151515151515151515151",
1141
+ data: {
1142
+ type: "0x1::object::ObjectCore",
1143
+ data: {
1144
+ owner: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
1145
+ }
1146
+ }
1147
+ },
1148
+ {
1149
+ address: "0x5252525252525252525252525252525252525252525252525252525252525252",
1150
+ data: {
1151
+ type: "0x1::object::ObjectCore",
1152
+ data: {
1153
+ owner: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2"
1154
+ }
1155
+ }
1156
+ },
1157
+ {
1158
+ address: "0x5353535353535353535353535353535353535353535353535353535353535353",
1159
+ data: {
1160
+ type: "0x1::object::ObjectCore",
1161
+ data: {
1162
+ owner: "0xc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"
1163
+ }
1164
+ }
1165
+ }
1166
+ ]
1167
+ }
1168
+ ]
1169
+ },
1170
+ output: [
1171
+ {
1172
+ amount: 100n,
1173
+ blockNumber: 999000003,
1174
+ from: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
1175
+ to: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
1176
+ timestamp: "2023-11-14T22:13:20.000Z",
1177
+ token: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4",
1178
+ tokenType: "TOKEN",
1179
+ transactionGasFee: 5n,
1180
+ transactionHash: "0x3333333333333333333333333333333333333333333333333333333333333333"
924
1181
  },
925
1182
  {
926
- amount: 53458526229170n,
927
- blockNumber: 661127894,
928
- from: "0xaa0090c74e4976834ff1b9b9ef945e1c4b6cdb49cccf37c2554ef026081312f1",
929
- to: "0xf520886f20b097e2e2e4116ab66d943f13a3f107d2ba09f6f1abc38e872b234c",
930
- timestamp: "2026-03-13T15:33:48.659Z",
931
- token: "0x6dba1728c73363be1bdd4d504844c40fbb893e368ccbeff1d1bd83497dbc756d",
1183
+ amount: 200n,
1184
+ blockNumber: 999000003,
1185
+ from: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
1186
+ to: "0xc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3",
1187
+ timestamp: "2023-11-14T22:13:20.000Z",
1188
+ token: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4",
932
1189
  tokenType: "TOKEN",
933
- transactionGasFee: 16n,
934
- transactionHash: "0xaaec78039e7392b430c554bc33291c2786a1c92669e8f8f88280f419b0792d29"
1190
+ transactionGasFee: 5n,
1191
+ transactionHash: "0x3333333333333333333333333333333333333333333333333333333333333333"
1192
+ }
1193
+ ]
1194
+ },
1195
+ // Two transfers of the SAME asset AND SAME amount in one tx (an equal-split
1196
+ // batch send) must NOT collapse: the canonical half-legs are paired
1197
+ // index-wise, so each surfaces as its own row.
1198
+ {
1199
+ params: {
1200
+ network: "APTOS"
1201
+ },
1202
+ payload: {
1203
+ _network: "APTOS",
1204
+ block_height: "999000004",
1205
+ transactions: [
1206
+ {
1207
+ type: "user_transaction",
1208
+ hash: "0x4444444444444444444444444444444444444444444444444444444444444444",
1209
+ timestamp: "1700000000000000",
1210
+ gas_used: "6",
1211
+ sender: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
1212
+ events: [
1213
+ {
1214
+ type: "0x1::fungible_asset::Withdraw",
1215
+ guid: {
1216
+ account_address: "0x0"
1217
+ },
1218
+ data: {
1219
+ store: "0x5151515151515151515151515151515151515151515151515151515151515151",
1220
+ amount: "100"
1221
+ }
1222
+ },
1223
+ {
1224
+ type: "0x1::fungible_asset::Deposit",
1225
+ guid: {
1226
+ account_address: "0x0"
1227
+ },
1228
+ data: {
1229
+ store: "0x5252525252525252525252525252525252525252525252525252525252525252",
1230
+ amount: "100"
1231
+ }
1232
+ },
1233
+ {
1234
+ type: "0x1::fungible_asset::Withdraw",
1235
+ guid: {
1236
+ account_address: "0x0"
1237
+ },
1238
+ data: {
1239
+ store: "0x5151515151515151515151515151515151515151515151515151515151515151",
1240
+ amount: "100"
1241
+ }
1242
+ },
1243
+ {
1244
+ type: "0x1::fungible_asset::Deposit",
1245
+ guid: {
1246
+ account_address: "0x0"
1247
+ },
1248
+ data: {
1249
+ store: "0x5353535353535353535353535353535353535353535353535353535353535353",
1250
+ amount: "100"
1251
+ }
1252
+ }
1253
+ ],
1254
+ changes: [
1255
+ {
1256
+ address: "0x5151515151515151515151515151515151515151515151515151515151515151",
1257
+ data: {
1258
+ type: "0x1::fungible_asset::FungibleStore",
1259
+ data: {
1260
+ metadata: {
1261
+ inner: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4"
1262
+ }
1263
+ }
1264
+ }
1265
+ },
1266
+ {
1267
+ address: "0x5252525252525252525252525252525252525252525252525252525252525252",
1268
+ data: {
1269
+ type: "0x1::fungible_asset::FungibleStore",
1270
+ data: {
1271
+ metadata: {
1272
+ inner: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4"
1273
+ }
1274
+ }
1275
+ }
1276
+ },
1277
+ {
1278
+ address: "0x5353535353535353535353535353535353535353535353535353535353535353",
1279
+ data: {
1280
+ type: "0x1::fungible_asset::FungibleStore",
1281
+ data: {
1282
+ metadata: {
1283
+ inner: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4"
1284
+ }
1285
+ }
1286
+ }
1287
+ },
1288
+ {
1289
+ address: "0x5151515151515151515151515151515151515151515151515151515151515151",
1290
+ data: {
1291
+ type: "0x1::object::ObjectCore",
1292
+ data: {
1293
+ owner: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
1294
+ }
1295
+ }
1296
+ },
1297
+ {
1298
+ address: "0x5252525252525252525252525252525252525252525252525252525252525252",
1299
+ data: {
1300
+ type: "0x1::object::ObjectCore",
1301
+ data: {
1302
+ owner: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2"
1303
+ }
1304
+ }
1305
+ },
1306
+ {
1307
+ address: "0x5353535353535353535353535353535353535353535353535353535353535353",
1308
+ data: {
1309
+ type: "0x1::object::ObjectCore",
1310
+ data: {
1311
+ owner: "0xc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"
1312
+ }
1313
+ }
1314
+ }
1315
+ ]
1316
+ }
1317
+ ]
1318
+ },
1319
+ output: [
1320
+ {
1321
+ amount: 100n,
1322
+ blockNumber: 999000004,
1323
+ from: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
1324
+ to: "0xb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2",
1325
+ timestamp: "2023-11-14T22:13:20.000Z",
1326
+ token: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4",
1327
+ tokenType: "TOKEN",
1328
+ transactionGasFee: 6n,
1329
+ transactionHash: "0x4444444444444444444444444444444444444444444444444444444444444444"
935
1330
  },
936
1331
  {
937
- amount: 18740000000000n,
938
- blockNumber: 661127894,
939
- from: "0xaa0090c74e4976834ff1b9b9ef945e1c4b6cdb49cccf37c2554ef026081312f1",
940
- to: "0xf520886f20b097e2e2e4116ab66d943f13a3f107d2ba09f6f1abc38e872b234c",
941
- timestamp: "2026-03-13T15:33:48.659Z",
942
- token: "0xe50684a338db732d8fb8a3ac71c4b8633878bd0193bca5de2ebc852a83b35099::propbase_coin::props",
1332
+ amount: 100n,
1333
+ blockNumber: 999000004,
1334
+ from: "0xa1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1",
1335
+ to: "0xc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3",
1336
+ timestamp: "2023-11-14T22:13:20.000Z",
1337
+ token: "0xd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4",
943
1338
  tokenType: "TOKEN",
944
- transactionGasFee: 16n,
945
- transactionHash: "0xaaec78039e7392b430c554bc33291c2786a1c92669e8f8f88280f419b0792d29"
1339
+ transactionGasFee: 6n,
1340
+ transactionHash: "0x4444444444444444444444444444444444444444444444444444444444444444"
946
1341
  }
947
1342
  ]
948
1343
  }
@@ -1788,11 +2183,17 @@ const $2e46ec862e47c14f$export$400f08bfae9ee97f = {
1788
2183
  const deliveredOrAmount = typedTx.metaData?.delivered_amount ?? typedTx.Amount ?? "0";
1789
2184
  let tokenSymbol = "XRP";
1790
2185
  let tokenType = "NATIVE";
2186
+ // An issued currency is uniquely identified by (currency, issuer) — two "USD"
2187
+ // IOUs from different issuers are distinct tokens. Emit the token id as
2188
+ // `<currency>.<issuer>` so consumers can match by issuer; native XRP has no
2189
+ // issuer and keeps the bare "XRP" symbol.
2190
+ let issuer;
1791
2191
  let parsedAmount;
1792
2192
  // Only issued currencies carry an explicit scale; native XRP stays in integer drops.
1793
2193
  let decimals;
1794
2194
  if (typeof deliveredOrAmount === "object") {
1795
2195
  tokenSymbol = $2e46ec862e47c14f$var$decodeXrplCurrency(deliveredOrAmount.currency);
2196
+ issuer = deliveredOrAmount.issuer;
1796
2197
  tokenType = "TOKEN";
1797
2198
  const scaled = $2e46ec862e47c14f$var$xrplIssuedValueToAmount(deliveredOrAmount.value);
1798
2199
  parsedAmount = scaled.amount;
@@ -1808,7 +2209,7 @@ const $2e46ec862e47c14f$export$400f08bfae9ee97f = {
1808
2209
  memo: typedTx.DestinationTag,
1809
2210
  timestamp: typedBlock.close_time_iso || null,
1810
2211
  to: typedTx.Destination ?? "UNKNOWN",
1811
- token: tokenSymbol,
2212
+ token: issuer ? `${tokenSymbol}.${issuer}` : tokenSymbol,
1812
2213
  tokenType: tokenType,
1813
2214
  transactionGasFee: BigInt(typedTx.Fee ?? "0"),
1814
2215
  transactionHash: typedTx.hash ?? ""
@@ -1916,7 +2317,7 @@ const $2e46ec862e47c14f$export$400f08bfae9ee97f = {
1916
2317
  memo: undefined,
1917
2318
  timestamp: "2026-01-01T00:00:00Z",
1918
2319
  to: "rBITXTOxxxxxxxxxxxxxxxxxxxxxxxxxxx",
1919
- token: "BITx",
2320
+ token: "BITx.rBitcoiNXev8VoVxV7pwoQx1sSfonVP9i3",
1920
2321
  tokenType: "TOKEN",
1921
2322
  transactionGasFee: 12n,
1922
2323
  transactionHash: "BITXSMALLHASH"
@@ -1959,7 +2360,7 @@ const $2e46ec862e47c14f$export$400f08bfae9ee97f = {
1959
2360
  memo: undefined,
1960
2361
  timestamp: "2026-01-01T00:00:00Z",
1961
2362
  to: "rUSDTOxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
1962
- token: "USD",
2363
+ token: "USD.rIssuerxxxxxxxxxxxxxxxxxxxxxxxxxxx",
1963
2364
  tokenType: "TOKEN",
1964
2365
  transactionGasFee: 15n,
1965
2366
  transactionHash: "USDWHOLEHASH"
@@ -2002,7 +2403,7 @@ const $2e46ec862e47c14f$export$400f08bfae9ee97f = {
2002
2403
  memo: undefined,
2003
2404
  timestamp: "2026-01-01T00:00:00Z",
2004
2405
  to: "rMAGTOxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
2005
- token: "MAG",
2406
+ token: "MAG.rXmagwMmnFtVet3uL26Q2iwk287SRvVMJ",
2006
2407
  tokenType: "TOKEN",
2007
2408
  transactionGasFee: 10n,
2008
2409
  transactionHash: "MAGSCIHASH"