tbc-tx-parser 0.1.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.
Files changed (183) hide show
  1. package/README.md +214 -0
  2. package/dist/adapters/partial-sha256.d.ts +2 -0
  3. package/dist/adapters/partial-sha256.d.ts.map +1 -0
  4. package/dist/adapters/partial-sha256.js +5 -0
  5. package/dist/adapters/partial-sha256.js.map +1 -0
  6. package/dist/adapters/tbc-lib.d.ts +4 -0
  7. package/dist/adapters/tbc-lib.d.ts.map +1 -0
  8. package/dist/adapters/tbc-lib.js +4 -0
  9. package/dist/adapters/tbc-lib.js.map +1 -0
  10. package/dist/constants/chain.d.ts +4 -0
  11. package/dist/constants/chain.d.ts.map +1 -0
  12. package/dist/constants/chain.js +7 -0
  13. package/dist/constants/chain.js.map +1 -0
  14. package/dist/core/chain.d.ts +10 -0
  15. package/dist/core/chain.d.ts.map +1 -0
  16. package/dist/core/chain.js +41 -0
  17. package/dist/core/chain.js.map +1 -0
  18. package/dist/core/input-utxos.d.ts +3 -0
  19. package/dist/core/input-utxos.d.ts.map +1 -0
  20. package/dist/core/input-utxos.js +69 -0
  21. package/dist/core/input-utxos.js.map +1 -0
  22. package/dist/core/public-chain-compression.d.ts +4 -0
  23. package/dist/core/public-chain-compression.d.ts.map +1 -0
  24. package/dist/core/public-chain-compression.js +87 -0
  25. package/dist/core/public-chain-compression.js.map +1 -0
  26. package/dist/core/public-script.d.ts +4 -0
  27. package/dist/core/public-script.d.ts.map +1 -0
  28. package/dist/core/public-script.js +94 -0
  29. package/dist/core/public-script.js.map +1 -0
  30. package/dist/decode-tx-raw.d.ts +3 -0
  31. package/dist/decode-tx-raw.d.ts.map +1 -0
  32. package/dist/decode-tx-raw.js +350 -0
  33. package/dist/decode-tx-raw.js.map +1 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +3 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/protocols/ft/index.d.ts +7 -0
  39. package/dist/protocols/ft/index.d.ts.map +1 -0
  40. package/dist/protocols/ft/index.js +393 -0
  41. package/dist/protocols/ft/index.js.map +1 -0
  42. package/dist/protocols/index.d.ts +5 -0
  43. package/dist/protocols/index.d.ts.map +1 -0
  44. package/dist/protocols/index.js +61 -0
  45. package/dist/protocols/index.js.map +1 -0
  46. package/dist/protocols/nft/index.d.ts +8 -0
  47. package/dist/protocols/nft/index.d.ts.map +1 -0
  48. package/dist/protocols/nft/index.js +335 -0
  49. package/dist/protocols/nft/index.js.map +1 -0
  50. package/dist/protocols/nft/media.js.map +1 -0
  51. package/dist/protocols/order-book/actions.d.ts +7 -0
  52. package/dist/protocols/order-book/actions.d.ts.map +1 -0
  53. package/dist/protocols/order-book/actions.js +102 -0
  54. package/dist/protocols/order-book/actions.js.map +1 -0
  55. package/dist/protocols/order-book/facts.d.ts +7 -0
  56. package/dist/protocols/order-book/facts.d.ts.map +1 -0
  57. package/dist/protocols/order-book/facts.js +81 -0
  58. package/dist/protocols/order-book/facts.js.map +1 -0
  59. package/dist/protocols/order-book/identity.d.ts +3 -0
  60. package/dist/protocols/order-book/identity.d.ts.map +1 -0
  61. package/dist/protocols/order-book/identity.js +46 -0
  62. package/dist/protocols/order-book/identity.js.map +1 -0
  63. package/dist/protocols/order-book/index.d.ts +12 -0
  64. package/dist/protocols/order-book/index.d.ts.map +1 -0
  65. package/dist/protocols/order-book/index.js +34 -0
  66. package/dist/protocols/order-book/index.js.map +1 -0
  67. package/dist/protocols/order-book/settlement.d.ts +10 -0
  68. package/dist/protocols/order-book/settlement.d.ts.map +1 -0
  69. package/dist/protocols/order-book/settlement.js +318 -0
  70. package/dist/protocols/order-book/settlement.js.map +1 -0
  71. package/dist/protocols/order-book/types.d.ts +101 -0
  72. package/dist/protocols/order-book/types.d.ts.map +1 -0
  73. package/dist/protocols/order-book/types.js +2 -0
  74. package/dist/protocols/order-book/types.js.map +1 -0
  75. package/dist/protocols/piggy-bank/index.d.ts +7 -0
  76. package/dist/protocols/piggy-bank/index.d.ts.map +1 -0
  77. package/dist/protocols/piggy-bank/index.js +91 -0
  78. package/dist/protocols/piggy-bank/index.js.map +1 -0
  79. package/dist/protocols/pool-nft/actions.d.ts +15 -0
  80. package/dist/protocols/pool-nft/actions.d.ts.map +1 -0
  81. package/dist/protocols/pool-nft/actions.js +90 -0
  82. package/dist/protocols/pool-nft/actions.js.map +1 -0
  83. package/dist/protocols/pool-nft/amounts.d.ts +10 -0
  84. package/dist/protocols/pool-nft/amounts.d.ts.map +1 -0
  85. package/dist/protocols/pool-nft/amounts.js +45 -0
  86. package/dist/protocols/pool-nft/amounts.js.map +1 -0
  87. package/dist/protocols/pool-nft/constants.d.ts +15 -0
  88. package/dist/protocols/pool-nft/constants.d.ts.map +1 -0
  89. package/dist/protocols/pool-nft/constants.js +27 -0
  90. package/dist/protocols/pool-nft/constants.js.map +1 -0
  91. package/dist/protocols/pool-nft/facts.d.ts +11 -0
  92. package/dist/protocols/pool-nft/facts.d.ts.map +1 -0
  93. package/dist/protocols/pool-nft/facts.js +113 -0
  94. package/dist/protocols/pool-nft/facts.js.map +1 -0
  95. package/dist/protocols/pool-nft/fees.d.ts +14 -0
  96. package/dist/protocols/pool-nft/fees.d.ts.map +1 -0
  97. package/dist/protocols/pool-nft/fees.js +161 -0
  98. package/dist/protocols/pool-nft/fees.js.map +1 -0
  99. package/dist/protocols/pool-nft/index.d.ts +4 -0
  100. package/dist/protocols/pool-nft/index.d.ts.map +1 -0
  101. package/dist/protocols/pool-nft/index.js +19 -0
  102. package/dist/protocols/pool-nft/index.js.map +1 -0
  103. package/dist/protocols/pool-nft/movements.d.ts +5 -0
  104. package/dist/protocols/pool-nft/movements.d.ts.map +1 -0
  105. package/dist/protocols/pool-nft/movements.js +804 -0
  106. package/dist/protocols/pool-nft/movements.js.map +1 -0
  107. package/dist/protocols/pool-nft/selectors.d.ts +40 -0
  108. package/dist/protocols/pool-nft/selectors.d.ts.map +1 -0
  109. package/dist/protocols/pool-nft/selectors.js +173 -0
  110. package/dist/protocols/pool-nft/selectors.js.map +1 -0
  111. package/dist/protocols/pool-nft/types.d.ts +34 -0
  112. package/dist/protocols/pool-nft/types.d.ts.map +1 -0
  113. package/dist/protocols/pool-nft/types.js +2 -0
  114. package/dist/protocols/pool-nft/types.js.map +1 -0
  115. package/dist/protocols/tbc/index.d.ts +3 -0
  116. package/dist/protocols/tbc/index.d.ts.map +1 -0
  117. package/dist/protocols/tbc/index.js +21 -0
  118. package/dist/protocols/tbc/index.js.map +1 -0
  119. package/dist/protocols/types.d.ts +15 -0
  120. package/dist/protocols/types.d.ts.map +1 -0
  121. package/dist/protocols/types.js +2 -0
  122. package/dist/protocols/types.js.map +1 -0
  123. package/dist/result/index.d.ts +7 -0
  124. package/dist/result/index.d.ts.map +1 -0
  125. package/dist/result/index.js +869 -0
  126. package/dist/result/index.js.map +1 -0
  127. package/dist/result/semantic.d.ts +6 -0
  128. package/dist/result/semantic.d.ts.map +1 -0
  129. package/dist/result/semantic.js +950 -0
  130. package/dist/result/semantic.js.map +1 -0
  131. package/dist/script-parser/contracts.d.ts +7 -0
  132. package/dist/script-parser/contracts.d.ts.map +1 -0
  133. package/dist/script-parser/contracts.js +104 -0
  134. package/dist/script-parser/contracts.js.map +1 -0
  135. package/dist/script-parser/op-return.d.ts +8 -0
  136. package/dist/script-parser/op-return.d.ts.map +1 -0
  137. package/dist/script-parser/op-return.js +315 -0
  138. package/dist/script-parser/op-return.js.map +1 -0
  139. package/dist/script-parser/origin.d.ts +6 -0
  140. package/dist/script-parser/origin.d.ts.map +1 -0
  141. package/dist/script-parser/origin.js +68 -0
  142. package/dist/script-parser/origin.js.map +1 -0
  143. package/dist/script-parser.d.ts +3 -0
  144. package/dist/script-parser.d.ts.map +1 -0
  145. package/dist/script-parser.js +183 -0
  146. package/dist/script-parser.js.map +1 -0
  147. package/dist/types/protocol.d.ts +175 -0
  148. package/dist/types/protocol.d.ts.map +1 -0
  149. package/dist/types/protocol.js +2 -0
  150. package/dist/types/protocol.js.map +1 -0
  151. package/dist/types/result.d.ts +167 -0
  152. package/dist/types/result.d.ts.map +1 -0
  153. package/dist/types/result.js +2 -0
  154. package/dist/types/result.js.map +1 -0
  155. package/dist/types/script.d.ts +180 -0
  156. package/dist/types/script.d.ts.map +1 -0
  157. package/dist/types/script.js +9 -0
  158. package/dist/types/script.js.map +1 -0
  159. package/dist/types/semantic.d.ts +94 -0
  160. package/dist/types/semantic.d.ts.map +1 -0
  161. package/dist/types/semantic.js +2 -0
  162. package/dist/types/semantic.js.map +1 -0
  163. package/dist/types.d.ts +6 -0
  164. package/dist/types.d.ts.map +1 -0
  165. package/dist/types.js +4 -0
  166. package/dist/types.js.map +1 -0
  167. package/dist/utils/asm-converter.d.ts +7 -0
  168. package/dist/utils/asm-converter.d.ts.map +1 -0
  169. package/dist/utils/asm-converter.js +105 -0
  170. package/dist/utils/asm-converter.js.map +1 -0
  171. package/dist/utils/binary.d.ts +3 -0
  172. package/dist/utils/binary.d.ts.map +1 -0
  173. package/dist/utils/binary.js +17 -0
  174. package/dist/utils/binary.js.map +1 -0
  175. package/dist/utils/multi-sig.d.ts +9 -0
  176. package/dist/utils/multi-sig.d.ts.map +1 -0
  177. package/dist/utils/multi-sig.js +125 -0
  178. package/dist/utils/multi-sig.js.map +1 -0
  179. package/dist/utils/token-amount.d.ts +3 -0
  180. package/dist/utils/token-amount.d.ts.map +1 -0
  181. package/dist/utils/token-amount.js +37 -0
  182. package/dist/utils/token-amount.js.map +1 -0
  183. package/package.json +47 -0
