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

hirosystems / stacks-blockchain-api / 4175232103

pending completion
4175232103

push

github

Matthew Little
feat: beta release with subnets support

2106 of 3165 branches covered (66.54%)

7335 of 9394 relevant lines covered (78.08%)

1193.31 hits per line

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

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

73
export function getTxSenderAddress(tx: DecodedTxResult): string {
36✔
74
  const txSender = tx.auth.origin_condition.signer.address;
987✔
75
  return txSender;
987✔
76
}
77

78
export function getTxSponsorAddress(tx: DecodedTxResult): string | undefined {
36✔
79
  let sponsorAddress: string | undefined = undefined;
890✔
80
  if (tx.auth.type_id === PostConditionAuthFlag.Sponsored) {
890✔
81
    sponsorAddress = tx.auth.sponsor_condition.signer.address;
1✔
82
  }
83
  return sponsorAddress;
890✔
84
}
85

86
function createSubnetTransactionFromL1NftDeposit(
87
  chainId: ChainID,
88
  event: NftMintEvent,
89
  txId: string
90
): DecodedTxResult {
91
  const decRecipientAddress = decodeStacksAddress(event.nft_mint_event.recipient);
×
92
  const [contractAddress, contractName] = event.nft_mint_event.asset_identifier
×
93
    .split('::')[0]
94
    .split('.');
95
  const decContractAddress = decodeStacksAddress(contractAddress);
×
96
  const legacyClarityVals = [
×
97
    hexToCV(event.nft_mint_event.raw_value),
98
    principalCV(event.nft_mint_event.recipient),
99
  ];
100
  const fnLenBuffer = Buffer.alloc(4);
×
101
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
×
102
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
×
103
  const rawFnArgs = bufferToHexPrefixString(
×
104
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
105
  );
106
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
×
107

108
  const tx: DecodedTxResult = {
×
109
    tx_id: txId,
110
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
111
    chain_id: chainId,
112
    auth: {
113
      type_id: PostConditionAuthFlag.Standard,
114
      origin_condition: {
115
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
116
        signer: {
117
          address_version: decRecipientAddress[0],
118
          address_hash_bytes: decRecipientAddress[1],
119
          address: event.nft_mint_event.recipient,
120
        },
121
        nonce: '0',
122
        tx_fee: '0',
123
        key_encoding: TxPublicKeyEncoding.Compressed,
124
        signature: '0x',
125
      },
126
    },
127
    anchor_mode: AnchorModeID.Any,
128
    post_condition_mode: PostConditionModeID.Allow,
129
    post_conditions: [],
130
    post_conditions_buffer: '0x0100000000',
131
    payload: {
132
      type_id: TxPayloadTypeID.ContractCall,
133
      address_version: decContractAddress[0],
134
      address_hash_bytes: decContractAddress[1],
135
      address: contractAddress,
136
      contract_name: contractName,
137
      function_name: 'deposit-from-burnchain',
138
      function_args: clarityFnArgs,
139
      function_args_buffer: rawFnArgs,
140
    },
141
  };
142
  return tx;
×
143
}
144

