• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hirosystems / stacks-blockchain-api / 4940012903

pending completion
4940012903

push

github

GitHub
Merge pull request #1655 from hirosystems/develop

2085 of 3228 branches covered (64.59%)

133 of 234 new or added lines in 42 files covered. (56.84%)

4 existing lines in 1 file now uncovered.

7335 of 9606 relevant lines covered (76.36%)

1765.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.11
/src/event-stream/reader.ts
1
import {
39✔
2
  BurnchainOp,
3
  CoreNodeBlockMessage,
4
  CoreNodeEvent,
5
  CoreNodeEventType,
6
  CoreNodeMicroblockTxMessage,
7
  CoreNodeParsedTxMessage,
8
  CoreNodeTxMessage,
9
  FtMintEvent,
10
  isTxWithMicroblockInfo,
11
  NftMintEvent,
12
  SmartContractEvent,
13
  StxLockEvent,
14
  StxMintEvent,
15
  StxTransferEvent,
16
} from './core-node-message';
17
import {
39✔
18
  decodeClarityValue,
19
  decodeTransaction,
20
  decodeStacksAddress,
21
  ClarityTypeID,
22
  ClarityValuePrincipalStandard,
23
  ClarityValueResponse,
24
  ClarityValueTuple,
25
  ClarityValueUInt,
26
  AnchorModeID,
27
  DecodedTxResult,
28
  PostConditionModeID,
29
  PrincipalTypeID,
30
  TxPayloadTypeID,
31
  PostConditionAuthFlag,
32
  TxPublicKeyEncoding,
33
  TxSpendingConditionSingleSigHashMode,
34
  decodeClarityValueList,
35
  ClarityValueBuffer,
36
} from 'stacks-encoding-native-js';
37
import {
38
  DbMicroblockPartial,
39
  DbPox2DelegateStxEvent,
40
  DbPox2StackStxEvent,
41
} from '../datastore/common';
42
import { NotImplementedError } from '../errors';
39✔
43
import {
39✔
44
  getEnumDescription,
45
  I32_MAX,
46
  bufferToHexPrefixString,
47
  hexToBuffer,
48
  SubnetContractIdentifer,
49
} from '../helpers';
50
import {
39✔
51
  TransactionVersion,
52
  ChainID,
53
  uintCV,
54
  tupleCV,
55
  bufferCV,
56
  serializeCV,
57
  noneCV,
58
  someCV,
59
  OptionalCV,
60
  TupleCV,
61
  BufferCV,
62
  SomeCV,
63
  NoneCV,
64
  UIntCV,
65
  stringAsciiCV,
66
  hexToCV,
67
} from '@stacks/transactions';
68
import { poxAddressToTuple } from '@stacks/stacking';
39✔
69
import { c32ToB58 } from 'c32check';
39✔
70
import { decodePox2PrintEvent } from './pox2-event-parsing';
39✔
71
import { Pox2ContractIdentifer, Pox2EventName } from '../pox-helpers';
72
import { principalCV } from '@stacks/transactions/dist/clarity/types/principalCV';
39✔
73
import { logger } from '../logger';
39✔
74

75
export function getTxSenderAddress(tx: DecodedTxResult): string {
39✔
76
  const txSender = tx.auth.origin_condition.signer.address;
1,266✔
77
  return txSender;
1,266✔
78
}
79

80
export function getTxSponsorAddress(tx: DecodedTxResult): string | undefined {
39✔
81
  let sponsorAddress: string | undefined = undefined;
1,133✔
82
  if (tx.auth.type_id === PostConditionAuthFlag.Sponsored) {
1,133✔
83
    sponsorAddress = tx.auth.sponsor_condition.signer.address;
1✔
84
  }
85
  return sponsorAddress;
1,133✔
86
}
87