@@ -0,0 +1,869 @@
1
+ import { ScriptType, } from '../types.js';
2
+ import { tbc } from '../adapters/tbc-lib.js';
3
+ import { formatTokenRawAmount } from '../utils/token-amount.js';
4
+ // Convert protocol movements, semantic effects, and native outputs into display-oriented asset flows.
5
+ const NATIVE_TBC_ASSET = { type: 'tbc', symbol: 'TBC' };
6
+ const MIN_NON_STANDARD_TBC_FLOW_SATOSHIS = 5_000;
7
+ const FT_CODE_MARKER_HEX = '32436f6465';
8
+ const FTLP_CODE_MARKER_HEX = '02436f6465';
9
+ const ORDER_BOOK_ACTION_BY_TYPE = {
10
+ 'OrderBook buy order': 'placeBuy',
11
+ 'OrderBook sell order': 'placeSell',
12
+ 'OrderBook buy order cancel': 'cancelBuy',
13
+ 'OrderBook sell order cancel': 'cancelSell',
14
+ 'OrderBook match': 'match',
15
+ 'OrderBook order update': 'update',
16
+ };
17
+ function firstInputAddress(inputs) {
18
+ // Fallback source for ordinary TBC and legacy FT semantics; strict protocols should prefer verified movement sources.
19
+ return inputs.map((input) => input.prevout?.address).find((address) => Boolean(address));
20
+ }
21
+ function firstInputOwnerEndpoint(inputs) {
22
+ const input = inputs.find((item) => Boolean(item.prevout?.address ?? item.prevout?.hash));
23
+ if (!input?.prevout) {
24
+ return undefined;
25
+ }
26
+ return {
27
+ address: input.prevout.address,
28
+ hash: input.prevout.hash,
29
+ };
30
+ }
31
+ function scriptLockHash(scriptHex) {
32
+ if (!scriptHex || scriptHex.length % 2 !== 0) {
33
+ return undefined;
34
+ }
35
+ return tbc.crypto.Hash.sha256ripemd160(tbc.crypto.Hash.sha256(Buffer.from(scriptHex, 'hex'))).toString('hex');
36
+ }
37
+ function multiSigAddressByLockHash(inputs, outputs) {
38
+ // Some protocols expose only a lock hash; resolve it to an address when a matching MultiSig script is available.
39
+ const addressByHash = new Map();
40
+ for (const output of outputs) {
41
+ if (output.scriptKind !== 'multisig' || !output.address) {
42
+ continue;
43
+ }
44
+ const hash = scriptLockHash(output.script.hex);
45
+ if (hash) {
46
+ addressByHash.set(hash, output.address);
47
+ }
48
+ }
49
+ for (const input of inputs) {
50
+ const prevout = input.prevout;
51
+ if (prevout?.script.type !== ScriptType.MULTISIG || !prevout.address) {
52
+ continue;
53
+ }
54
+ const hash = scriptLockHash(prevout.script.hex);
55
+ if (hash) {
56
+ addressByHash.set(hash, prevout.address);
57
+ }
58
+ }
59
+ return addressByHash;
60
+ }
61
+ function isFtCodePrevout(input) {
62
+ const hex = input.prevout?.script.hex?.toLowerCase();
63
+ return Boolean(hex?.endsWith(FT_CODE_MARKER_HEX) || hex?.endsWith(FTLP_CODE_MARKER_HEX));
64
+ }
65
+ function ftInputOwnerEndpoint(inputs, outputs) {
66
+ // Prefer the verified FT Code prevout over the first ordinary input as the FT source.
67
+ const ftInput = inputs.find(isFtCodePrevout);
68
+ if (!ftInput?.prevout) {
69
+ return firstInputOwnerEndpoint(inputs);
70
+ }
71
+ const ownerHash = ftInput.prevout.hash ?? ftInput.prevout.script.data?.hash;
72
+ if (ownerHash) {
73
+ const address = multiSigAddressByLockHash(inputs, outputs).get(ownerHash);
74
+ return address ? { address } : { hash: ownerHash };
75
+ }
76
+ if (ftInput.prevout.address) {
77
+ return { address: ftInput.prevout.address };
78
+ }
79
+ return firstInputOwnerEndpoint(inputs);
80
+ }
81
+ function inputAddresses(inputs) {
82
+ return new Set(inputs.map((input) => input.prevout?.address).filter((address) => Boolean(address)));
83
+ }
84
+ function singleInputAddress(inputs) {
85
+ const addresses = inputAddresses(inputs);
86
+ return addresses.size === 1 ? [...addresses][0] : undefined;
87
+ }
88
+ function inputAddressEndpoint(inputs) {
89
+ const addresses = [...inputAddresses(inputs)];
90
+ if (addresses.length === 0) {
91
+ return undefined;
92
+ }
93
+ return addresses.length === 1 ? { address: addresses[0] } : { addresses };
94
+ }
95
+ function nativeTbcSourceInputs(inputs, ftMovements) {
96
+ // FT transactions may include protocol inputs; prefer ordinary spendable inputs as native TBC sources.
97
+ if (ftMovements.length === 0) {
98
+ return inputs;
99
+ }
100
+ const nativeInputs = inputs.filter((input) => input.prevout?.script.type === ScriptType.P2PKH ||
101
+ input.prevout?.script.type === ScriptType.MULTISIG);
102
+ return nativeInputs.length > 0 ? nativeInputs : inputs;
103
+ }
104
+ export function normalizeActionType(transactionType) {
105
+ // Convert display-oriented transaction types into stable camelCase action keys.
106
+ const normalized = transactionType
107
+ .replace(/OrderBook/gu, 'Order Book')
108
+ .replace(/PoolNFT/gu, 'Pool NFT')
109
+ .replace(/StableCoin/gu, 'Stable Coin')
110
+ .replace(/\/+/gu, ' ')
111
+ .replace(/[^a-z0-9]+/giu, ' ')
112
+ .trim()
113
+ .split(/\s+/u)
114
+ .map((part, index) => {
115
+ const lower = part.toLowerCase();
116
+ return index === 0 ? lower : `${lower.charAt(0).toUpperCase()}${lower.slice(1)}`;
117
+ })
118
+ .join('');
119
+ return normalized || 'transaction';
120
+ }
121
+ function tokenTransactionAsset(movement) {
122
+ return {
123
+ type: movement.isStableCoin ? 'stablecoin' : 'ft',
124
+ symbol: movement.symbol,
125
+ name: movement.name,
126
+ contractId: movement.contractId,
127
+ decimal: movement.decimal,
128
+ };
129
+ }
130
+ function tokenTransactionAmount(movement) {
131
+ return {
132
+ raw: movement.rawAmount,
133
+ display: movement.displayAmount,
134
+ decimal: movement.decimal,
135
+ };
136
+ }
137
+ function semanticAssetToTransactionAsset(asset) {
138
+ // Semantics use abstract asset types; asset flows map them back to the public result model.
139
+ if (asset.type === 'native') {
140
+ return NATIVE_TBC_ASSET;
141
+ }
142
+ if (asset.type === 'nftCollection') {
143
+ return {
144
+ type: 'nftCollection',
145
+ name: asset.name,
146
+ collectionId: asset.collectionId,
147
+ contractId: asset.contractId,
148
+ };
149
+ }
150
+ if (asset.type === 'nft') {
151
+ if (asset.subtype === 'poolNft') {
152
+ return {
153
+ type: 'nft',
154
+ subtype: 'poolNft',
155
+ contractId: asset.contractId,
156
+ poolNft: asset.poolNft,
157
+ };
158
+ }
159
+ return {
160
+ type: 'nft',
161
+ symbol: asset.symbol,
162
+ name: asset.name,
163
+ collectionId: asset.collectionId,
164
+ contractId: asset.contractId,
165
+ };
166
+ }
167
+ return {
168
+ type: asset.type,
169
+ symbol: asset.symbol,
170
+ name: asset.name,
171
+ contractId: asset.contractId,
172
+ decimal: asset.decimal,
173
+ };
174
+ }
175
+ function semanticAmountToTransactionAmount(amount) {
176
+ if (amount.satoshis !== undefined) {
177
+ return { satoshis: amount.satoshis };
178
+ }
179
+ return {
180
+ raw: amount.rawAmount ?? '0',
181
+ display: amount.displayAmount,
182
+ decimal: amount.decimal,
183
+ };
184
+ }
185
+ function semanticEffectPurpose(effect, isOrderBook) {
186
+ switch (effect.kind) {
187
+ case 'mint':
188
+ return 'mint';
189
+ case 'state':
190
+ return 'state';
191
+ case 'fee':
192
+ return isOrderBook ? 'transfer' : 'fee';
193
+ case 'orderLock':
194
+ return 'lock';
195
+ case 'orderRelease':
196
+ return 'release';
197
+ case 'self':
198
+ return 'self';
199
+ default:
200
+ return 'transfer';
201
+ }
202
+ }
203
+ function orderBookSemanticContext(semantic) {
204
+ const hasOrderBookRelation = semantic.relations.some((relation) => relation.kind.startsWith('orderBook'));
205
+ if (!hasOrderBookRelation) {
206
+ return undefined;
207
+ }
208
+ const action = ORDER_BOOK_ACTION_BY_TYPE[semantic.type];
209
+ return {
210
+ protocol: 'orderBook',
211
+ recognized: true,
212
+ ...(action ? { action } : {}),
213
+ };
214
+ }
215
+ function endpoint(value) {
216
+ // Remove undefined fields so public results never expose empty endpoint shells.
217
+ if (!value) {
218
+ return undefined;
219
+ }
220
+ const publicEndpoint = withoutUndefinedValues({
221
+ address: value.address,
222
+ addresses: value.addresses && value.addresses.length > 0 ? value.addresses : undefined,
223
+ hash: value.hash,
224
+ poolId: value.poolId,
225
+ txid: value.txid,
226
+ vout: value.vout,
227
+ script: value.script ? withoutUndefinedValues(value.script) : undefined,
228
+ });
229
+ return Object.values(publicEndpoint).some((item) => item !== undefined) ? publicEndpoint : undefined;
230
+ }
231
+ function nativeOutputEndpoint(output) {
232
+ const address = output.address;
233
+ const hash = address ? undefined : output.script.data?.hash;
234
+ const script = address || hash
235
+ ? undefined
236
+ : {
237
+ hex: output.script.hex,
238
+ asm: output.script.asm,
239
+ };
240
+ return endpoint({ address, hash, script });
241
+ }
242
+ function withoutUndefinedValues(value) {
243
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
244
+ }
245
+ function directionForFlow(purpose, from, to) {
246
+ // Direction is a display hint and is conservatively inferred from purpose, source, and destination.
247
+ if (purpose === 'self' || purpose === 'state') {
248
+ return 'self';
249
+ }
250
+ if (purpose === 'mint' || purpose === 'release') {
251
+ return 'in';
252
+ }
253
+ if (purpose === 'burn' || purpose === 'fee' || purpose === 'lock') {
254
+ return 'out';
255
+ }
256
+ if (purpose === 'transfer' && to?.script) {
257
+ return 'out';
258
+ }
259
+ const fromAddresses = [
260
+ ...(from?.address ? [from.address] : []),
261
+ ...(from?.addresses ?? []),
262
+ ];
263
+ const fromOwners = [
264
+ ...fromAddresses,
265
+ ...(from?.hash ? [from.hash] : []),
266
+ ...(from?.poolId ? [from.poolId] : []),
267
+ ];
268
+ const toOwner = to?.address ?? to?.hash ?? to?.poolId;
269
+ if (fromOwners.length > 0 && toOwner) {
270
+ return fromOwners.includes(toOwner) ? 'self' : 'out';
271
+ }
272
+ if (purpose === 'transfer' && toOwner) {
273
+ return 'out';
274
+ }
275
+ return 'unknown';
276
+ }
277
+ function unsupportedOutputContexts(unsupported) {
278
+ const markersByOutput = new Map();
279
+ for (const item of unsupported) {
280
+ if (item.outputIndex === undefined) {
281
+ continue;
282
+ }
283
+ const markers = markersByOutput.get(item.outputIndex) ?? new Set();
284
+ if (item.marker) {
285
+ markers.add(item.marker);
286
+ }
287
+ markersByOutput.set(item.outputIndex, markers);
288
+ }
289
+ return new Map([...markersByOutput].map(([outputIndex, markers]) => [
290
+ outputIndex,
291
+ {
292
+ protocol: 'unknown',
293
+ recognized: false,
294
+ ...(markers.size ? { markers: [...markers].sort() } : {}),
295
+ },
296
+ ]));
297
+ }
298
+ function hasAssetFlowAmount(flow) {
299
+ return flow.amount !== undefined;
300
+ }
301
+ function assetFlowAggregationKey(flow) {
302
+ const amountKind = 'satoshis' in flow.amount ? 'satoshis' : 'raw';
303
+ return JSON.stringify({
304
+ purpose: flow.purpose,
305
+ direction: flow.direction,
306
+ asset: flow.asset,
307
+ from: flow.from,
308
+ to: flow.to,
309
+ context: flow.context,
310
+ amountKind,
311
+ });
312
+ }
313
+ function canAggregateAssetFlow(flow) {
314
+ if ('satoshis' in flow.amount) {
315
+ return flow.asset.type === 'tbc';
316
+ }
317
+ return flow.asset.type === 'ft' || flow.asset.type === 'stablecoin';
318
+ }
319
+ function aggregateAssetFlowAmount(target, source) {
320
+ if ('satoshis' in target.amount && 'satoshis' in source.amount) {
321
+ target.amount = { satoshis: target.amount.satoshis + source.amount.satoshis };
322
+ return;
323
+ }
324
+ if ('raw' in target.amount && 'raw' in source.amount) {
325
+ const raw = (BigInt(target.amount.raw) + BigInt(source.amount.raw)).toString();
326
+ const decimal = target.amount.decimal ?? source.amount.decimal;
327
+ target.amount = {
328
+ raw,
329
+ display: decimal === undefined ? undefined : formatTokenRawAmount(raw, decimal),
330
+ decimal,
331
+ };
332
+ }
333
+ }
334
+ function aggregateAssetFlows(flows) {
335
+ // Aggregate TBC or FT flows with the same asset, endpoints, and purpose to avoid fragmented multi-output displays.
336
+ const aggregated = [];
337
+ const flowByKey = new Map();
338
+ for (const flow of flows) {
339
+ if (!hasAssetFlowAmount(flow) || !canAggregateAssetFlow(flow)) {
340
+ aggregated.push(flow);
341
+ continue;
342
+ }
343
+ const key = assetFlowAggregationKey(flow);
344
+ const existing = flowByKey.get(key);
345
+ if (existing) {
346
+ aggregateAssetFlowAmount(existing, flow);
347
+ continue;
348
+ }
349
+ flowByKey.set(key, flow);
350
+ aggregated.push(flow);
351
+ }
352
+ return aggregated;
353
+ }
354
+ function buildNativeAssetFlows(inputs, outputs, transactionType, ftMovements, protocolNativeOutputIndexes = new Set(), nativeFtCarrierOutputIndexes = new Set(), unsupportedContexts = new Map()) {
355
+ // Native TBC flows cover ordinary transfers and significant TBC value carried by protocol transactions.
356
+ const sourceInputs = nativeTbcSourceInputs(inputs, ftMovements);
357
+ const sourceEndpoint = inputAddressEndpoint(sourceInputs);
358
+ const sourceAddresses = inputAddresses(sourceInputs);
359
+ const firstFtReceiver = ftMovements[0]?.receiver;
360
+ const nativeOutputs = outputs.filter((output) => isNativeTbcFlowOutput(output, protocolNativeOutputIndexes, nativeFtCarrierOutputIndexes));
361
+ if (transactionType === 'TBC coinbase') {
362
+ return nativeOutputs.map((output) => {
363
+ const purpose = 'mint';
364
+ const to = nativeOutputEndpoint(output);
365
+ return {
366
+ purpose,
367
+ direction: directionForFlow(purpose, undefined, to),
368
+ asset: NATIVE_TBC_ASSET,
369
+ amount: { satoshis: output.satoshis },
370
+ ...(to ? { to } : {}),
371
+ };
372
+ });
373
+ }
374
+ const allOutputsReturnToInputs = Boolean(sourceAddresses.size) &&
375
+ nativeOutputs.length > 0 &&
376
+ nativeOutputs.every((output) => Boolean(isAddressLikeNativeOutput(output) && output.address && sourceAddresses.has(output.address)));
377
+ return nativeOutputs
378
+ .map((output) => {
379
+ const context = unsupportedContexts.get(output.index);
380
+ const isChange = Boolean(output.address && sourceAddresses.has(output.address));
381
+ const isSelfAnnotatedOutput = Boolean(isChange && output.script.asm.includes('OP_RETURN'));
382
+ let purpose = context ? 'transfer' : 'unknown';
383
+ if (!context && sourceAddresses.size) {
384
+ if (isSelfAnnotatedOutput || allOutputsReturnToInputs) {
385
+ purpose = 'self';
386
+ }
387
+ else if (allOutputsReturnToInputs ||
388
+ isChange ||
389
+ (ftMovements.length > 0 &&
390
+ transactionType !== 'TBC transfer' &&
391
+ firstFtReceiver !== undefined &&
392
+ Boolean(output.address) &&
393
+ output.address !== firstFtReceiver)) {
394
+ purpose = 'self';
395
+ }
396
+ else {
397
+ purpose = 'transfer';
398
+ }
399
+ }
400
+ const from = endpoint(purpose === 'transfer' ? sourceEndpoint : undefined);
401
+ const to = nativeOutputEndpoint(output);
402
+ return {
403
+ purpose,
404
+ direction: context ? 'out' : directionForFlow(purpose, from, to),
405
+ asset: NATIVE_TBC_ASSET,
406
+ amount: { satoshis: output.satoshis },
407
+ ...(from ? { from } : {}),
408
+ ...(to ? { to } : {}),
409
+ ...(context ? { context } : {}),
410
+ };
411
+ });
412
+ }
413
+ export function isNativeTbcFlowOutput(output, protocolNativeOutputIndexes, nativeProtocolCarrierOutputIndexes = new Set()) {
414
+ // Protocol state outputs are not native flows by default, but high-value carriers remain explicit TBC flows.
415
+ if (output.satoshis <= 0) {
416
+ return false;
417
+ }
418
+ if (nativeProtocolCarrierOutputIndexes.has(output.index)) {
419
+ return true;
420
+ }
421
+ if (protocolNativeOutputIndexes.has(output.index)) {
422
+ return false;
423
+ }
424
+ const isPlainP2pkh = output.scriptKind === 'p2pkh' && !output.script.asm.includes('OP_RETURN');
425
+ if (!isPlainP2pkh && output.satoshis < MIN_NON_STANDARD_TBC_FLOW_SATOSHIS) {
426
+ return false;
427
+ }
428
+ if (output.scriptKind === 'p2pkh') {
429
+ return true;
430
+ }
431
+ if (output.scriptKind === 'multisig') {
432
+ return true;
433
+ }
434
+ if (output.scriptKind === 'op_return') {
435
+ return false;
436
+ }
437
+ return true;
438
+ }
439
+ function isAddressLikeNativeOutput(output) {
440
+ return output.scriptKind === 'p2pkh' || output.scriptKind === 'multisig';
441
+ }
442
+ function buildFtAssetFlows(inputs, outputs, transactionType, movements) {
443
+ // Build FT asset flows from verified movements, never directly from FTape markers.
444
+ const sourceAddress = firstInputAddress(inputs);
445
+ const sourceOwnerEndpoint = ftInputOwnerEndpoint(inputs, outputs);
446
+ return movements.map((movement, index) => {
447
+ const to = endpoint({
448
+ address: movement.receiver,
449
+ hash: movement.receiverHash,
450
+ outputIndex: movement.outputIndex,
451
+ tapeOutputIndex: movement.tapeOutputIndex,
452
+ });
453
+ const isMint = index === 0 && transactionType.endsWith('mint');
454
+ const candidateFrom = endpoint(index === 0 ? (sourceOwnerEndpoint ?? { address: sourceAddress }) : undefined);
455
+ const purpose = index > 0
456
+ ? 'self'
457
+ : isMint
458
+ ? 'mint'
459
+ : transactionType.endsWith('merge') && directionForFlow('transfer', candidateFrom, to) === 'self'
460
+ ? 'self'
461
+ : 'transfer';
462
+ const from = isMint ? undefined : candidateFrom;
463
+ return {
464
+ purpose,
465
+ direction: directionForFlow(purpose, from, to),
466
+ asset: tokenTransactionAsset(movement),
467
+ amount: tokenTransactionAmount(movement),
468
+ ...(from ? { from } : {}),
469
+ ...(to ? { to } : {}),
470
+ };
471
+ });
472
+ }
473
+ function buildSemanticAssetFlows(semantic) {
474
+ // Derive asset flows for complex protocols such as OrderBook from semantic effects to avoid duplicate inference.
475
+ const orderBookContext = orderBookSemanticContext(semantic);
476
+ const publicEffects = orderBookContext
477
+ ? semantic.effects.filter((effect) => effect.kind !== 'state')
478
+ : semantic.effects;
479
+ return publicEffects.map((effect) => {
480
+ const purpose = semanticEffectPurpose(effect, Boolean(orderBookContext));
481
+ const context = purpose === 'lock' ? orderBookContext : undefined;
482
+ const from = endpoint({ address: effect.from?.address, inputIndex: effect.inputIndex });
483
+ const to = endpoint({
484
+ address: effect.to?.address,
485
+ outputIndex: effect.outputIndex,
486
+ tapeOutputIndex: effect.tapeOutputIndex,
487
+ });
488
+ const asset = semanticAssetToTransactionAsset(effect.asset);
489
+ if (asset.type === 'nftCollection') {
490
+ return {
491
+ purpose: 'mint',
492
+ direction: 'in',
493
+ asset,
494
+ ...(to ? { to } : {}),
495
+ ...(context ? { context } : {}),
496
+ };
497
+ }
498
+ if (asset.type === 'nft') {
499
+ return {
500
+ purpose,
501
+ direction: directionForFlow(purpose, from, to),
502
+ asset,
503
+ ...(from ? { from } : {}),
504
+ ...(to ? { to } : {}),
505
+ ...(context ? { context } : {}),
506
+ };
507
+ }
508
+ return {
509
+ purpose,
510
+ direction: directionForFlow(purpose, from, to),
511
+ asset,
512
+ amount: semanticAmountToTransactionAmount(effect.amount),
513
+ ...(from ? { from } : {}),
514
+ ...(to ? { to } : {}),
515
+ ...(context ? { context } : {}),
516
+ };
517
+ });
518
+ }
519
+ function poolNftTokenAsset(movement) {
520
+ return {
521
+ type: 'ft',
522
+ symbol: movement.ftSymbol ?? 'FT',
523
+ name: movement.ftName,
524
+ contractId: movement.ftTokenId ?? movement.ftContractTxid,
525
+ decimal: movement.ftDecimal,
526
+ };
527
+ }
528
+ function poolNftLpAsset(movement) {
529
+ const asset = {
530
+ type: 'ft',
531
+ symbol: movement.ftSymbol ? `${movement.ftSymbol}-LP` : 'LP',
532
+ name: movement.ftName ? `${movement.ftName} LP` : undefined,
533
+ decimal: movement.ftDecimal,
534
+ };
535
+ if (!movement.poolId) {
536
+ return asset;
537
+ }
538
+ return {
539
+ ...asset,
540
+ subtype: 'poolLp',
541
+ poolId: movement.poolId,
542
+ };
543
+ }
544
+ function poolNftCreateAsset(movement) {
545
+ if (movement.action !== 'create' || !movement.contractId) {
546
+ return undefined;
547
+ }
548
+ return {
549
+ type: 'nft',
550
+ subtype: 'poolNft',
551
+ contractId: movement.contractId,
552
+ poolNft: {
553
+ version: movement.version ?? 1,
554
+ ...(movement.serviceFeeRate !== undefined ? { serviceFeeRate: movement.serviceFeeRate } : {}),
555
+ ...(movement.lpPlan !== undefined ? { lpPlan: movement.lpPlan } : {}),
556
+ ...(movement.withLock !== undefined ? { withLock: movement.withLock } : {}),
557
+ ...(movement.withLockTime !== undefined ? { withLockTime: movement.withLockTime } : {}),
558
+ },
559
+ };
560
+ }
561
+ function poolNftRawAmount(raw, decimal) {
562
+ return {
563
+ raw,
564
+ display: decimal === undefined ? undefined : formatTokenRawAmount(raw, decimal),
565
+ decimal,
566
+ };
567
+ }
568
+ function poolNftPoolEndpoint(movement) {
569
+ const pool = endpoint({ poolId: movement.poolId });
570
+ if (pool) {
571
+ return pool;
572
+ }
573
+ return endpoint({
574
+ address: movement.poolStateAddress,
575
+ hash: movement.poolStateAddress ? undefined : movement.poolStateHash,
576
+ outputIndex: movement.poolStateOutputIndex ?? movement.poolOutputIndex,
577
+ tapeOutputIndex: movement.tapeOutputIndex,
578
+ });
579
+ }
580
+ function poolNftReceiverEndpoint(movement) {
581
+ return endpoint({
582
+ address: movement.receiver,
583
+ hash: movement.receiver ? undefined : movement.receiverHash,
584
+ });
585
+ }
586
+ function poolNftSenderEndpoint(movement) {
587
+ if (movement.action === 'mergeFtInPool') {
588
+ return poolNftPoolEndpoint(movement);
589
+ }
590
+ return endpoint({
591
+ address: movement.sender,
592
+ hash: movement.senderHash,
593
+ });
594
+ }
595
+ function poolNftFeeEndpoint(feeOutput) {
596
+ return endpoint({
597
+ address: feeOutput.address,
598
+ outputIndex: feeOutput.outputIndex,
599
+ });
600
+ }
601
+ function buildPoolNftAssetFlows(movement) {
602
+ const flows = [];
603
+ const pool = poolNftPoolEndpoint(movement);
604
+ const receiver = poolNftReceiverEndpoint(movement);
605
+ const sender = poolNftSenderEndpoint(movement);
606
+ const userIn = sender ?? receiver;
607
+ const userOut = receiver ?? sender;
608
+ const tokenAsset = poolNftTokenAsset(movement);
609
+ const lpAsset = poolNftLpAsset(movement);
610
+ const createAsset = poolNftCreateAsset(movement);
611
+ if (createAsset) {
612
+ const to = endpoint({
613
+ txid: movement.contractId,
614
+ vout: movement.poolStateOutputIndex ?? movement.poolOutputIndex ?? 0,
615
+ });
616
+ flows.push({
617
+ purpose: 'mint',
618
+ direction: 'in',
619
+ asset: createAsset,
620
+ ...(to ? { to } : {}),
621
+ });
622
+ }
623
+ if (movement.tbcInSatoshis !== undefined && movement.tbcInSatoshis > 0) {
624
+ const purpose = 'transfer';
625
+ flows.push({
626
+ purpose,
627
+ direction: directionForFlow(purpose, userIn, pool),
628
+ asset: NATIVE_TBC_ASSET,
629
+ amount: { satoshis: movement.tbcInSatoshis },
630
+ ...(userIn ? { from: userIn } : {}),
631
+ ...(pool ? { to: pool } : {}),
632
+ });
633
+ }
634
+ if (movement.tbcOutSatoshis !== undefined && movement.tbcOutSatoshis > 0) {
635
+ const purpose = 'transfer';
636
+ flows.push({
637
+ purpose,
638
+ direction: directionForFlow(purpose, pool, receiver),
639
+ asset: NATIVE_TBC_ASSET,
640
+ amount: { satoshis: movement.tbcOutSatoshis },
641
+ ...(pool ? { from: pool } : {}),
642
+ ...(userOut ? { to: userOut } : {}),
643
+ });
644
+ }
645
+ if (movement.ftInRawAmount) {
646
+ const purpose = movement.action === 'mergeFtInPool' &&
647
+ directionForFlow('transfer', userIn, pool) === 'self'
648
+ ? 'self'
649
+ : 'transfer';
650
+ flows.push({
651
+ purpose,
652
+ direction: directionForFlow(purpose, userIn, pool),
653
+ asset: tokenAsset,
654
+ amount: poolNftRawAmount(movement.ftInRawAmount, movement.ftDecimal),
655
+ ...(userIn ? { from: userIn } : {}),
656
+ ...(pool ? { to: pool } : {}),
657
+ });
658
+ }
659
+ if (movement.ftOutRawAmount) {
660
+ const purpose = 'transfer';
661
+ flows.push({
662
+ purpose,
663
+ direction: directionForFlow(purpose, pool, receiver),
664
+ asset: tokenAsset,
665
+ amount: poolNftRawAmount(movement.ftOutRawAmount, movement.ftDecimal),
666
+ ...(pool ? { from: pool } : {}),
667
+ ...(userOut ? { to: userOut } : {}),
668
+ });
669
+ }
670
+ if (movement.ftLpInRawAmount) {
671
+ const purpose = 'burn';
672
+ flows.push({
673
+ purpose,
674
+ direction: directionForFlow(purpose, userIn, undefined),
675
+ asset: lpAsset,
676
+ amount: poolNftRawAmount(movement.ftLpInRawAmount, movement.ftDecimal),
677
+ ...(userIn ? { from: userIn } : {}),
678
+ });
679
+ }
680
+ if (movement.ftLpOutRawAmount) {
681
+ const purpose = 'mint';
682
+ flows.push({
683
+ purpose,
684
+ direction: directionForFlow(purpose, undefined, userOut),
685
+ asset: lpAsset,
686
+ amount: poolNftRawAmount(movement.ftLpOutRawAmount, movement.ftDecimal),
687
+ ...(userOut ? { to: userOut } : {}),
688
+ });
689
+ }
690
+ for (const feeOutput of movement.feeOutputs ?? []) {
691
+ const purpose = 'transfer';
692
+ const to = poolNftFeeEndpoint(feeOutput);
693
+ flows.push({
694
+ purpose,
695
+ direction: directionForFlow(purpose, userIn, to),
696
+ asset: NATIVE_TBC_ASSET,
697
+ amount: { satoshis: feeOutput.satoshis },
698
+ ...(userIn ? { from: userIn } : {}),
699
+ ...(to ? { to } : {}),
700
+ });
701
+ }
702
+ return flows;
703
+ }
704
+ function buildProtocolAssetFlows(nftMovements, piggyBankMovements, poolNftMovements) {
705
+ // Add protocol-specific non-FT flows here; native TBC carriers are handled separately below.
706
+ const piggyBankFlows = piggyBankMovements.map((movement) => {
707
+ const purpose = movement.action === 'unfreeze' ? 'release' : 'lock';
708
+ const from = endpoint(movement.action === 'unfreeze'
709
+ ? {
710
+ address: movement.address,
711
+ inputIndex: movement.inputIndex,
712
+ }
713
+ : undefined);
714
+ const to = endpoint({
715
+ address: movement.receiver ?? movement.address,
716
+ outputIndex: movement.action === 'unfreeze' ? movement.releaseOutputIndex : movement.outputIndex,
717
+ });
718
+ return {
719
+ purpose,
720
+ direction: directionForFlow(purpose, from, to),
721
+ asset: NATIVE_TBC_ASSET,
722
+ amount: { satoshis: movement.releasedSatoshis ?? movement.satoshis },
723
+ context: {
724
+ protocol: 'piggyBank',
725
+ recognized: true,
726
+ action: movement.action,
727
+ },
728
+ ...(from ? { from } : {}),
729
+ ...(to ? { to } : {}),
730
+ };
731
+ });
732
+ const nftFlows = nftMovements.map((movement) => {
733
+ if (movement.action === 'mintCollection') {
734
+ const to = endpoint({ address: movement.mintSlotAddress });
735
+ return {
736
+ purpose: 'mint',
737
+ direction: 'in',
738
+ asset: {
739
+ type: 'nftCollection',
740
+ name: movement.collectionName,
741
+ collectionId: movement.collectionId,
742
+ contractId: movement.contractId,
743
+ },
744
+ ...(to ? { to } : {}),
745
+ };
746
+ }
747
+ const purpose = movement.action === 'burn'
748
+ ? 'burn'
749
+ : movement.action === 'transfer'
750
+ ? 'transfer'
751
+ : 'mint';
752
+ const from = endpoint({
753
+ address: movement.sender,
754
+ });
755
+ const to = movement.action === 'burn'
756
+ ? undefined
757
+ : endpoint({
758
+ address: movement.receiver,
759
+ outputIndex: movement.outputIndex,
760
+ tapeOutputIndex: movement.tapeOutputIndex,
761
+ });
762
+ const asset = {
763
+ type: 'nft',
764
+ symbol: movement.symbol,
765
+ name: movement.nftName,
766
+ contractId: movement.contractId,
767
+ ...(movement.collectionId ? { collectionId: movement.collectionId } : {}),
768
+ };
769
+ return {
770
+ purpose,
771
+ direction: directionForFlow(purpose, from, to),
772
+ asset,
773
+ ...(from ? { from } : {}),
774
+ ...(to ? { to } : {}),
775
+ };
776
+ });
777
+ const poolFlows = poolNftMovements.flatMap(buildPoolNftAssetFlows);
778
+ return [...piggyBankFlows, ...nftFlows, ...poolFlows];
779
+ }
780
+ function poolNftExplainedFtOutputIndexes(poolNftMovements) {
781
+ return new Set(poolNftMovements.flatMap((movement) => [
782
+ movement.ftInOutputIndex,
783
+ movement.ftOutOutputIndex,
784
+ movement.ftLpInOutputIndex,
785
+ movement.ftLpOutOutputIndex,
786
+ ]).filter((index) => index !== undefined));
787
+ }
788
+ function isPoolNftStateFtMovement(ftMovement, poolNftMovements) {
789
+ return poolNftMovements.some((poolMovement) => {
790
+ const locksToPoolState = (ftMovement.receiverHash !== undefined && ftMovement.receiverHash === poolMovement.poolStateHash) ||
791
+ (ftMovement.receiver !== undefined && ftMovement.receiver === poolMovement.poolStateAddress);
792
+ if (!locksToPoolState) {
793
+ return false;
794
+ }
795
+ const ftContractTxid = poolMovement.ftTokenId ?? poolMovement.ftContractTxid;
796
+ return !ftContractTxid || !ftMovement.contractId || ftMovement.contractId === ftContractTxid;
797
+ });
798
+ }
799
+ function isPoolNftLpCodeOutput(ftMovement, outputs) {
800
+ const output = outputs.find((candidate) => candidate.index === ftMovement.outputIndex);
801
+ return Boolean(output?.script.hex?.toLowerCase().endsWith(FTLP_CODE_MARKER_HEX));
802
+ }
803
+ function isPoolNftLpChangeMovement(ftMovement, outputs, poolNftMovements) {
804
+ if (!isPoolNftLpCodeOutput(ftMovement, outputs)) {
805
+ return false;
806
+ }
807
+ return poolNftMovements.some((poolMovement) => poolMovement.action === 'consumeLiquidity' &&
808
+ ftMovement.outputIndex !== poolMovement.ftOutOutputIndex &&
809
+ ftMovement.outputIndex !== poolMovement.ftLpInOutputIndex);
810
+ }
811
+ function poolNftStateCarrierOutputIndexes(outputs) {
812
+ // A decoded PoolNFT tape describes the adjacent contract output even when the
813
+ // action matcher cannot safely classify the transaction. Keep that state UTXO
814
+ // out of generic native flows rather than exposing its locking script as a payee.
815
+ const outputByIndex = new Map(outputs.map((output) => [output.index, output]));
816
+ return outputs.flatMap((tapeOutput) => {
817
+ if (!tapeOutput.script.data?.poolNft) {
818
+ return [];
819
+ }
820
+ const stateCarrier = outputByIndex.get(tapeOutput.index - 1);
821
+ return stateCarrier?.scriptKind === 'contract' ? [stateCarrier.index] : [];
822
+ });
823
+ }
824
+ export function buildAssetFlows(transactionType, inputs, outputs, ftMovements, nftMovements, piggyBankMovements, poolNftMovements, orderBookOrders, semantic, unsupported = []) {
825
+ // Asset flows are the core public display layer for observable protocol assets and native TBC movements.
826
+ // Contract state remains available from decoded chain inputs and outputs and internal semantic evidence.
827
+ if (orderBookOrders.length) {
828
+ return aggregateAssetFlows(buildSemanticAssetFlows(semantic));
829
+ }
830
+ const protocolNativeOutputIndexes = new Set([
831
+ ...nftMovements.flatMap((movement) => [movement.outputIndex, movement.holdOutputIndex]),
832
+ ...piggyBankMovements.flatMap((movement) => [movement.outputIndex, movement.releaseOutputIndex]),
833
+ ...poolNftMovements.map((movement) => movement.poolOutputIndex),
834
+ ...poolNftMovements.map((movement) => movement.tbcOutOutputIndex),
835
+ ...poolNftMovements.flatMap((movement) => (movement.feeOutputs ?? []).map((feeOutput) => feeOutput.outputIndex)),
836
+ ...poolNftStateCarrierOutputIndexes(outputs),
837
+ ].filter((index) => index !== undefined));
838
+ const explainedFtOutputIndexes = poolNftExplainedFtOutputIndexes(poolNftMovements);
839
+ const ftMovementsForAssetFlows = ftMovements.filter((movement) => !explainedFtOutputIndexes.has(movement.outputIndex) &&
840
+ !isPoolNftStateFtMovement(movement, poolNftMovements) &&
841
+ !isPoolNftLpChangeMovement(movement, outputs, poolNftMovements));
842
+ const nativeProtocolCarrierOutputIndexes = new Set([
843
+ ...ftMovements.flatMap((movement) => [
844
+ (outputs[movement.outputIndex]?.satoshis ?? 0) >= MIN_NON_STANDARD_TBC_FLOW_SATOSHIS
845
+ ? movement.outputIndex
846
+ : undefined,
847
+ (outputs[movement.tapeOutputIndex]?.satoshis ?? 0) >= MIN_NON_STANDARD_TBC_FLOW_SATOSHIS
848
+ ? movement.tapeOutputIndex
849
+ : undefined,
850
+ ]),
851
+ ...nftMovements.flatMap((movement) => [
852
+ movement.outputIndex !== undefined && (outputs[movement.outputIndex]?.satoshis ?? 0) >= MIN_NON_STANDARD_TBC_FLOW_SATOSHIS
853
+ ? movement.outputIndex
854
+ : undefined,
855
+ movement.holdOutputIndex !== undefined && (outputs[movement.holdOutputIndex]?.satoshis ?? 0) >= MIN_NON_STANDARD_TBC_FLOW_SATOSHIS
856
+ ? movement.holdOutputIndex
857
+ : undefined,
858
+ movement.tapeOutputIndex !== undefined && (outputs[movement.tapeOutputIndex]?.satoshis ?? 0) >= MIN_NON_STANDARD_TBC_FLOW_SATOSHIS
859
+ ? movement.tapeOutputIndex
860
+ : undefined,
861
+ ]),
862
+ ].filter((index) => index !== undefined));
863
+ return aggregateAssetFlows([
864
+ ...buildFtAssetFlows(inputs, outputs, transactionType, ftMovementsForAssetFlows),
865
+ ...buildProtocolAssetFlows(nftMovements, piggyBankMovements, poolNftMovements),
866
+ ...buildNativeAssetFlows(inputs, outputs, transactionType, ftMovements, protocolNativeOutputIndexes, nativeProtocolCarrierOutputIndexes, unsupportedOutputContexts(unsupported)),
867
+ ]);
868
+ }
869
+ //# sourceMappingURL=index.js.map