145
function createSubnetTransactionFromL1FtDeposit(
146
  chainId: ChainID,
147
  event: FtMintEvent,
148
  txId: string
149
): DecodedTxResult {
150
  const decRecipientAddress = decodeStacksAddress(event.ft_mint_event.recipient);
×
151
  const [contractAddress, contractName] = event.ft_mint_event.asset_identifier
×
152
    .split('::')[0]
153
    .split('.');
154
  const decContractAddress = decodeStacksAddress(contractAddress);
×
155
  const legacyClarityVals = [
×
156
    uintCV(event.ft_mint_event.amount),
157
    principalCV(event.ft_mint_event.recipient),
158
  ];
159
  const fnLenBuffer = Buffer.alloc(4);
×
160
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
×
161
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
×
162
  const rawFnArgs = bufferToHexPrefixString(
×
163
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
164
  );
165
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
×
166

167
  const tx: DecodedTxResult = {
×
168
    tx_id: txId,
169
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
170
    chain_id: chainId,
171
    auth: {
172
      type_id: PostConditionAuthFlag.Standard,
173
      origin_condition: {
174
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
175
        signer: {
176
          address_version: decRecipientAddress[0],
177
          address_hash_bytes: decRecipientAddress[1],
178
          address: event.ft_mint_event.recipient,
179
        },
180
        nonce: '0',
181
        tx_fee: '0',
182
        key_encoding: TxPublicKeyEncoding.Compressed,
183
        signature: '0x',
184
      },
185
    },
186
    anchor_mode: AnchorModeID.Any,
187
    post_condition_mode: PostConditionModeID.Allow,
188
    post_conditions: [],
189
    post_conditions_buffer: '0x0100000000',
190
    payload: {
191
      type_id: TxPayloadTypeID.ContractCall,
192
      address_version: decContractAddress[0],
193
      address_hash_bytes: decContractAddress[1],
194
      address: contractAddress,
195
      contract_name: contractName,
196
      function_name: 'deposit-from-burnchain',
197
      function_args: clarityFnArgs,
198
      function_args_buffer: rawFnArgs,
199
    },
200
  };
201
  return tx;
×
202
}
203

204
function createSubnetTransactionFromL1StxDeposit(
205
  chainId: ChainID,
206
  event: StxMintEvent,
207
  txId: string
208
): DecodedTxResult {
209
  const recipientAddress = decodeStacksAddress(event.stx_mint_event.recipient);
×
210
  const bootAddressString =
211
    chainId === ChainID.Mainnet ? 'SP000000000000000000002Q6VF78' : 'ST000000000000000000002AMW42H';
×
212
  const bootAddress = decodeStacksAddress(bootAddressString);
×
213

214
  const tx: DecodedTxResult = {
×
215
    tx_id: txId,
216
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
217
    chain_id: chainId,
218
    auth: {
219
      type_id: PostConditionAuthFlag.Standard,
220
      origin_condition: {
221
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
222
        signer: {
223
          address_version: bootAddress[0],
224
          address_hash_bytes: bootAddress[1],
225
          address: bootAddressString,
226
        },
227
        nonce: '0',
228
        tx_fee: '0',
229
        key_encoding: TxPublicKeyEncoding.Compressed,
230
        signature: '0x',
231
      },
232
    },
233
    anchor_mode: AnchorModeID.Any,
234
    post_condition_mode: PostConditionModeID.Allow,
235
    post_conditions: [],
236
    post_conditions_buffer: '0x0100000000',
237
    payload: {
238
      type_id: TxPayloadTypeID.TokenTransfer,
239
      recipient: {
240
        type_id: PrincipalTypeID.Standard,
241
        address_version: recipientAddress[0],
242
        address_hash_bytes: recipientAddress[1],
243
        address: event.stx_mint_event.recipient,
244
      },
245
      amount: BigInt(event.stx_mint_event.amount).toString(),
246
      memo_hex: '0x',
247
    },
248
  };
249
  return tx;
×
250
}
251