88
function createSubnetTransactionFromL1RegisterAsset(
89
  chainId: ChainID,
90
  burnchainOp: BurnchainOp,
91
  subnetEvent: SmartContractEvent,
92
  txId: string
93
): DecodedTxResult {
94
  if (
×
95
    burnchainOp.register_asset.asset_type !== 'ft' &&
×
96
    burnchainOp.register_asset.asset_type !== 'nft'
97
  ) {
98
    throw new Error(
×
99
      `Unexpected L1 register asset type: ${JSON.stringify(burnchainOp.register_asset)}`
100
    );
101
  }
102

103
  const [contractAddress, contractName] = subnetEvent.contract_event.contract_identifier
×
104
    .split('::')[0]
105
    .split('.');
106
  const decContractAddress = decodeStacksAddress(contractAddress);
×
107

108
  const decodedLogEvent = decodeClarityValue<
×
109
    ClarityValueTuple<{
110
      'burnchain-txid': ClarityValueBuffer;
111
    }>
112
  >(subnetEvent.contract_event.raw_value);
113

114
  // (define-public (register-asset-contract
115
  //   (asset-type (string-ascii 3))
116
  //   (l1-contract principal)
117
  //   (l2-contract principal)
118
  //   (burnchain-txid (buff 32))
119
  const fnName = 'register-asset-contract';
×
120
  const legacyClarityVals = [
×
121
    stringAsciiCV(burnchainOp.register_asset.asset_type),
122
    principalCV(burnchainOp.register_asset.l1_contract_id),
123
    principalCV(burnchainOp.register_asset.l2_contract_id),
124
    bufferCV(hexToBuffer(decodedLogEvent.data['burnchain-txid'].buffer)),
125
  ];
126
  const fnLenBuffer = Buffer.alloc(4);
×
127
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
×
128
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
×
129
  const rawFnArgs = bufferToHexPrefixString(
×
130
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
131
  );
132
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
×
133

134
  const tx: DecodedTxResult = {
×
135
    tx_id: txId,
136
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
137
    chain_id: chainId,
138
    auth: {
139
      type_id: PostConditionAuthFlag.Standard,
140
      origin_condition: {
141
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
142
        signer: {
143
          address_version: decContractAddress[0],
144
          address_hash_bytes: decContractAddress[1],
145
          address: contractAddress,
146
        },
147
        nonce: '0',
148
        tx_fee: '0',
149
        key_encoding: TxPublicKeyEncoding.Compressed,
150
        signature: '0x',
151
      },
152
    },
153
    anchor_mode: AnchorModeID.Any,
154
    post_condition_mode: PostConditionModeID.Allow,
155
    post_conditions: [],
156
    post_conditions_buffer: '0x0100000000',
157
    payload: {
158
      type_id: TxPayloadTypeID.ContractCall,
159
      address_version: decContractAddress[0],
160
      address_hash_bytes: decContractAddress[1],
161
      address: contractAddress,
162
      contract_name: contractName,
163
      function_name: fnName,
164
      function_args: clarityFnArgs,
165
      function_args_buffer: rawFnArgs,
166
    },
167
  };
168
  return tx;
×
169
}
170

171
function createSubnetTransactionFromL1NftDeposit(
172
  chainId: ChainID,
173
  event: NftMintEvent,
174
  txId: string
175
): DecodedTxResult {
176
  const decRecipientAddress = decodeStacksAddress(event.nft_mint_event.recipient);
×
177
  const [contractAddress, contractName] = event.nft_mint_event.asset_identifier
×
178
    .split('::')[0]
179
    .split('.');
180
  const decContractAddress = decodeStacksAddress(contractAddress);
×
181
  const legacyClarityVals = [
×
182
    hexToCV(event.nft_mint_event.raw_value),
183
    principalCV(event.nft_mint_event.recipient),
184
  ];
185
  const fnLenBuffer = Buffer.alloc(4);
×
186
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
×
187
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
×
188
  const rawFnArgs = bufferToHexPrefixString(
×
189
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
190
  );
191
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
×
192

193
  const tx: DecodedTxResult = {
×
194
    tx_id: txId,
195
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
196
    chain_id: chainId,
197
    auth: {
198
      type_id: PostConditionAuthFlag.Standard,
199
      origin_condition: {
200
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
201
        signer: {
202
          address_version: decRecipientAddress[0],
203
          address_hash_bytes: decRecipientAddress[1],
204
          address: event.nft_mint_event.recipient,
205
        },
206
        nonce: '0',
207
        tx_fee: '0',
208
        key_encoding: TxPublicKeyEncoding.Compressed,
209
        signature: '0x',
210
      },
211
    },
212
    anchor_mode: AnchorModeID.Any,
213
    post_condition_mode: PostConditionModeID.Allow,
214
    post_conditions: [],
215
    post_conditions_buffer: '0x0100000000',
216
    payload: {
217
      type_id: TxPayloadTypeID.ContractCall,
218
      address_version: decContractAddress[0],
219
      address_hash_bytes: decContractAddress[1],
220
      address: contractAddress,
221
      contract_name: contractName,
222
      function_name: 'deposit-from-burnchain',
223
      function_args: clarityFnArgs,
224
      function_args_buffer: rawFnArgs,
225
    },
226
  };
227
  return tx;
×
228
}
229

