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

hirosystems / stacks-blockchain-api / 5229768095

pending completion
5229768095

Pull #1669

github

web-flow
Merge c02a7bddb into b050b1bf2
Pull Request #1669: feat: support custom chain_id (e.g. for subnets)

2150 of 3230 branches covered (66.56%)

28 of 57 new or added lines in 14 files covered. (49.12%)

7499 of 9630 relevant lines covered (77.87%)

2041.58 hits per line

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

43.42
/src/event-stream/reader.ts
1
import {
78✔
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 {
78✔
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';
78✔
43
import {
78✔
44
  getEnumDescription,
45
  I32_MAX,
46
  bufferToHexPrefixString,
47
  hexToBuffer,
48
  SubnetContractIdentifer,
49
  getChainIDNetwork,
50
  ChainID,
51
} from '../helpers';
52
import {
78✔
53
  TransactionVersion,
54
  uintCV,
55
  tupleCV,
56
  bufferCV,
57
  serializeCV,
58
  noneCV,
59
  someCV,
60
  OptionalCV,
61
  TupleCV,
62
  BufferCV,
63
  SomeCV,
64
  NoneCV,
65
  UIntCV,
66
  stringAsciiCV,
67
  hexToCV,
68
} from '@stacks/transactions';
69
import { poxAddressToTuple } from '@stacks/stacking';
78✔
70
import { c32ToB58 } from 'c32check';
78✔
71
import { decodePox2PrintEvent } from './pox2-event-parsing';
78✔
72
import { Pox2ContractIdentifer, Pox2EventName } from '../pox-helpers';
73
import { principalCV } from '@stacks/transactions/dist/clarity/types/principalCV';
78✔
74
import { logger } from '../logger';
78✔
75

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

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

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

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

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

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

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

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

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

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

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

299
function createSubnetTransactionFromL1StxDeposit(
300
  chainId: ChainID,
301
  event: StxMintEvent,
302
  txId: string
303
): DecodedTxResult {
304
  const recipientAddress = decodeStacksAddress(event.stx_mint_event.recipient);
×
305
  const bootAddressString =
NEW
306
    getChainIDNetwork(chainId) === 'mainnet'
×
307
      ? 'SP000000000000000000002Q6VF78'
308
      : 'ST000000000000000000002AMW42H';
309
  const bootAddress = decodeStacksAddress(bootAddressString);
×
310

311
  const tx: DecodedTxResult = {
×
312
    tx_id: txId,
313
    version:
314
      getChainIDNetwork(chainId) === 'mainnet'
×
315
        ? TransactionVersion.Mainnet
316
        : TransactionVersion.Testnet,
317
    chain_id: chainId,
318
    auth: {
319
      type_id: PostConditionAuthFlag.Standard,
320
      origin_condition: {
321
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
322
        signer: {
323
          address_version: bootAddress[0],
324
          address_hash_bytes: bootAddress[1],
325
          address: bootAddressString,
326
        },
327
        nonce: '0',
328
        tx_fee: '0',
329
        key_encoding: TxPublicKeyEncoding.Compressed,
330
        signature: '0x',
331
      },
332
    },
333
    anchor_mode: AnchorModeID.Any,
334
    post_condition_mode: PostConditionModeID.Allow,
335
    post_conditions: [],
336
    post_conditions_buffer: '0x0100000000',
337
    payload: {
338
      type_id: TxPayloadTypeID.TokenTransfer,
339
      recipient: {
340
        type_id: PrincipalTypeID.Standard,
341
        address_version: recipientAddress[0],
342
        address_hash_bytes: recipientAddress[1],
343
        address: event.stx_mint_event.recipient,
344
      },
345
      amount: BigInt(event.stx_mint_event.amount).toString(),
346
      memo_hex: '0x',
347
    },
348
  };
349
  return tx;
×
350
}
351

352
function createTransactionFromCoreBtcStxLockEvent(
353
  chainId: ChainID,
354
  event: StxLockEvent,
355
  burnBlockHeight: number,
356
  txResult: string,
357
  txId: string,
358
  stxStacksPox2Event: DbPox2StackStxEvent | undefined
359
): DecodedTxResult {
360
  const resultCv = decodeClarityValue<
4✔
361
    ClarityValueResponse<
362
      ClarityValueTuple<{
363
        'lock-amount': ClarityValueUInt;
364
        'unlock-burn-height': ClarityValueUInt;
365
        stacker: ClarityValuePrincipalStandard;
366
      }>
367
    >
368
  >(txResult);
369
  if (resultCv.type_id !== ClarityTypeID.ResponseOk) {
4!
370
    throw new Error(`Unexpected tx result Clarity type ID: ${resultCv.type_id}`);
×
371
  }
372
  const resultTuple = resultCv.value;
4✔
373
  const lockAmount = resultTuple.data['lock-amount'];
4✔
374
  const stacker = resultTuple.data['stacker'];
4✔
375
  const unlockBurnHeight = Number(resultTuple.data['unlock-burn-height'].value);
4✔
376

377
  // Number of cycles: floor((unlock-burn-height - burn-height) / reward-cycle-length)
378
  const rewardCycleLength = getChainIDNetwork(chainId) === 'mainnet' ? 2100 : 50;
4!
379
  const lockPeriod = Math.floor((unlockBurnHeight - burnBlockHeight) / rewardCycleLength);
4✔
380
  const senderAddress = decodeStacksAddress(event.stx_lock_event.locked_address);
4✔
381
  const poxAddressString =
382
    getChainIDNetwork(chainId) === 'mainnet'
4!
383
      ? 'SP000000000000000000002Q6VF78'
384
      : 'ST000000000000000000002AMW42H';
385
  const poxAddress = decodeStacksAddress(poxAddressString);
4✔
386

387
  const contractName = event.stx_lock_event.contract_identifier?.split('.')?.[1] ?? 'pox';
4✔
388

389
  // If a pox-2 event is available then use its pox_addr, otherwise fallback to the stacker address
390
  const poxAddrArg = stxStacksPox2Event?.pox_addr
4!
391
    ? poxAddressToTuple(stxStacksPox2Event.pox_addr)
392
    : poxAddressToTuple(c32ToB58(stacker.address));
393

394
  const legacyClarityVals = [
4✔
395
    uintCV(lockAmount.value), // amount-ustx
396
    poxAddrArg, // pox-addr
397
    uintCV(burnBlockHeight), // start-burn-height
398
    uintCV(lockPeriod), // lock-period
399
  ];
400
  const fnLenBuffer = Buffer.alloc(4);
4✔
401
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
4✔
402
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
16✔
403
  const rawFnArgs = bufferToHexPrefixString(
4✔
404
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
405
  );
406
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
4✔
407

408
  const tx: DecodedTxResult = {
4✔
409
    tx_id: txId,
410
    version:
411
      getChainIDNetwork(chainId) === 'mainnet'
4!
412
        ? TransactionVersion.Mainnet
413
        : TransactionVersion.Testnet,
414
    chain_id: chainId,
415
    auth: {
416
      type_id: PostConditionAuthFlag.Standard,
417
      origin_condition: {
418
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
419
        signer: {
420
          address_version: senderAddress[0],
421
          address_hash_bytes: senderAddress[1],
422
          address: event.stx_lock_event.locked_address,
423
        },
424
        nonce: '0',
425
        tx_fee: '0',
426
        key_encoding: TxPublicKeyEncoding.Compressed,
427
        signature: '0x',
428
      },
429
    },
430
    anchor_mode: AnchorModeID.Any,
431
    post_condition_mode: PostConditionModeID.Allow,
432
    post_conditions: [],
433
    post_conditions_buffer: '0x0100000000',
434
    payload: {
435
      type_id: TxPayloadTypeID.ContractCall,
436
      address: poxAddressString,
437
      address_version: poxAddress[0],
438
      address_hash_bytes: poxAddress[1],
439
      contract_name: contractName,
440
      function_name: 'stack-stx',
441
      function_args: clarityFnArgs,
442
      function_args_buffer: rawFnArgs,
443
    },
444
  };
445
  return tx;
4✔
446
}
447

448
/*
449
;; Delegate to `delegate-to` the ability to stack from a given address.
450
;;  This method _does not_ lock the funds, rather, it allows the delegate
451
;;  to issue the stacking lock.
452
;; The caller specifies:
453
;;   * amount-ustx: the total amount of ustx the delegate may be allowed to lock
454
;;   * until-burn-ht: an optional burn height at which this delegation expiration
455
;;   * pox-addr: an optional address to which any rewards *must* be sent
456
(define-public (delegate-stx (amount-ustx uint)
457
                             (delegate-to principal)
458
                             (until-burn-ht (optional uint))
459
                             (pox-addr (optional { version: (buff 1),
460
                                                   hashbytes: (buff 32) })))
461
*/
462
function createTransactionFromCoreBtcDelegateStxEvent(
463
  chainId: ChainID,
464
  contractEvent: SmartContractEvent,
465
  decodedEvent: DbPox2DelegateStxEvent,
466
  txResult: string,
467
  txId: string
468
): DecodedTxResult {
469
  const resultCv = decodeClarityValue<ClarityValueResponse>(txResult);
×
470
  if (resultCv.type_id !== ClarityTypeID.ResponseOk) {
×
471
    throw new Error(`Unexpected tx result Clarity type ID: ${resultCv.type_id}`);
×
472
  }
473

474
  const senderAddress = decodeStacksAddress(decodedEvent.stacker);
×
475
  const poxContractAddressString =
NEW
476
    getChainIDNetwork(chainId) === 'mainnet'
×
477
      ? 'SP000000000000000000002Q6VF78'
478
      : 'ST000000000000000000002AMW42H';
479
  const poxContractAddress = decodeStacksAddress(poxContractAddressString);
×
480
  const contractName = contractEvent.contract_event.contract_identifier?.split('.')?.[1] ?? 'pox';
×
481

482
  let poxAddr: NoneCV | OptionalCV<TupleCV> = noneCV();
×
483
  if (decodedEvent.pox_addr) {
×
484
    poxAddr = someCV(poxAddressToTuple(decodedEvent.pox_addr));
×
485
  }
486

487
  let untilBurnHeight: NoneCV | OptionalCV<UIntCV> = noneCV();
×
488
  if (decodedEvent.data.unlock_burn_height) {
×
489
    untilBurnHeight = someCV(uintCV(decodedEvent.data.unlock_burn_height));
×
490
  }
491

492
  const legacyClarityVals = [
×
493
    uintCV(decodedEvent.data.amount_ustx), // amount-ustx
494
    principalCV(decodedEvent.data.delegate_to), // delegate-to
495
    untilBurnHeight, // until-burn-ht
496
    poxAddr, // pox-addr
497
  ];
498
  const fnLenBuffer = Buffer.alloc(4);
×
499
  fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
×
500
  const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
×
501
  const rawFnArgs = bufferToHexPrefixString(
×
502
    Buffer.concat([fnLenBuffer, ...serializedClarityValues])
503
  );
504
  const clarityFnArgs = decodeClarityValueList(rawFnArgs);
×
505

506
  const tx: DecodedTxResult = {
×
507
    tx_id: txId,
508
    version:
509
      getChainIDNetwork(chainId) === 'mainnet'
×
510
        ? TransactionVersion.Mainnet
511
        : TransactionVersion.Testnet,
512
    chain_id: chainId,
513
    auth: {
514
      type_id: PostConditionAuthFlag.Standard,
515
      origin_condition: {
516
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
517
        signer: {
518
          address_version: senderAddress[0],
519
          address_hash_bytes: senderAddress[1],
520
          address: decodedEvent.stacker,
521
        },
522
        nonce: '0',
523
        tx_fee: '0',
524
        key_encoding: TxPublicKeyEncoding.Compressed,
525
        signature: '0x',
526
      },
527
    },
528
    anchor_mode: AnchorModeID.Any,
529
    post_condition_mode: PostConditionModeID.Allow,
530
    post_conditions: [],
531
    post_conditions_buffer: '0x0100000000',
532
    payload: {
533
      type_id: TxPayloadTypeID.ContractCall,
534
      address: poxContractAddressString,
535
      address_version: poxContractAddress[0],
536
      address_hash_bytes: poxContractAddress[1],
537
      contract_name: contractName,
538
      function_name: 'delegate-stx',
539
      function_args: clarityFnArgs,
540
      function_args_buffer: rawFnArgs,
541
    },
542
  };
543
  return tx;
×
544
}
545

546
function createTransactionFromCoreBtcTxEvent(
547
  chainId: ChainID,
548
  event: StxTransferEvent,
549
  txId: string
550
): DecodedTxResult {
551
  const recipientAddress = decodeStacksAddress(event.stx_transfer_event.recipient);
4✔
552
  const senderAddress = decodeStacksAddress(event.stx_transfer_event.sender);
4✔
553
  const tx: DecodedTxResult = {
4✔
554
    tx_id: txId,
555
    version:
556
      getChainIDNetwork(chainId) === 'mainnet'
4!
557
        ? TransactionVersion.Mainnet
558
        : TransactionVersion.Testnet,
559
    chain_id: chainId,
560
    auth: {
561
      type_id: PostConditionAuthFlag.Standard,
562
      origin_condition: {
563
        hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
564
        signer: {
565
          address_version: senderAddress[0],
566
          address_hash_bytes: senderAddress[1],
567
          address: event.stx_transfer_event.sender,
568
        },
569
        nonce: '0',
570
        tx_fee: '0',
571
        key_encoding: TxPublicKeyEncoding.Compressed,
572
        signature: '0x',
573
      },
574
    },
575
    anchor_mode: AnchorModeID.Any,
576
    post_condition_mode: PostConditionModeID.Allow,
577
    post_conditions: [],
578
    post_conditions_buffer: '0x0100000000',
579
    payload: {
580
      type_id: TxPayloadTypeID.TokenTransfer,
581
      recipient: {
582
        type_id: PrincipalTypeID.Standard,
583
        address_version: recipientAddress[0],
584
        address_hash_bytes: recipientAddress[1],
585
        address: event.stx_transfer_event.recipient,
586
      },
587
      amount: BigInt(event.stx_transfer_event.amount).toString(),
588
      memo_hex: '0x',
589
    },
590
  };
591
  return tx;
4✔
592
}
593

594
export interface CoreNodeMsgBlockData {
595
  block_hash: string;
596
  index_block_hash: string;
597
  parent_index_block_hash: string;
598
  parent_block_hash: string;
599
  parent_burn_block_timestamp: number;
600
  parent_burn_block_height: number;
601
  parent_burn_block_hash: string;
602
  block_height: number;
603
  burn_block_time: number;
604
  burn_block_height: number;
605
}
606

607
export function parseMicroblocksFromTxs(args: {
78✔
608
  parentIndexBlockHash: string;
609
  txs: CoreNodeTxMessage[];
610
  parentBurnBlock: {
611
    hash: string;
612
    time: number;
613
    height: number;
614
  };
615
}): DbMicroblockPartial[] {
616
  const microblockMap = new Map<string, DbMicroblockPartial>();
666✔
617
  args.txs.forEach(tx => {
666✔
618
    if (isTxWithMicroblockInfo(tx) && !microblockMap.has(tx.microblock_hash)) {
1,150✔
619
      const dbMbPartial: DbMicroblockPartial = {
131✔
620
        microblock_hash: tx.microblock_hash,
621
        microblock_sequence: tx.microblock_sequence,
622
        microblock_parent_hash: tx.microblock_parent_hash,
623
        parent_index_block_hash: args.parentIndexBlockHash,
624
        parent_burn_block_height: args.parentBurnBlock.height,
625
        parent_burn_block_hash: args.parentBurnBlock.hash,
626
        parent_burn_block_time: args.parentBurnBlock.time,
627
      };
628
      microblockMap.set(tx.microblock_hash, dbMbPartial);
131✔
629
    }
630
  });
631
  const dbMicroblocks = [...microblockMap.values()].sort(
666✔
632
    (a, b) => a.microblock_sequence - b.microblock_sequence
69✔
633
  );
634
  return dbMicroblocks;
666✔
635
}
636

637
export function parseMessageTransaction(
78✔
638
  chainId: ChainID,
639
  coreTx: CoreNodeTxMessage,
640
  blockData: CoreNodeMsgBlockData,
641
  allEvents: CoreNodeEvent[]
642
): CoreNodeParsedTxMessage | null {
643
  try {
1,158✔
644
    let rawTx: DecodedTxResult;
645
    let txSender: string;
646
    let sponsorAddress: string | undefined = undefined;
1,158✔
647
    if (coreTx.raw_tx === '0x00') {
1,158✔
648
      const events = allEvents.filter(event => event.txid === coreTx.txid);
90✔
649
      if (events.length === 0) {
8!
650
        logger.warn(`Could not find event for process BTC tx: ${JSON.stringify(coreTx)}`);
×
651
        return null;
×
652
      }
653
      const stxTransferEvent = events.find(
8✔
654
        (e): e is StxTransferEvent => e.type === CoreNodeEventType.StxTransferEvent
8✔
655
      );
656
      const stxLockEvent = events.find(
8✔
657
        (e): e is StxLockEvent => e.type === CoreNodeEventType.StxLockEvent
8✔
658
      );
659
      const nftMintEvent = events.find(
8✔
660
        (e): e is NftMintEvent => e.type === CoreNodeEventType.NftMintEvent
8✔
661
      );
662
      const ftMintEvent = events.find(
8✔
663
        (e): e is FtMintEvent => e.type === CoreNodeEventType.FtMintEvent
8✔
664
      );
665
      const stxMintEvent = events.find(
8✔
666
        (e): e is StxMintEvent => e.type === CoreNodeEventType.StxMintEvent
8✔
667
      );
668

669
      const pox2Event = events
8✔
670
        .filter(
671
          (e): e is SmartContractEvent =>
672
            e.type === CoreNodeEventType.ContractEvent &&
8!
673
            e.contract_event.topic === 'print' &&
674
            (e.contract_event.contract_identifier === Pox2ContractIdentifer.mainnet ||
675
              e.contract_event.contract_identifier === Pox2ContractIdentifer.testnet)
676
        )
677
        .map(e => {
NEW
678
          const network = getChainIDNetwork(chainId);
×
679
          const decodedEvent = decodePox2PrintEvent(e.contract_event.raw_value, network);
×
680
          if (decodedEvent) {
×
681
            return {
×
682
              contractEvent: e,
683
              decodedEvent,
684
            };
685
          }
686
        })
687
        .find(e => !!e);
×
688

689
      const subnetEvents = events.filter(
8✔
690
        (e): e is SmartContractEvent =>
691
          e.type === CoreNodeEventType.ContractEvent &&
8!
692
          e.contract_event.topic === 'print' &&
693
          (e.contract_event.contract_identifier === SubnetContractIdentifer.mainnet ||
694
            e.contract_event.contract_identifier === SubnetContractIdentifer.testnet)
695
      );
696

697
      if (stxTransferEvent) {
8✔
698
        rawTx = createTransactionFromCoreBtcTxEvent(chainId, stxTransferEvent, coreTx.txid);
4✔
699
        txSender = stxTransferEvent.stx_transfer_event.sender;
4✔
700
      } else if (stxLockEvent) {
4!
701
        const stxStacksPox2Event =
702
          pox2Event?.decodedEvent.name === Pox2EventName.StackStx
4!
703
            ? pox2Event.decodedEvent
704
            : undefined;
705
        rawTx = createTransactionFromCoreBtcStxLockEvent(
4✔
706
          chainId,
707
          stxLockEvent,
708
          blockData.burn_block_height,
709
          coreTx.raw_result,
710
          coreTx.txid,
711
          stxStacksPox2Event
712
        );
713
        txSender = stxLockEvent.stx_lock_event.locked_address;
4✔
714
      } else if (pox2Event && pox2Event.decodedEvent.name === Pox2EventName.DelegateStx) {
×
715
        rawTx = createTransactionFromCoreBtcDelegateStxEvent(
×
716
          chainId,
717
          pox2Event.contractEvent,
718
          pox2Event.decodedEvent,
719
          coreTx.raw_result,
720
          coreTx.txid
721
        );
722
        txSender = pox2Event.decodedEvent.stacker;
×
723
      } else if (nftMintEvent) {
×
724
        rawTx = createSubnetTransactionFromL1NftDeposit(chainId, nftMintEvent, coreTx.txid);
×
725
        txSender = nftMintEvent.nft_mint_event.recipient;
×
726
      } else if (ftMintEvent) {
×
727
        rawTx = createSubnetTransactionFromL1FtDeposit(chainId, ftMintEvent, coreTx.txid);
×
728
        txSender = ftMintEvent.ft_mint_event.recipient;
×
729
      } else if (stxMintEvent) {
×
730
        rawTx = createSubnetTransactionFromL1StxDeposit(chainId, stxMintEvent, coreTx.txid);
×
731
        txSender = getTxSenderAddress(rawTx);
×
732
      } else if (
×
733
        subnetEvents.length > 0 &&
×
734
        coreTx.burnchain_op &&
735
        coreTx.burnchain_op.register_asset
736
      ) {
737
        rawTx = createSubnetTransactionFromL1RegisterAsset(
×
738
          chainId,
739
          coreTx.burnchain_op,
740
          subnetEvents[0],
741
          coreTx.txid
742
        );
743
        txSender = getTxSenderAddress(rawTx);
×
744
      } else {
745
        logger.error(
×
746
          `BTC transaction found, but no STX transfer event available to recreate transaction. TX: ${JSON.stringify(
747
            coreTx
748
          )}, event: ${JSON.stringify(events)}`
749
        );
750
        throw new Error('Unable to generate transaction from BTC tx');
×
751
      }
752
    } else {
753
      rawTx = decodeTransaction(coreTx.raw_tx.substring(2));
1,150✔
754
      txSender = getTxSenderAddress(rawTx);
1,150✔
755
      sponsorAddress = getTxSponsorAddress(rawTx);
1,150✔
756
    }
757
    const parsedTx: CoreNodeParsedTxMessage = {
1,158✔
758
      core_tx: coreTx,
759
      nonce: Number(rawTx.auth.origin_condition.nonce),
760
      raw_tx: coreTx.raw_tx,
761
      parsed_tx: rawTx,
762
      block_hash: blockData.block_hash,
763
      index_block_hash: blockData.index_block_hash,
764
      parent_index_block_hash: blockData.parent_index_block_hash,
765
      parent_block_hash: blockData.parent_block_hash,
766
      parent_burn_block_hash: blockData.parent_burn_block_hash,
767
      parent_burn_block_time: blockData.parent_burn_block_timestamp,
768
      block_height: blockData.block_height,
769
      burn_block_time: blockData.burn_block_time,
770
      microblock_sequence: coreTx.microblock_sequence ?? I32_MAX,
2,159✔
771
      microblock_hash: coreTx.microblock_hash ?? '',
2,159✔
772
      sender_address: txSender,
773
      sponsor_address: sponsorAddress,
774
    };
775
    const payload = rawTx.payload;
1,158✔
776
    switch (payload.type_id) {
1,158!
777
      case TxPayloadTypeID.Coinbase: {
778
        break;
633✔
779
      }
780
      case TxPayloadTypeID.CoinbaseToAltRecipient: {
781
        if (payload.recipient.type_id === PrincipalTypeID.Standard) {
×
782
          logger.debug(
×
783
            `Coinbase to alt recipient, standard principal: ${payload.recipient.address}`
784
          );
785
        } else {
786
          logger.debug(
×
787
            `Coinbase to alt recipient, contract principal: ${payload.recipient.address}.${payload.recipient.contract_name}`
788
          );
789
        }
790
        break;
×
791
      }
792
      case TxPayloadTypeID.SmartContract: {
793
        logger.debug(
136✔
794
          `Smart contract deployed: ${parsedTx.sender_address}.${payload.contract_name}`
795
        );
796
        break;
136✔
797
      }
798
      case TxPayloadTypeID.ContractCall: {
799
        logger.debug(
128✔
800
          `Contract call: ${payload.address}.${payload.contract_name}.${payload.function_name}`
801
        );
802
        break;
128✔
803
      }
804
      case TxPayloadTypeID.TokenTransfer: {
805
        let recipientPrincipal = payload.recipient.address;
261✔
806
        if (payload.recipient.type_id === PrincipalTypeID.Contract) {
261!
807
          recipientPrincipal += '.' + payload.recipient.contract_name;
×
808
        }
809
        logger.debug(
261✔
810
          `Token transfer: ${payload.amount} from ${parsedTx.sender_address} to ${recipientPrincipal}`
811
        );
812
        break;
261✔
813
      }
814
      case TxPayloadTypeID.PoisonMicroblock: {
815
        logger.debug(
×
816
          `Poison microblock: header1 ${payload.microblock_header_1}), header2: ${payload.microblock_header_2}`
817
        );
818
        break;
×
819
      }
820
      case TxPayloadTypeID.VersionedSmartContract: {
821
        logger.debug(
×
822
          `Versioned smart contract deployed: Clarity version ${payload.clarity_version}, ${parsedTx.sender_address}.${payload.contract_name}`
823
        );
824
        break;
×
825
      }
826
      default: {
827
        throw new NotImplementedError(
×
828
          `extracting data for tx type: ${getEnumDescription(
829
            TxPayloadTypeID,
830
            rawTx.payload.type_id
831
          )}`
832
        );
833
      }
834
    }
835
    return parsedTx;
1,158✔
836
  } catch (error) {
837
    logger.error(error, `error parsing message transaction ${JSON.stringify(coreTx)}`);
×
838
    throw error;
×
839
  }
840
}
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