252
function createTransactionFromCoreBtcStxLockEvent(
253
  chainId: ChainID,
254
  event: StxLockEvent,
255
  burnBlockHeight: number,
256
  txResult: string,
257
  txId: string,
258
  stxStacksPox2Event: DbPox2StackStxEvent | undefined
259
): DecodedTxResult {
260
  const resultCv = decodeClarityValue<
2✔
261
    ClarityValueResponse<
262
      ClarityValueTuple<{
263
        'lock-amount': ClarityValueUInt;
264
        'unlock-burn-height': ClarityValueUInt;
265
        stacker: ClarityValuePrincipalStandard;
266
      }>
267
    >
268
  >(txResult);
269
  if (resultCv.type_id !== ClarityTypeID.ResponseOk) {
2!
270
    throw new Error(`Unexpected tx result Clarity type ID: ${resultCv.type_id}`);
×
271
  }
272
  const resultTuple = resultCv.value;
2✔
273
  const lockAmount = resultTuple.data['lock-amount'];
2✔
274
  const stacker = resultTuple.data['stacker'];
2✔
275
  const unlockBurnHeight = Number(resultTuple.data['unlock-burn-height'].value);
2✔
276

277
  // Number of cycles: floor((unlock-burn-height - burn-height) / reward-cycle-length)
278
  const rewardCycleLength = chainId === ChainID.Mainnet ? 2100 : 50;
2!
279
  const lockPeriod = Math.floor((unlockBurnHeight - burnBlockHeight) / rewardCycleLength);
2✔
280
  const senderAddress = decodeStacksAddress(event.stx_lock_event.locked_address);
2✔
281
  const poxAddressString =
282
    chainId === ChainID.Mainnet ? 'SP000000000000000000002Q6VF78' : 'ST000000000000000000002AMW42H';
2!
283
  const poxAddress = decodeStacksAddress(poxAddressString);
2✔
284

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

287
  // If a pox-2 event is available then use its pox_addr, otherwise fallback to the stacker address
288
  const poxAddrArg = stxStacksPox2Event?.pox_addr
2!
289
    ? poxAddressToTuple(stxStacksPox2Event.pox_addr)
290
    : poxAddressToTuple(c32ToB58(stacker.address));
291

292
  const legacyClarityVals = [
2✔
293
    uintCV(lockAmount.value), // amount-ustx
294
    poxAddrArg, // pox-addr
295
    uintCV(burnBlockHeight), // start-burn-height
296
    uintCV(lockPeriod), // lock-period
297
  ];
298
  const fnLenBuffer = Buffer.alloc(4);
2✔
299
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
2✔
300
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
8✔
301
  const rawFnArgs = bufferToHexPrefixString(
2✔
302
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
303
  );
304
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
2✔
305

306
  const tx: DecodedTxResult = {
2✔
307
    tx_id: txId,
308
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
2!
309
    chain_id: chainId,
310
    auth: {
311
      type_id: PostConditionAuthFlag.Standard,
312
      origin_condition: {
313
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
314
        signer: {
315
          address_version: senderAddress[0],
316
          address_hash_bytes: senderAddress[1],
317
          address: event.stx_lock_event.locked_address,
318
        },
319
        nonce: '0',
320
        tx_fee: '0',
321
        key_encoding: TxPublicKeyEncoding.Compressed,
322
        signature: '0x',
323
      },
324
    },
325
    anchor_mode: AnchorModeID.Any,
326
    post_condition_mode: PostConditionModeID.Allow,
327
    post_conditions: [],
328
    post_conditions_buffer: '0x0100000000',
329
    payload: {
330
      type_id: TxPayloadTypeID.ContractCall,
331
      address: poxAddressString,
332
      address_version: poxAddress[0],
333
      address_hash_bytes: poxAddress[1],
334
      contract_name: contractName,
335
      function_name: 'stack-stx',
336
      function_args: clarityFnArgs,
337
      function_args_buffer: rawFnArgs,
338
    },
339
  };
340
  return tx;
2✔
341
}
342