230
function createSubnetTransactionFromL1FtDeposit(
231
  chainId: ChainID,
232
  event: FtMintEvent,
233
  txId: string
234
): DecodedTxResult {
235
  const decRecipientAddress = decodeStacksAddress(event.ft_mint_event.recipient);
×
236
  const [contractAddress, contractName] = event.ft_mint_event.asset_identifier
×
237
    .split('::')[0]
238
    .split('.');
239
  const decContractAddress = decodeStacksAddress(contractAddress);
×
240
  const legacyClarityVals = [
×
241
    uintCV(event.ft_mint_event.amount),
242
    principalCV(event.ft_mint_event.recipient),
243
  ];
244
  const fnLenBuffer = Buffer.alloc(4);
×
245
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
×
246
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
×
247
  const rawFnArgs = bufferToHexPrefixString(
×
248
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
249
  );
250
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
×
251

252
  const tx: DecodedTxResult = {
×
253
    tx_id: txId,
254
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
255
    chain_id: chainId,
256
    auth: {
257
      type_id: PostConditionAuthFlag.Standard,
258
      origin_condition: {
259
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
260
        signer: {
261
          address_version: decRecipientAddress[0],
262
          address_hash_bytes: decRecipientAddress[1],
263
          address: event.ft_mint_event.recipient,
264
        },
265
        nonce: '0',
266
        tx_fee: '0',
267
        key_encoding: TxPublicKeyEncoding.Compressed,
268
        signature: '0x',
269
      },
270
    },
271
    anchor_mode: AnchorModeID.Any,
272
    post_condition_mode: PostConditionModeID.Allow,
273
    post_conditions: [],
274
    post_conditions_buffer: '0x0100000000',
275
    payload: {
276
      type_id: TxPayloadTypeID.ContractCall,
277
      address_version: decContractAddress[0],
278
      address_hash_bytes: decContractAddress[1],
279
      address: contractAddress,
280
      contract_name: contractName,
281
      function_name: 'deposit-from-burnchain',
282
      function_args: clarityFnArgs,
283
      function_args_buffer: rawFnArgs,
284
    },
285
  };
286
  return tx;
×
287
}
288

289
function createSubnetTransactionFromL1StxDeposit(
290
  chainId: ChainID,
291
  event: StxMintEvent,
292
  txId: string
293
): DecodedTxResult {
294
  const recipientAddress = decodeStacksAddress(event.stx_mint_event.recipient);
×
295
  const bootAddressString =
296
    chainId === ChainID.Mainnet ? 'SP000000000000000000002Q6VF78' : 'ST000000000000000000002AMW42H';
×
297
  const bootAddress = decodeStacksAddress(bootAddressString);
×
298

299
  const tx: DecodedTxResult = {
×
300
    tx_id: txId,
301
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
302
    chain_id: chainId,
303
    auth: {
304
      type_id: PostConditionAuthFlag.Standard,
305
      origin_condition: {
306
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
307
        signer: {
308
          address_version: bootAddress[0],
309
          address_hash_bytes: bootAddress[1],
310
          address: bootAddressString,
311
        },
312
        nonce: '0',
313
        tx_fee: '0',
314
        key_encoding: TxPublicKeyEncoding.Compressed,
315
        signature: '0x',
316
      },
317
    },
318
    anchor_mode: AnchorModeID.Any,
319
    post_condition_mode: PostConditionModeID.Allow,
320
    post_conditions: [],
321
    post_conditions_buffer: '0x0100000000',
322
    payload: {
323
      type_id: TxPayloadTypeID.TokenTransfer,
324
      recipient: {
325
        type_id: PrincipalTypeID.Standard,
326
        address_version: recipientAddress[0],
327
        address_hash_bytes: recipientAddress[1],
328
        address: event.stx_mint_event.recipient,
329
      },
330
      amount: BigInt(event.stx_mint_event.amount).toString(),
331
      memo_hex: '0x',
332
    },
333
  };
334
  return tx;
×
335
}
336

337
function createTransactionFromCoreBtcStxLockEvent(
338
  chainId: ChainID,
339
  event: StxLockEvent,
340
  burnBlockHeight: number,
341
  txResult: string,
342
  txId: string,
343
  stxStacksPox2Event: DbPox2StackStxEvent | undefined
344
): DecodedTxResult {
345
  const resultCv = decodeClarityValue<
2✔
346
    ClarityValueResponse<
347
      ClarityValueTuple<{
348
        'lock-amount': ClarityValueUInt;
349
        'unlock-burn-height': ClarityValueUInt;
350
        stacker: ClarityValuePrincipalStandard;
351
      }>
352
    >
353
  >(txResult);
354
  if (resultCv.type_id !== ClarityTypeID.ResponseOk) {
2!
355
    throw new Error(`Unexpected tx result Clarity type ID: ${resultCv.type_id}`);
×
356
  }
357
  const resultTuple = resultCv.value;
2✔
358
  const lockAmount = resultTuple.data['lock-amount'];
2✔
359
  const stacker = resultTuple.data['stacker'];
2✔
360
  const unlockBurnHeight = Number(resultTuple.data['unlock-burn-height'].value);
2✔
361

362
  // Number of cycles: floor((unlock-burn-height - burn-height) / reward-cycle-length)
363
  const rewardCycleLength = chainId === ChainID.Mainnet ? 2100 : 50;
2!
364
  const lockPeriod = Math.floor((unlockBurnHeight - burnBlockHeight) / rewardCycleLength);
2✔
365
  const senderAddress = decodeStacksAddress(event.stx_lock_event.locked_address);
2✔
366
  const poxAddressString =
367
    chainId === ChainID.Mainnet ? 'SP000000000000000000002Q6VF78' : 'ST000000000000000000002AMW42H';
2!
368
  const poxAddress = decodeStacksAddress(poxAddressString);
2✔
369

370
  const contractName = event.stx_lock_event.contract_identifier?.split('.')?.[1] ?? 'pox';
2✔
371

372
  // If a pox-2 event is available then use its pox_addr, otherwise fallback to the stacker address
373
  const poxAddrArg = stxStacksPox2Event?.pox_addr
2!
374
    ? poxAddressToTuple(stxStacksPox2Event.pox_addr)
375
    : poxAddressToTuple(c32ToB58(stacker.address));
376

377
  const legacyClarityVals = [
2✔
378
    uintCV(lockAmount.value), // amount-ustx
379
    poxAddrArg, // pox-addr
380
    uintCV(burnBlockHeight), // start-burn-height
381
    uintCV(lockPeriod), // lock-period
382
  ];
383
  const fnLenBuffer = Buffer.alloc(4);
2✔
384
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
2✔
385
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
8✔
386
  const rawFnArgs = bufferToHexPrefixString(
2✔
387
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
388
  );
389
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
2✔
390

391
  const tx: DecodedTxResult = {
2✔
392
    tx_id: txId,
393
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
2!
394
    chain_id: chainId,
395
    auth: {
396
      type_id: PostConditionAuthFlag.Standard,
397
      origin_condition: {
398
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
399
        signer: {
400
          address_version: senderAddress[0],
401
          address_hash_bytes: senderAddress[1],
402
          address: event.stx_lock_event.locked_address,
403
        },
404
        nonce: '0',
405
        tx_fee: '0',
406
        key_encoding: TxPublicKeyEncoding.Compressed,
407
        signature: '0x',
408
      },
409
    },
410
    anchor_mode: AnchorModeID.Any,
411
    post_condition_mode: PostConditionModeID.Allow,
412
    post_conditions: [],
413
    post_conditions_buffer: '0x0100000000',
414
    payload: {
415
      type_id: TxPayloadTypeID.ContractCall,
416
      address: poxAddressString,
417
      address_version: poxAddress[0],
418
      address_hash_bytes: poxAddress[1],
419
      contract_name: contractName,
420
      function_name: 'stack-stx',
421
      function_args: clarityFnArgs,
422
      function_args_buffer: rawFnArgs,
423
    },
424
  };
425
  return tx;
2✔
426
}
427