343
/*
344
;; Delegate to `delegate-to` the ability to stack from a given address.
345
;;  This method _does not_ lock the funds, rather, it allows the delegate
346
;;  to issue the stacking lock.
347
;; The caller specifies:
348
;;   * amount-ustx: the total amount of ustx the delegate may be allowed to lock
349
;;   * until-burn-ht: an optional burn height at which this delegation expiration
350
;;   * pox-addr: an optional address to which any rewards *must* be sent
351
(define-public (delegate-stx (amount-ustx uint)
352
                             (delegate-to principal)
353
                             (until-burn-ht (optional uint))
354
                             (pox-addr (optional { version: (buff 1),
355
                                                   hashbytes: (buff 32) })))
356
*/
357
function createTransactionFromCoreBtcDelegateStxEvent(
358
  chainId: ChainID,
359
  contractEvent: SmartContractEvent,
360
  decodedEvent: DbPox2DelegateStxEvent,
361
  txResult: string,
362
  txId: string
363
): DecodedTxResult {
364
  const resultCv = decodeClarityValue<ClarityValueResponse>(txResult);
×
365
  if (resultCv.type_id !== ClarityTypeID.ResponseOk) {
×
366
    throw new Error(`Unexpected tx result Clarity type ID: ${resultCv.type_id}`);
×
367
  }
368

369
  const senderAddress = decodeStacksAddress(decodedEvent.stacker);
×
370
  const poxContractAddressString =
371
    chainId === ChainID.Mainnet ? 'SP000000000000000000002Q6VF78' : 'ST000000000000000000002AMW42H';
×
372
  const poxContractAddress = decodeStacksAddress(poxContractAddressString);
×
373
  const contractName = contractEvent.contract_event.contract_identifier?.split('.')?.[1] ?? 'pox';
×
374

375
  let poxAddr: NoneCV | OptionalCV<TupleCV> = noneCV();
×
376
  if (decodedEvent.pox_addr) {
×
377
    poxAddr = someCV(poxAddressToTuple(decodedEvent.pox_addr));
×
378
  }
379

380
  let untilBurnHeight: NoneCV | OptionalCV<UIntCV> = noneCV();
×
381
  if (decodedEvent.data.unlock_burn_height) {
×
382
    untilBurnHeight = someCV(uintCV(decodedEvent.data.unlock_burn_height));
×
383
  }
384

385
  const legacyClarityVals = [
×
386
    uintCV(decodedEvent.data.amount_ustx), // amount-ustx
387
    principalCV(decodedEvent.data.delegate_to), // delegate-to
388
    untilBurnHeight, // until-burn-ht
389
    poxAddr, // pox-addr
390
  ];
391
  const fnLenBuffer = Buffer.alloc(4);
×
392
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
×
393
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
×
394
  const rawFnArgs = bufferToHexPrefixString(
×
395
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
396
  );
397
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
×
398

399
  const tx: DecodedTxResult = {
×
400
    tx_id: txId,
401
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
×
402
    chain_id: chainId,
403
    auth: {
404
      type_id: PostConditionAuthFlag.Standard,
405
      origin_condition: {
406
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
407
        signer: {
408
          address_version: senderAddress[0],
409
          address_hash_bytes: senderAddress[1],
410
          address: decodedEvent.stacker,
411
        },
412
        nonce: '0',
413
        tx_fee: '0',
414
        key_encoding: TxPublicKeyEncoding.Compressed,
415
        signature: '0x',
416
      },
417
    },
418
    anchor_mode: AnchorModeID.Any,
419
    post_condition_mode: PostConditionModeID.Allow,
420
    post_conditions: [],
421
    post_conditions_buffer: '0x0100000000',
422
    payload: {
423
      type_id: TxPayloadTypeID.ContractCall,
424
      address: poxContractAddressString,
425
      address_version: poxContractAddress[0],
426
      address_hash_bytes: poxContractAddress[1],
427
      contract_name: contractName,
428
      function_name: 'delegate-stx',
429
      function_args: clarityFnArgs,
430
      function_args_buffer: rawFnArgs,
431
    },
432
  };
433
  return tx;
×
434
}
435

436
function createTransactionFromCoreBtcTxEvent(
437
  chainId: ChainID,
438
  event: StxTransferEvent,
439
  txId: string
440
): DecodedTxResult {
441
  const recipientAddress = decodeStacksAddress(event.stx_transfer_event.recipient);
2✔
442
  const senderAddress = decodeStacksAddress(event.stx_transfer_event.sender);
2✔
443
  const tx: DecodedTxResult = {
2✔
444
    tx_id: txId,
445
    version: chainId === ChainID.Mainnet ? TransactionVersion.Mainnet : TransactionVersion.Testnet,
2!
446
    chain_id: chainId,
447
    auth: {
448
      type_id: PostConditionAuthFlag.Standard,
449
      origin_condition: {
450
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
451
        signer: {
452
          address_version: senderAddress[0],
453
          address_hash_bytes: senderAddress[1],
454
          address: event.stx_transfer_event.sender,
455
        },
456
        nonce: '0',
457
        tx_fee: '0',
458
        key_encoding: TxPublicKeyEncoding.Compressed,
459
        signature: '0x',
460
      },
461
    },
462
    anchor_mode: AnchorModeID.Any,
463
    post_condition_mode: PostConditionModeID.Allow,
464
    post_conditions: [],
465
    post_conditions_buffer: '0x0100000000',
466
    payload: {
467
      type_id: TxPayloadTypeID.TokenTransfer,
468
      recipient: {
469
        type_id: PrincipalTypeID.Standard,
470
        address_version: recipientAddress[0],
471
        address_hash_bytes: recipientAddress[1],
472
        address: event.stx_transfer_event.recipient,
473
      },
474
      amount: BigInt(event.stx_transfer_event.amount).toString(),
475
      memo_hex: '0x',
476
    },
477
  };
478
  return tx;
2✔
479
}
480

481
export interface CoreNodeMsgBlockData {
482
  block_hash: string;
483
  index_block_hash: string;
484
  parent_index_block_hash: string;
485
  parent_block_hash: string;
486
  parent_burn_block_timestamp: number;
487
  parent_burn_block_height: number;
488
  parent_burn_block_hash: string;
489
  block_height: number;
490
  burn_block_time: number;
491
  burn_block_height: number;
492
}
493

494
export function parseMicroblocksFromTxs(args: {
36✔
495
  parentIndexBlockHash: string;
496
  txs: CoreNodeTxMessage[];
497
  parentBurnBlock: {
498
    hash: string;
499
    time: number;
500
    height: number;
501
  };
502
}): DbMicroblockPartial[] {
503
  const microblockMap = new Map<string, DbMicroblockPartial>();
419✔
504
  args.txs.forEach(tx => {
419✔
505
    if (isTxWithMicroblockInfo(tx) && !microblockMap.has(tx.microblock_hash)) {
734✔
506
      const dbMbPartial: DbMicroblockPartial = {
59✔
507
        microblock_hash: tx.microblock_hash,
508
        microblock_sequence: tx.microblock_sequence,
509
        microblock_parent_hash: tx.microblock_parent_hash,
510
        parent_index_block_hash: args.parentIndexBlockHash,
511
        parent_burn_block_height: args.parentBurnBlock.height,
512
        parent_burn_block_hash: args.parentBurnBlock.hash,
513
        parent_burn_block_time: args.parentBurnBlock.time,
514
      };
515
      microblockMap.set(tx.microblock_hash, dbMbPartial);
59✔
516
    }
517
  });
518
  const dbMicroblocks = [...microblockMap.values()].sort(
419✔
519
    (a, b) => a.microblock_sequence - b.microblock_sequence
31✔
520
  );
521
  return dbMicroblocks;
419✔
522
}
523