428
/*
429
;; Delegate to `delegate-to` the ability to stack from a given address.
430
;;  This method _does not_ lock the funds, rather, it allows the delegate
431
;;  to issue the stacking lock.
432
;; The caller specifies:
433
;;   * amount-ustx: the total amount of ustx the delegate may be allowed to lock
434
;;   * until-burn-ht: an optional burn height at which this delegation expiration
435
;;   * pox-addr: an optional address to which any rewards *must* be sent
436
(define-public (delegate-stx (amount-ustx uint)
437
                             (delegate-to principal)
438
                             (until-burn-ht (optional uint))
439
                             (pox-addr (optional { version: (buff 1),
440
                                                   hashbytes: (buff 32) })))
441
*/
442
function createTransactionFromCoreBtcDelegateStxEvent(
443
  chainId: ChainID,
444
  contractEvent: SmartContractEvent,
445
  decodedEvent: DbPox2DelegateStxEvent,
446
  txResult: string,
447
  txId: string
448
): DecodedTxResult {
449
  const resultCv = decodeClarityValue<ClarityValueResponse>(txResult);
×
450
  if (resultCv.type_id !== ClarityTypeID.ResponseOk) {
×
451
    throw new Error(`Unexpected tx result Clarity type ID: ${resultCv.type_id}`);
×
452
  }
453

454
  const senderAddress = decodeStacksAddress(decodedEvent.stacker);
×
455
  const poxContractAddressString =
456
    chainId === ChainID.Mainnet ? 'SP000000000000000000002Q6VF78' : 'ST000000000000000000002AMW42H';
×
457
  const poxContractAddress = decodeStacksAddress(poxContractAddressString);
×
458
  const contractName = contractEvent.contract_event.contract_identifier?.split('.')?.[1] ?? 'pox';
×
459

460
  let poxAddr: NoneCV | OptionalCV<TupleCV> = noneCV();
×
461
  if (decodedEvent.pox_addr) {
×
462
    poxAddr = someCV(poxAddressToTuple(decodedEvent.pox_addr));
×
463
  }
464

465
  let untilBurnHeight: NoneCV | OptionalCV<UIntCV> = noneCV();
×
466
  if (decodedEvent.data.unlock_burn_height) {
×
467
    untilBurnHeight = someCV(uintCV(decodedEvent.data.unlock_burn_height));
×
468
  }
469

470
  const legacyClarityVals = [
×
471
    uintCV(decodedEvent.data.amount_ustx), // amount-ustx
472
    principalCV(decodedEvent.data.delegate_to), // delegate-to
473
    untilBurnHeight, // until-burn-ht
474
    poxAddr, // pox-addr
475
  ];
476
  const fnLenBuffer = Buffer.alloc(4);
×
477
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
×
478
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
×
479
  const rawFnArgs = bufferToHexPrefixString(
×
480
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
481
  );
482
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
×
483

484
  const tx: DecodedTxResult = {
×
485
    tx_id: txId,
486
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
487
    chain_id: chainId,
488
    auth: {
489
      type_id: PostConditionAuthFlag.Standard,
490
      origin_condition: {
491
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
492
        signer: {
493
          address_version: senderAddress[0],
494
          address_hash_bytes: senderAddress[1],
495
          address: decodedEvent.stacker,
496
        },
497
        nonce: '0',
498
        tx_fee: '0',
499
        key_encoding: TxPublicKeyEncoding.Compressed,
500
        signature: '0x',
501
      },
502
    },
503
    anchor_mode: AnchorModeID.Any,
504
    post_condition_mode: PostConditionModeID.Allow,
505
    post_conditions: [],
506
    post_conditions_buffer: '0x0100000000',
507
    payload: {
508
      type_id: TxPayloadTypeID.ContractCall,
509
      address: poxContractAddressString,
510
      address_version: poxContractAddress[0],
511
      address_hash_bytes: poxContractAddress[1],
512
      contract_name: contractName,
513
      function_name: 'delegate-stx',
514
      function_args: clarityFnArgs,
515
      function_args_buffer: rawFnArgs,
516
    },
517
  };
518
  return tx;
×
519
}
520

521
function createTransactionFromCoreBtcTxEvent(
522
  chainId: ChainID,
523
  event: StxTransferEvent,
524
  txId: string
525
): DecodedTxResult {
526
  const recipientAddress = decodeStacksAddress(event.stx_transfer_event.recipient);
2✔
527
  const senderAddress = decodeStacksAddress(event.stx_transfer_event.sender);
2✔
528
  const tx: DecodedTxResult = {
2✔
529
    tx_id: txId,
530
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
2!
531
    chain_id: chainId,
532
    auth: {
533
      type_id: PostConditionAuthFlag.Standard,
534
      origin_condition: {
535
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
536
        signer: {
537
          address_version: senderAddress[0],
538
          address_hash_bytes: senderAddress[1],
539
          address: event.stx_transfer_event.sender,
540
        },
541
        nonce: '0',
542
        tx_fee: '0',
543
        key_encoding: TxPublicKeyEncoding.Compressed,
544
        signature: '0x',
545
      },
546
    },
547
    anchor_mode: AnchorModeID.Any,
548
    post_condition_mode: PostConditionModeID.Allow,
549
    post_conditions: [],
550
    post_conditions_buffer: '0x0100000000',
551
    payload: {
552
      type_id: TxPayloadTypeID.TokenTransfer,
553
      recipient: {
554
        type_id: PrincipalTypeID.Standard,
555
        address_version: recipientAddress[0],
556
        address_hash_bytes: recipientAddress[1],
557
        address: event.stx_transfer_event.recipient,
558
      },
559
      amount: BigInt(event.stx_transfer_event.amount).toString(),
560
      memo_hex: '0x',
561
    },
562
  };
563
  return tx;
2✔
564
}
565

566
export interface CoreNodeMsgBlockData {
567
  block_hash: string;
568
  index_block_hash: string;
569
  parent_index_block_hash: string;
570
  parent_block_hash: string;
571
  parent_burn_block_timestamp: number;
572
  parent_burn_block_height: number;
573
  parent_burn_block_hash: string;
574
  block_height: number;
575
  burn_block_time: number;
576
  burn_block_height: number;
577
}
578

579
export function parseMicroblocksFromTxs(args: {
39✔
580
  parentIndexBlockHash: string;
581
  txs: CoreNodeTxMessage[];
582
  parentBurnBlock: {
583
    hash: string;
584
    time: number;
585
    height: number;
586
  };
587
}): DbMicroblockPartial[] {
588
  const microblockMap = new Map<string, DbMicroblockPartial>();
601✔
589
  args.txs.forEach(tx => {
601✔
590
    if (isTxWithMicroblockInfo(tx) && !microblockMap.has(tx.microblock_hash)) {
968✔
591
      const dbMbPartial: DbMicroblockPartial = {
73✔
592
        microblock_hash: tx.microblock_hash,
593
        microblock_sequence: tx.microblock_sequence,
594
        microblock_parent_hash: tx.microblock_parent_hash,
595
        parent_index_block_hash: args.parentIndexBlockHash,
596
        parent_burn_block_height: args.parentBurnBlock.height,
597
        parent_burn_block_hash: args.parentBurnBlock.hash,
598
        parent_burn_block_time: args.parentBurnBlock.time,
599
      };
600
      microblockMap.set(tx.microblock_hash, dbMbPartial);
73✔
601
    }
602
  });
603
  const dbMicroblocks = [...microblockMap.values()].sort(
601✔
604
    (a, b) => a.microblock_sequence - b.microblock_sequence
38✔
605
  );
606
  return dbMicroblocks;
601✔
607
}
608