524
export function parseMessageTransaction(
36✔
525
  chainId: ChainID,
526
  coreTx: CoreNodeTxMessage,
527
  blockData: CoreNodeMsgBlockData,
528
  allEvents: CoreNodeEvent[]
529
): CoreNodeParsedTxMessage | null {
530
  try {
738✔
531
    let rawTx: DecodedTxResult;
532
    let txSender: string;
533
    let sponsorAddress: string | undefined = undefined;
738✔
534
    if (coreTx.raw_tx === '0x00') {
738✔
535
      const events = allEvents.filter(event => event.txid === coreTx.txid);
45✔
536
      if (events.length === 0) {
4!
537
        logger.warn(`Could not find event for process BTC tx: ${JSON.stringify(coreTx)}`);
×
538
        return null;
×
539
      }
540
      const stxTransferEvent = events.find(
4✔
541
        (e): e is StxTransferEvent => e.type === CoreNodeEventType.StxTransferEvent
4✔
542
      );
543
      const stxLockEvent = events.find(
4✔
544
        (e): e is StxLockEvent => e.type === CoreNodeEventType.StxLockEvent
4✔
545
      );
546
      const nftMintEvent = events.find(
4✔
547
        (e): e is NftMintEvent => e.type === CoreNodeEventType.NftMintEvent
4✔
548
      );
549
      const ftMintEvent = events.find(
4✔
550
        (e): e is FtMintEvent => e.type === CoreNodeEventType.FtMintEvent
4✔
551
      );
552
      const stxMintEvent = events.find(
4✔
553
        (e): e is StxMintEvent => e.type === CoreNodeEventType.StxMintEvent
4✔
554
      );
555

556
      const pox2Event = events
4✔
557
        .filter(
558
          (e): e is SmartContractEvent =>
559
            e.type === CoreNodeEventType.ContractEvent &&
4!
560
            e.contract_event.topic === 'print' &&
561
            (e.contract_event.contract_identifier === Pox2ContractIdentifer.mainnet ||
562
              e.contract_event.contract_identifier === Pox2ContractIdentifer.testnet)
563
        )
564
        .map(e => {
565
          const network = chainId === ChainID.Mainnet ? 'mainnet' : 'testnet';
×
566
          const decodedEvent = decodePox2PrintEvent(e.contract_event.raw_value, network);
×
567
          if (decodedEvent) {
×
568
            return {
×
569
              contractEvent: e,
570
              decodedEvent,
571
            };
572
          }
573
        })
574
        .find(e => !!e);
×
575

576
      if (stxTransferEvent) {
4✔
577
        rawTx = createTransactionFromCoreBtcTxEvent(chainId, stxTransferEvent, coreTx.txid);
2✔
578
        txSender = stxTransferEvent.stx_transfer_event.sender;
2✔
579
      } else if (stxLockEvent) {
2!
580
        const stxStacksPox2Event =
581
          pox2Event?.decodedEvent.name === Pox2EventName.StackStx
2!
582
            ? pox2Event.decodedEvent
583
            : undefined;
584
        rawTx = createTransactionFromCoreBtcStxLockEvent(
2✔
585
          chainId,
586
          stxLockEvent,
587
          blockData.burn_block_height,
588
          coreTx.raw_result,
589
          coreTx.txid,
590
          stxStacksPox2Event
591
        );
592
        txSender = stxLockEvent.stx_lock_event.locked_address;
2✔
593
      } else if (pox2Event && pox2Event.decodedEvent.name === Pox2EventName.DelegateStx) {
×
594
        rawTx = createTransactionFromCoreBtcDelegateStxEvent(
×
595
          chainId,
596
          pox2Event.contractEvent,
597
          pox2Event.decodedEvent,
598
          coreTx.raw_result,
599
          coreTx.txid
600
        );
601
        txSender = pox2Event.decodedEvent.stacker;
×
602
      } else if (nftMintEvent) {
×
603
        rawTx = createSubnetTransactionFromL1NftDeposit(chainId, nftMintEvent, coreTx.txid);
×
604
        txSender = nftMintEvent.nft_mint_event.recipient;
×
605
      } else if (ftMintEvent) {
×
606
        rawTx = createSubnetTransactionFromL1FtDeposit(chainId, ftMintEvent, coreTx.txid);
×
607
        txSender = ftMintEvent.ft_mint_event.recipient;
×
608
      } else if (stxMintEvent) {
×
609
        rawTx = createSubnetTransactionFromL1StxDeposit(chainId, stxMintEvent, coreTx.txid);
×
610
        txSender = getTxSenderAddress(rawTx);
×
611
      } else {
612
        logError(
×
613
          `BTC transaction found, but no STX transfer event available to recreate transaction. TX: ${JSON.stringify(
614
            coreTx
615
          )}, event: ${JSON.stringify(events)}`
616
        );
617
        throw new Error('Unable to generate transaction from BTC tx');
×
618
      }
619
    } else {
620
      rawTx = decodeTransaction(coreTx.raw_tx.substring(2));
734✔
621
      txSender = getTxSenderAddress(rawTx);
734✔
622
      sponsorAddress = getTxSponsorAddress(rawTx);
734✔
623
    }
624
    const parsedTx: CoreNodeParsedTxMessage = {
738✔
625
      core_tx: coreTx,
626
      nonce: Number(rawTx.auth.origin_condition.nonce),
627
      raw_tx: coreTx.raw_tx,
628
      parsed_tx: rawTx,
629
      block_hash: blockData.block_hash,
630
      index_block_hash: blockData.index_block_hash,
631
      parent_index_block_hash: blockData.parent_index_block_hash,
632
      parent_block_hash: blockData.parent_block_hash,
633
      parent_burn_block_hash: blockData.parent_burn_block_hash,
634
      parent_burn_block_time: blockData.parent_burn_block_timestamp,
635
      block_height: blockData.block_height,
636
      burn_block_time: blockData.burn_block_time,
637
      microblock_sequence: coreTx.microblock_sequence ?? I32_MAX,
1,404✔
638
      microblock_hash: coreTx.microblock_hash ?? '',
1,404✔
639
      sender_address: txSender,
640
      sponsor_address: sponsorAddress,
641
    };
642
    const payload = rawTx.payload;
738✔
643
    switch (payload.type_id) {
738!
644
      case TxPayloadTypeID.Coinbase: {
645
        break;
401✔
646
      }
647
      case TxPayloadTypeID.CoinbaseToAltRecipient: {
648
        if (payload.recipient.type_id === PrincipalTypeID.Standard) {
×
649
          logger.verbose(
×
650
            `Coinbase to alt recipient, standard principal: ${payload.recipient.address}`
651
          );
652
        } else {
653
          logger.verbose(
×
654
            `Coinbase to alt recipient, contract principal: ${payload.recipient.address}.${payload.recipient.contract_name}`
655
          );
656
        }
657
        break;
×
658
      }
659
      case TxPayloadTypeID.SmartContract: {
660
        logger.verbose(
84✔
661
          `Smart contract deployed: ${parsedTx.sender_address}.${payload.contract_name}`
662
        );
663
        break;
84✔
664
      }
665
      case TxPayloadTypeID.ContractCall: {
666
        logger.verbose(
81✔
667
          `Contract call: ${payload.address}.${payload.contract_name}.${payload.function_name}`
668
        );
669
        break;
81✔
670
      }
671
      case TxPayloadTypeID.TokenTransfer: {
672
        let recipientPrincipal = payload.recipient.address;
172✔
673
        if (payload.recipient.type_id === PrincipalTypeID.Contract) {
172!
674
          recipientPrincipal += '.' + payload.recipient.contract_name;
×
675
        }
676
        logger.verbose(
172✔
677
          `Token transfer: ${payload.amount} from ${parsedTx.sender_address} to ${recipientPrincipal}`
678
        );
679
        break;
172✔
680
      }
681
      case TxPayloadTypeID.PoisonMicroblock: {
682
        logger.verbose(
×
683
          `Poison microblock: header1 ${payload.microblock_header_1}), header2: ${payload.microblock_header_2}`
684
        );
685
        break;
×
686
      }
687
      case TxPayloadTypeID.VersionedSmartContract: {
688
        logger.verbose(
×
689
          `Versioned smart contract deployed: Clarity version ${payload.clarity_version}, ${parsedTx.sender_address}.${payload.contract_name}`
690
        );
691
        break;
×
692
      }
693
      default: {
694
        throw new NotImplementedError(
×
695
          `extracting data for tx type: ${getEnumDescription(
696
            TxPayloadTypeID,
697
            rawTx.payload.type_id
698
          )}`
699
        );
700
      }
701
    }
702
    return parsedTx;
738✔
703
  } catch (error) {
704
    logError(`error parsing message transaction ${JSON.stringify(coreTx)}: ${error}`, error);
×
705
    throw error;
×
706
  }
707
}
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