609
export function parseMessageTransaction(
39✔
610
  chainId: ChainID,
611
  coreTx: CoreNodeTxMessage,
612
  blockData: CoreNodeMsgBlockData,
613
  allEvents: CoreNodeEvent[]
614
): CoreNodeParsedTxMessage | null {
615
  try {
972✔
616
    let rawTx: DecodedTxResult;
617
    let txSender: string;
618
    let sponsorAddress: string | undefined = undefined;
972✔
619
    if (coreTx.raw_tx === '0x00') {
972✔
620
      const events = allEvents.filter(event => event.txid === coreTx.txid);
45✔
621
      if (events.length === 0) {
4!
622
        logger.warn(`Could not find event for process BTC tx: ${JSON.stringify(coreTx)}`);
×
623
        return null;
×
624
      }
625
      const stxTransferEvent = events.find(
4✔
626
        (e): e is StxTransferEvent => e.type === CoreNodeEventType.StxTransferEvent
4✔
627
      );
628
      const stxLockEvent = events.find(
4✔
629
        (e): e is StxLockEvent => e.type === CoreNodeEventType.StxLockEvent
4✔
630
      );
631
      const nftMintEvent = events.find(
4✔
632
        (e): e is NftMintEvent => e.type === CoreNodeEventType.NftMintEvent
4✔
633
      );
634
      const ftMintEvent = events.find(
4✔
635
        (e): e is FtMintEvent => e.type === CoreNodeEventType.FtMintEvent
4✔
636
      );
637
      const stxMintEvent = events.find(
4✔
638
        (e): e is StxMintEvent => e.type === CoreNodeEventType.StxMintEvent
4✔
639
      );
640

641
      const pox2Event = events
4✔
642
        .filter(
643
          (e): e is SmartContractEvent =>
644
            e.type === CoreNodeEventType.ContractEvent &&
4!
645
            e.contract_event.topic === 'print' &&
646
            (e.contract_event.contract_identifier === Pox2ContractIdentifer.mainnet ||
647
              e.contract_event.contract_identifier === Pox2ContractIdentifer.testnet)
648
        )
649
        .map(e => {
650
          const network = chainId === ChainID.Mainnet ? 'mainnet' : 'testnet';
×
651
          const decodedEvent = decodePox2PrintEvent(e.contract_event.raw_value, network);
×
652
          if (decodedEvent) {
×
653
            return {
×
654
              contractEvent: e,
655
              decodedEvent,
656
            };
657
          }
658
        })
659
        .find(e => !!e);
×
660

661
      const subnetEvents = events.filter(
4✔
662
        (e): e is SmartContractEvent =>
663
          e.type === CoreNodeEventType.ContractEvent &&
4!
664
          e.contract_event.topic === 'print' &&
665
          (e.contract_event.contract_identifier === SubnetContractIdentifer.mainnet ||
666
            e.contract_event.contract_identifier === SubnetContractIdentifer.testnet)
667
      );
668

669
      if (stxTransferEvent) {
4✔
670
        rawTx = createTransactionFromCoreBtcTxEvent(chainId, stxTransferEvent, coreTx.txid);
2✔
671
        txSender = stxTransferEvent.stx_transfer_event.sender;
2✔
672
      } else if (stxLockEvent) {
2!
673
        const stxStacksPox2Event =
674
          pox2Event?.decodedEvent.name === Pox2EventName.StackStx
2!
675
            ? pox2Event.decodedEvent
676
            : undefined;
677
        rawTx = createTransactionFromCoreBtcStxLockEvent(
2✔
678
          chainId,
679
          stxLockEvent,
680
          blockData.burn_block_height,
681
          coreTx.raw_result,
682
          coreTx.txid,
683
          stxStacksPox2Event
684
        );
685
        txSender = stxLockEvent.stx_lock_event.locked_address;
2✔
686
      } else if (pox2Event && pox2Event.decodedEvent.name === Pox2EventName.DelegateStx) {
×
687
        rawTx = createTransactionFromCoreBtcDelegateStxEvent(
×
688
          chainId,
689
          pox2Event.contractEvent,
690
          pox2Event.decodedEvent,
691
          coreTx.raw_result,
692
          coreTx.txid
693
        );
694
        txSender = pox2Event.decodedEvent.stacker;
×
695
      } else if (nftMintEvent) {
×
696
        rawTx = createSubnetTransactionFromL1NftDeposit(chainId, nftMintEvent, coreTx.txid);
×
697
        txSender = nftMintEvent.nft_mint_event.recipient;
×
698
      } else if (ftMintEvent) {
×
699
        rawTx = createSubnetTransactionFromL1FtDeposit(chainId, ftMintEvent, coreTx.txid);
×
700
        txSender = ftMintEvent.ft_mint_event.recipient;
×
701
      } else if (stxMintEvent) {
×
702
        rawTx = createSubnetTransactionFromL1StxDeposit(chainId, stxMintEvent, coreTx.txid);
×
703
        txSender = getTxSenderAddress(rawTx);
×
704
      } else if (
×
705
        subnetEvents.length > 0 &&
×
706
        coreTx.burnchain_op &&
707
        coreTx.burnchain_op.register_asset
708
      ) {
709
        rawTx = createSubnetTransactionFromL1RegisterAsset(
×
710
          chainId,
711
          coreTx.burnchain_op,
712
          subnetEvents[0],
713
          coreTx.txid
714
        );
715
        txSender = getTxSenderAddress(rawTx);
×
716
      } else {
NEW
717
        logger.error(
×
718
          `BTC transaction found, but no STX transfer event available to recreate transaction. TX: ${JSON.stringify(
719
            coreTx
720
          )}, event: ${JSON.stringify(events)}`
721
        );
722
        throw new Error('Unable to generate transaction from BTC tx');
×
723
      }
724
    } else {
725
      rawTx = decodeTransaction(coreTx.raw_tx.substring(2));
968✔
726
      txSender = getTxSenderAddress(rawTx);
968✔
727
      sponsorAddress = getTxSponsorAddress(rawTx);
968✔
728
    }
729
    const parsedTx: CoreNodeParsedTxMessage = {
972✔
730
      core_tx: coreTx,
731
      nonce: Number(rawTx.auth.origin_condition.nonce),
732
      raw_tx: coreTx.raw_tx,
733
      parsed_tx: rawTx,
734
      block_hash: blockData.block_hash,
735
      index_block_hash: blockData.index_block_hash,
736
      parent_index_block_hash: blockData.parent_index_block_hash,
737
      parent_block_hash: blockData.parent_block_hash,
738
      parent_burn_block_hash: blockData.parent_burn_block_hash,
739
      parent_burn_block_time: blockData.parent_burn_block_timestamp,
740
      block_height: blockData.block_height,
741
      burn_block_time: blockData.burn_block_time,
742
      microblock_sequence: coreTx.microblock_sequence ?? I32_MAX,
1,858✔
743
      microblock_hash: coreTx.microblock_hash ?? '',
1,858✔
744
      sender_address: txSender,
745
      sponsor_address: sponsorAddress,
746
    };
747
    const payload = rawTx.payload;
972✔
748
    switch (payload.type_id) {
972!
749
      case TxPayloadTypeID.Coinbase: {
750
        break;
576✔
751
      }
752
      case TxPayloadTypeID.CoinbaseToAltRecipient: {
753
        if (payload.recipient.type_id === PrincipalTypeID.Standard) {
×
NEW
754
          logger.debug(
×
755
            `Coinbase to alt recipient, standard principal: ${payload.recipient.address}`
756
          );
757
        } else {
NEW
758
          logger.debug(
×
759
            `Coinbase to alt recipient, contract principal: ${payload.recipient.address}.${payload.recipient.contract_name}`
760
          );
761
        }
762
        break;
×
763
      }
764
      case TxPayloadTypeID.SmartContract: {
765
        logger.debug(
120✔
766
          `Smart contract deployed: ${parsedTx.sender_address}.${payload.contract_name}`
767
        );
768
        break;
120✔
769
      }
770
      case TxPayloadTypeID.ContractCall: {
771
        logger.debug(
95✔
772
          `Contract call: ${payload.address}.${payload.contract_name}.${payload.function_name}`
773
        );
774
        break;
95✔
775
      }
776
      case TxPayloadTypeID.TokenTransfer: {
777
        let recipientPrincipal = payload.recipient.address;
181✔
778
        if (payload.recipient.type_id === PrincipalTypeID.Contract) {
181!
779
          recipientPrincipal += '.' + payload.recipient.contract_name;
×
780
        }
781
        logger.debug(
181✔
782
          `Token transfer: ${payload.amount} from ${parsedTx.sender_address} to ${recipientPrincipal}`
783
        );
784
        break;
181✔
785
      }
786
      case TxPayloadTypeID.PoisonMicroblock: {
NEW
787
        logger.debug(
×
788
          `Poison microblock: header1 ${payload.microblock_header_1}), header2: ${payload.microblock_header_2}`
789
        );
790
        break;
×
791
      }
792
      case TxPayloadTypeID.VersionedSmartContract: {
NEW
793
        logger.debug(
×
794
          `Versioned smart contract deployed: Clarity version ${payload.clarity_version}, ${parsedTx.sender_address}.${payload.contract_name}`
795
        );
796
        break;
×
797
      }
798
      default: {
799
        throw new NotImplementedError(
×
800
          `extracting data for tx type: ${getEnumDescription(
801
            TxPayloadTypeID,
802
            rawTx.payload.type_id
803
          )}`
804
        );
805
      }
806
    }
807
    return parsedTx;
972✔
808
  } catch (error) {
NEW
809
    logger.error(error, `error parsing message transaction ${JSON.stringify(coreTx)}`);
×
810
    throw error;
×
811
  }
812
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc