import { ContractCallTransaction , RosettaAccountIdentifier , RosettaCurrency , RosettaOperation , RosettaOptions , } from '@stacks/stacks-blockchain-api-types' ; import { addressToString , AuthType , BufferCV , BufferReader , ChainID , ClarityType , cvToString , deserializeCV , deserializeTransaction , emptyMessageSignature , isSingleSig , makeSigHashPreSign , MessageSignature , parseRecoverableSignature , PayloadType , SomeCV , StacksTransaction , txidFromData , } from '@stacks/transactions' ; import { StacksMainnet , StacksTestnet } from '@stacks/network' ; import { ec as EC } from 'elliptic' ; import * as btc from 'bitcoinjs-lib' ; import * as c32check from 'c32check' ; import { getAssetEventTypeString , getEventTypeString , getTxFromDataStore , getTxStatus , getTxTypeString , } from './api/controllers/db-controller' ; import { PoxContractIdentifier , RosettaConstants , RosettaNetworks , RosettaOperationType , } from './api/rosetta-constants' ; import { BaseTx , DataStore , DbAssetEventTypeId , DbEvent , DbEventTypeId , DbMempoolTx , DbMinerReward , DbStxEvent , DbStxLockEvent , DbTx , DbTxStatus , DbTxTypeId , StxUnlockEvent , } from './datastore/common' ; import { getTxSenderAddress , getTxSponsorAddress } from './event-stream/reader' ; import { unwrapOptional , bufferToHexPrefixString , hexToBuffer } from './helpers' ; import { readTransaction , TransactionPayloadTypeID } from './p2p/tx' ; import { getCoreNodeEndpoint } from './core-rpc/client' ; import { serializeCV , TupleCV } from '@stacks/transactions' ; import { getBTCAddress , poxAddressToBtcAddress } from '@stacks/stacking' ; enum CoinAction { CoinSpent = 'coin_spent' , CoinCreated = 'coin_created' } type RosettaStakeContractArgs = { amount_ustx : string ; pox_addr : string ; stacker_address : string ; start_burn_height : string ; unlock_burn_height : string ; lock_period : string ; } ; type RosettaDelegateContractArgs = { amount_ustx : string ; pox_addr : string ; delegate_to : string ; until_burn_height : string ; result : string ; } ; type RosettaRevokeDelegateContractArgs = { result : string ; } ; export function parseTransactionMemo ( tx ) { if ( tx . token_transfer_memo ) { const memoBuffer = tx . token_transfer_memo ; if ( memoBuffer . every ( byte => byte === 0 ) ) { return null ; } const memoString = tx . token_transfer_memo . toString ( ) ; return memoString . replace ( /\0.*$/g , '' ) ; } return null ; } export async function getOperations ( tx , db , minerRewards ? , events ? , stxUnlockEvents ? ) { const operations = [ ] ; const txType = getTxTypeString ( tx . type_id ) ; switch ( txType ) { case 'token_transfer' : operations . push ( makeFeeOperation ( tx ) ) ; operations . push ( makeSenderOperation ( tx , operations . length ) ) ; operations . push ( makeReceiverOperation ( tx , operations . length ) ) ; break ; case 'contract_call' : operations . push ( makeFeeOperation ( tx ) ) ; operations . push ( await makeCallContractOperation ( tx , db , operations . length ) ) ; break ; case 'smart_contract' : operations . push ( makeFeeOperation ( tx ) ) ; operations . push ( makeDeployContractOperation ( tx , operations . length ) ) ; break ; case 'coinbase' : operations . push ( makeCoinbaseOperation ( tx , 0 ) ) ; if ( minerRewards !== undefined ) { getMinerOperations ( minerRewards , operations ) ; } if ( stxUnlockEvents && stxUnlockEvents ?. length > 0 ) { processUnlockingEvents ( stxUnlockEvents , operations ) ; } break ; case 'poison_microblock' : operations . push ( makePoisonMicroblockOperation ( tx , 0 ) ) ; break ; default : throw new Error ( `Unexpected tx type: ${ JSON . stringify ( txType ) }` ) ; } if ( events !== undefined ) { processEvents ( events , tx , operations ) ; } return operations ; } export function processUnlockingEvents ( events , operations ) { events . forEach ( event => { operations . push ( makeStakeUnlockOperation ( event , operations . length ) ) ; } ) ; } function processEvents ( events , baseTx , operations ) { events . forEach ( event => { const txEventType = event . event_type ; switch ( txEventType ) { case DbEventTypeId . StxAsset : const stxAssetEvent = event ; const txAssetEventType = stxAssetEvent . asset_event_type_id ; switch ( txAssetEventType ) { case DbAssetEventTypeId . Transfer : if ( baseTx . type_id == DbTxTypeId . TokenTransfer ) { break ; } const tx = baseTx ; tx . sender_address = unwrapOptional ( stxAssetEvent . sender , ( ) => 'Unexpected nullish sender' ) ; tx . token_transfer_recipient_address = unwrapOptional ( stxAssetEvent . recipient , ( ) => 'Unexpected nullish recipient' ) ; tx . token_transfer_amount = unwrapOptional ( stxAssetEvent . amount , ( ) => 'Unexpected nullish amount' ) ; operations . push ( makeSenderOperation ( tx , operations . length ) ) ; operations . push ( makeReceiverOperation ( tx , operations . length ) ) ; break ; case DbAssetEventTypeId . Burn : operations . push ( makeBurnOperation ( stxAssetEvent , baseTx , operations . length ) ) ; break ; case DbAssetEventTypeId . Mint : operations . push ( makeMintOperation ( stxAssetEvent , baseTx , operations . length ) ) ; break ; default : throw new Error ( `Unexpected StxAsset event: ${ txAssetEventType }` ) ; } break ; case DbEventTypeId . StxLock : const stxLockEvent = event ; operations . push ( makeStakeLockOperation ( stxLockEvent , baseTx , operations . length ) ) ; break ; case DbEventTypeId . NonFungibleTokenAsset : break ; case DbEventTypeId . FungibleTokenAsset : break ; case DbEventTypeId . SmartContractLog : break ; default : throw new Error ( `Unexpected DbEventTypeId: ${ txEventType }` ) ; } } ) ; } function makeStakeLockOperation ( tx , baseTx , index ) { const stake_metadata = { } ; stake_metadata . locked = tx . locked_amount . toString ( ) ; stake_metadata . unlock_height = tx . unlock_height . toString ( ) ; const lock = { operation_identifier : { index : index } , type : getEventTypeString ( tx . event_type ) , status : getTxStatus ( baseTx . status ) , account : { address : unwrapOptional ( tx . locked_address , ( ) => 'Unexpected nullish locked_address' ) , } , amount : { value : ( 0n - unwrapOptional ( tx . locked_amount . valueOf ( ) , ( ) => 'Unexpected nullish locked_amount' ) ) . toString ( 10 ) , currency : getStxCurrencyMetadata ( ) , } , metadata : stake_metadata , } ; return lock ; } function makeStakeUnlockOperation ( tx , index ) { const unlock_metadata = { } ; unlock_metadata . tx_id = tx . tx_id ; const unlock = { operation_identifier : { index : index } , type : RosettaOperationType . StxUnlock , status : 'success' , account : { address : unwrapOptional ( tx . stacker_address , ( ) => 'Unexpected nullish address' ) , } , amount : { value : unwrapOptional ( tx . unlocked_amount , ( ) => 'Unexpected nullish amount' ) , currency : getStxCurrencyMetadata ( ) , } , metadata : unlock_metadata , } ; return unlock ; } function getMinerOperations ( minerRewards , operations ) { minerRewards . forEach ( reward => { operations . push ( makeMinerRewardOperation ( reward , operations . length ) ) ; } ) ; } function makeMinerRewardOperation ( reward , index ) { const value = reward . coinbase_amount + reward . tx_fees_anchored + reward . tx_fees_streamed_confirmed + reward . tx_fees_streamed_produced ; const minerRewardOp = { operation_identifier : { index : index } , status : getTxStatus ( DbTxStatus . Success ) , type : RosettaOperationType . MinerReward , account : { address : unwrapOptional ( reward . recipient , ( ) => 'Unexpected nullish recipient' ) , } , amount : { value : unwrapOptional ( value , ( ) => 'Unexpected nullish coinbase_amount' ) . toString ( 10 ) , currency : getStxCurrencyMetadata ( ) , } , } ; return minerRewardOp ; } function makeFeeOperation ( tx ) { const fee = { operation_identifier : { index : 0 } , type : RosettaOperationType . Fee , status : getTxStatus ( tx . status ) , account : { address : tx . sender_address } , amount : { value : ( 0n - unwrapOptional ( tx . fee_rate , ( ) => 'Unexpected nullish amount' ) ) . toString ( 10 ) , currency : getStxCurrencyMetadata ( ) , } , } ; return fee ; } function makeBurnOperation ( tx , baseTx , index ) { const burn = { operation_identifier : { index : index } , type : getAssetEventTypeString ( tx . asset_event_type_id ) , status : getTxStatus ( baseTx . status ) , account : { address : unwrapOptional ( baseTx . sender_address , ( ) => 'Unexpected nullish sender_address' ) , } , amount : { value : ( 0n - unwrapOptional ( tx . amount , ( ) => 'Unexpected nullish amount' ) ) . toString ( 10 ) , currency : getStxCurrencyMetadata ( ) , } , } ; return burn ; } function makeMintOperation ( tx , baseTx , index ) { const mint = { operation_identifier : { index : index } , type : getAssetEventTypeString ( tx . asset_event_type_id ) , status : getTxStatus ( baseTx . status ) , account : { address : unwrapOptional ( tx . recipient , ( ) => 'Unexpected nullish sender_address' ) , } , amount : { value : unwrapOptional ( tx . amount , ( ) => 'Unexpected nullish token_transfer_amount' ) . toString ( 10 ) , currency : getStxCurrencyMetadata ( ) , } , } ; return mint ; } function makeSenderOperation ( tx , index ) { const sender = { operation_identifier : { index : index } , type : 'token_transfer' , status : getTxStatus ( tx . status ) , account : { address : unwrapOptional ( tx . sender_address , ( ) => 'Unexpected nullish sender_address' ) , } , amount : { value : ( 0n - unwrapOptional ( tx . token_transfer_amount , ( ) => 'Unexpected nullish token_transfer_amount' ) ) . toString ( 10 ) , currency : getStxCurrencyMetadata ( ) , } , coin_change : { coin_action : CoinAction . CoinSpent , coin_identifier : { identifier : tx . tx_id + ':' + index } , } , } ; return sender ; } function makeReceiverOperation ( tx , index ) { const receiver = { operation_identifier : { index : index } , related_operations : [ { index : index - 1 } ] , type : 'token_transfer' , status : getTxStatus ( tx . status ) , account : { address : unwrapOptional ( tx . token_transfer_recipient_address , ( ) => 'Unexpected nullish token_transfer_recipient_address' ) , } , amount : { value : unwrapOptional ( tx . token_transfer_amount , ( ) => 'Unexpected nullish token_transfer_amount' ) . toString ( 10 ) , currency : getStxCurrencyMetadata ( ) , } , coin_change : { coin_action : CoinAction . CoinCreated , coin_identifier : { identifier : tx . tx_id + ':' + index } , } , } ; return receiver ; } function makeDeployContractOperation ( tx , index ) { const deployer = { operation_identifier : { index : index } , type : getTxTypeString ( tx . type_id ) , status : getTxStatus ( tx . status ) , account : { address : unwrapOptional ( tx . sender_address , ( ) => 'Unexpected nullish sender_address' ) , } , } ; return deployer ; } async function makeCallContractOperation ( tx , db , index ) { const contractCallOp = { operation_identifier : { index : index } , type : getTxTypeString ( tx . type_id ) , status : getTxStatus ( tx . status ) , account : { address : unwrapOptional ( tx . sender_address , ( ) => 'Unexpected nullish sender_address' ) , } , } ; const parsed_tx = await getTxFromDataStore ( db , { txId : tx . tx_id , includeUnanchored : false } ) ; if ( ! parsed_tx . found ) { throw new Error ( 'unexpected tx not found -- could not get contract from data store' ) ; } const stackContractCall = parsed_tx . result as ContractCallTransaction ; contractCallOp . status = stackContractCall . tx_status ; switch ( tx . contract_call_function_name ) { case 'stack-stx' : case 'delegate-stx' : case 'revoke-delegate-stx' : if ( stackContractCall . contract_call . contract_id == PoxContractIdentifier . testnet || stackContractCall . contract_call . contract_id == PoxContractIdentifier . mainnet ) { parseStackingContractCall ( contractCallOp , stackContractCall ) ; } else { parseGenericContractCall ( contractCallOp , tx ) ; } break ; default : parseGenericContractCall ( contractCallOp , tx ) ; } return contractCallOp ; } function makeCoinbaseOperation ( tx , index ) { const sender = { operation_identifier : { index : index } , type : getTxTypeString ( tx . type_id ) , status : getTxStatus ( tx . status ) , account : { address : unwrapOptional ( tx . sender_address , ( ) => 'Unexpected nullish sender_address' ) , } , } ; return sender ; } function makePoisonMicroblockOperation ( tx , index ) { const sender = { operation_identifier : { index : index } , type : getTxTypeString ( tx . type_id ) , status : getTxStatus ( tx . status ) , account : { address : unwrapOptional ( tx . sender_address , ( ) => 'Unexpected nullish sender_address' ) , } , } ; return sender ; } export function publicKeyToBitcoinAddress ( publicKey , network ) { const publicKeyBuffer = Buffer . from ( publicKey , 'hex' ) ; let btcNetwork ; if ( network === RosettaNetworks . mainnet ) { btcNetwork = btc . networks . bitcoin ; } else if ( network === RosettaNetworks . testnet ) { btcNetwork = btc . networks . testnet ; } else { throw new Error ( `[publicKeyToBitcoinAddress] Unexpected network '${ network }'` ) ; } const address = btc . payments . p2pkh ( { pubkey : publicKeyBuffer , network : btcNetwork , } ) ; return address . address ; } export function bitcoinAddressToSTXAddress ( btcAddress ) { return c32check . b58ToC32 ( btcAddress ) ; } export function getOptionsFromOperations ( operations ) { const options = { } ; for ( const operation of operations ) { switch ( operation . type ) { case RosettaOperationType . Fee : options . fee = operation . amount ?. value ; break ; case RosettaOperationType . TokenTransfer : if ( operation . amount ) { if ( BigInt ( operation . amount . value ) < 0 ) { options . sender_address = operation . account ?. address ; options . type = operation . type ; } else { options . token_transfer_recipient_address = operation ?. account ?. address ; options . amount = operation ?. amount ?. value ; options . symbol = operation ?. amount ?. currency . symbol ; options . decimals = operation ?. amount ?. currency . decimals ; } } break ; case RosettaOperationType . StackStx : if ( operation . amount && BigInt ( operation . amount . value ) > 0 ) { return null ; } if ( ! operation . metadata || typeof operation . metadata . number_of_cycles !== 'number' ) { return null ; } options . sender_address = operation . account ?. address ; options . type = operation . type ; options . number_of_cycles = operation . metadata . number_of_cycles ; options . amount = operation . amount ?. value . replace ( '-' , '' ) ; options . symbol = operation . amount ?. currency . symbol ; options . decimals = operation . amount ?. currency . decimals ; options . pox_addr = operation . metadata ?. pox_addr as string ; break ; case RosettaOperationType . DelegateStx : if ( operation . amount && BigInt ( operation . amount . value ) > 0 ) { return null ; } if ( ! operation . metadata || typeof operation . metadata . delegate_to !== 'string' ) { return null ; } options . sender_address = operation . account ?. address ; options . type = operation . type ; options . delegate_to = operation . metadata ?. delegate_to ; options . amount = operation . amount ?. value . replace ( '-' , '' ) ; options . symbol = operation . amount ?. currency . symbol ; options . decimals = operation . amount ?. currency . decimals ; options . pox_addr = operation . metadata ?. pox_addr as string ; break ; default : return null ; } } return options ; } function parseStackingContractCall ( contractCallOp , stackContractCall ) { switch ( stackContractCall . contract_call . function_name ) { case 'stack-stx' : { contractCallOp . type = RosettaOperationType . StackStx ; contractCallOp . metadata = { ... parseStackStxArgs ( stackContractCall ) , } ; } break ; case 'delegate-stx' : { contractCallOp . type = RosettaOperationType . DelegateStx ; contractCallOp . metadata = { ... parseDelegateStxArgs ( stackContractCall ) , } ; } break ; case 'revoke-delegate-stx' : { contractCallOp . type = RosettaOperationType . RevokeDelegateStx ; contractCallOp . metadata = { ... parseRevokeDelegateStxArgs ( stackContractCall ) , } ; } break ; } } function parseGenericContractCall ( operation , tx ) { operation . metadata = { contract_call_function_name : tx . contract_call_function_name , contract_call_function_args : tx . contract_call_function_args ? bufferToHexPrefixString ( unwrapOptional ( tx . contract_call_function_args , ( ) => '' ) ) : '' , } ; } function parseRevokeDelegateStxArgs ( contract ) { const args = { } as RosettaRevokeDelegateContractArgs ; if ( contract . tx_result == undefined ) { throw new Error ( `Could not find field tx_result in contract call` ) ; } const result = deserializeCV ( hexToBuffer ( contract . tx_result . hex ) ) ; args . result = result . value . type === ClarityType . BoolTrue ? 'true' : 'false' ; return args ; } function parseDelegateStxArgs ( contract ) { const args = { } as RosettaDelegateContractArgs ; if ( contract . tx_result == undefined ) { throw new Error ( `Could not find field tx_result in contract call` ) ; } if ( contract . contract_call . function_args == undefined ) { throw new Error ( `Could not find field function_args in contract call` ) ; } let argName = 'amount-ustx' ; const amount_ustx = contract . contract_call . function_args ?. find ( a => a . name === argName ) ; if ( ! amount_ustx ) { throw new Error ( `Could not find field name ${ argName } in contract call` ) ; } args . amount_ustx = amount_ustx . repr . replace ( /[^\d.-]/g , '' ) ; argName = 'delegate-to' ; const delegate_to = contract . contract_call . function_args ?. find ( a => a . name === argName ) ; if ( ! delegate_to ) { throw new Error ( `Could not find field name ${ argName } in contract call` ) ; } args . delegate_to = delegate_to . repr ; argName = 'until-burn-ht' ; const until_burn = contract . contract_call . function_args . find ( a => a . name === argName ) ; if ( ! until_burn ) { throw new Error ( `Could not find field name ${ argName } in contract call` ) ; } args . until_burn_height = until_burn . repr !== 'none' ? until_burn . repr . replace ( /[^\d.-]/g , '' ) : 'none' ; argName = 'pox-addr' ; const pox_address_raw = contract . contract_call . function_args ?. find ( a => a . name === argName ) ; if ( pox_address_raw == undefined || pox_address_raw . repr == 'none' ) { args . pox_addr = 'none' ; } else { const pox_address_cv = deserializeCV ( hexToBuffer ( pox_address_raw . hex ) ) ; if ( pox_address_cv . type === ClarityType . OptionalSome ) { if ( pox_address_cv . value . type === ClarityType . Tuple ) args . pox_addr = bufferToHexPrefixString ( serializeCV ( pox_address_cv . value ) ) ; } } const result = deserializeCV ( hexToBuffer ( contract . tx_result . hex ) ) ; args . result = result . value . type === ClarityType . BoolTrue ? 'true' : 'false' ; return args ; } function parseStackStxArgs ( contract ) { const args = { } as RosettaStakeContractArgs ; if ( contract . tx_result == undefined ) { throw new Error ( `Could not find field tx_result in contract call` ) ; } if ( contract . contract_call . function_args == undefined ) { throw new Error ( `Could not find field function_args in contract call` ) ; } let argName = 'lock-period' ; const lock_period = contract . contract_call . function_args . find ( a => a . name === argName ) ; if ( ! lock_period ) { throw new Error ( `Could not find field name ${ argName } in contract call` ) ; } args . lock_period = lock_period . repr . replace ( /[^\d.-]/g , '' ) ; argName = 'amount-ustx' ; const amount_ustx = contract . contract_call . function_args ?. find ( a => a . name === argName ) ; if ( ! amount_ustx ) { throw new Error ( `Could not find field name ${ argName } in contract call` ) ; } args . amount_ustx = amount_ustx . repr . replace ( /[^\d.-]/g , '' ) ; argName = 'start-burn-ht' ; const start_burn_height = contract . contract_call . function_args ?. find ( a => a . name === argName ) ; if ( ! start_burn_height ) { throw new Error ( `Could not find field name ${ argName } in contract call` ) ; } args . start_burn_height = start_burn_height . repr . replace ( /[^\d.-]/g , '' ) ; const temp = deserializeCV ( hexToBuffer ( contract . tx_result . hex ) ) ; const resultTuple = temp . value as TupleCV ; if ( resultTuple . data !== undefined ) { args . unlock_burn_height = cvToString ( resultTuple . data [ 'unlock-burn-height' ] ) . replace ( /[^\d.-]/g , '' ) ; args . stacker_address = cvToString ( resultTuple . data [ 'stacker' ] ) ; } argName = 'pox-addr' ; const pox_address_raw = contract . contract_call . function_args ?. find ( a => a . name === argName ) ; if ( ! pox_address_raw ) { throw new Error ( `Could not find field name ${ argName } in contract call` ) ; } const pox_address_cv = deserializeCV ( hexToBuffer ( pox_address_raw . hex ) ) ; if ( pox_address_cv . type === ClarityType . Tuple ) { const chainID = parseInt ( process . env [ 'STACKS_CHAIN_ID' ] as string ) ; try { args . pox_addr = poxAddressToBtcAddress ( pox_address_cv , chainID == ChainID . Mainnet ? 'mainnet' : 'testnet' ) ; } catch ( error ) { console . log ( error ) ; args . pox_addr = 'Invalid' ; } } return args ; } export function isSymbolSupported ( operations ) { for ( const operation of operations ) { if ( operation . amount ?. currency . symbol !== RosettaConstants . symbol ) { return false ; } } return true ; } export function isDecimalsSupported ( operations ) { for ( const operation of operations ) { if ( operation . amount ?. currency . decimals !== RosettaConstants . decimals ) { return false ; } } return true ; } function getStxCurrencyMetadata ( ) { const currency = { decimals : RosettaConstants . decimals , symbol : RosettaConstants . symbol , } ; return currency ; } export function rawTxToStacksTransaction ( raw_tx ) { const buffer = hexToBuffer ( raw_tx ) ; const transaction = deserializeTransaction ( BufferReader . fromBuffer ( buffer ) ) ; return transaction ; } export function isSignedTransaction ( transaction ) { if ( ! transaction . auth . spendingCondition ) { return false ; } if ( isSingleSig ( transaction . auth . spendingCondition ) ) { /**Single signature Transaction has an empty signature, so the transaction is not signed */ if ( ! transaction . auth . spendingCondition . signature . data || emptyMessageSignature ( ) . data === transaction . auth . spendingCondition . signature . data ) { return false ; } } else { /**Multi-signature transaction does not have signature fields thus the transaction not signed */ if ( transaction . auth . spendingCondition . fields . length === 0 ) { return false ; } } return true ; } export function rawTxToBaseTx ( raw_tx ) { const txBuffer = Buffer . from ( raw_tx . substring ( 2 ) , 'hex' ) ; const txId = '0x' + txidFromData ( txBuffer ) ; const bufferReader = BufferReader . fromBuffer ( txBuffer ) ; const transaction = readTransaction ( bufferReader ) ; const txSender = getTxSenderAddress ( transaction ) ; const sponsorAddress = getTxSponsorAddress ( transaction ) ; const payload = transaction . payload ; const fee = transaction . auth . originCondition . feeRate ; const amount = payload . amount ; transaction . auth . originCondition ; const recipientAddr = payload . recipient && payload . recipient . address ? addressToString ( { type : payload . recipient . typeId , version : payload . recipient . address . version , hash160 : payload . recipient . address . bytes . toString ( 'hex' ) , } ) : '' ; const sponsored = sponsorAddress ? true : false ; let transactionType = DbTxTypeId . TokenTransfer ; switch ( transaction . payload . typeId ) { case TransactionPayloadTypeID . TokenTransfer : transactionType = DbTxTypeId . TokenTransfer ; break ; case TransactionPayloadTypeID . SmartContract : transactionType = DbTxTypeId . SmartContract ; break ; case TransactionPayloadTypeID . ContractCall : transactionType = DbTxTypeId . ContractCall ; break ; case TransactionPayloadTypeID . Coinbase : transactionType = DbTxTypeId . Coinbase ; break ; case TransactionPayloadTypeID . PoisonMicroblock : transactionType = DbTxTypeId . PoisonMicroblock ; break ; } const dbtx = { token_transfer_recipient_address : recipientAddr , tx_id : txId , anchor_mode : 3 , type_id : transactionType , status : '' as any , nonce : Number ( transaction . auth . originCondition . nonce ) , fee_rate : fee , sender_address : txSender , token_transfer_amount : amount , sponsored : sponsored , sponsor_address : sponsorAddress , } ; const txPayload = transaction . payload ; if ( txPayload . typeId === TransactionPayloadTypeID . TokenTransfer ) { dbtx . token_transfer_memo = txPayload . memo ; } return dbtx ; } export function getSigners ( transaction ) { let address ; if ( transaction . payload . payloadType == PayloadType . TokenTransfer ) { address = transaction . payload . recipient . address ; } else if ( transaction . payload . payloadType == PayloadType . ContractCall ) { address = transaction . payload . contractAddress ; } else { return ; } const { type , version } = address ; const account_identifier_signers = [ ] ; if ( transaction . auth . authType == AuthType . Standard ) { if ( transaction . auth . spendingCondition ) { const signer = { address : addressToString ( { version : version , hash160 : transaction . auth . spendingCondition . signer , type : type , } ) , } ; account_identifier_signers . push ( signer ) ; } } else if ( transaction . auth . authType == AuthType . Sponsored ) { if ( transaction . auth . spendingCondition ) { const signer = { address : addressToString ( { version : version , hash160 : transaction . auth . spendingCondition . signer , type : type , } ) , } ; account_identifier_signers . push ( signer ) ; } if ( transaction . auth . sponsorSpendingCondition ) { const sponsored = { address : addressToString ( { version : version , hash160 : transaction . auth . sponsorSpendingCondition . signer , type : type , } ) , } ; account_identifier_signers . push ( sponsored ) ; } } return account_identifier_signers ; } export function getStacksTestnetNetwork ( ) { const stacksNetwork = new StacksTestnet ( ) ; stacksNetwork . coreApiUrl = `http://${ getCoreNodeEndpoint ( ) }` ; return stacksNetwork ; } function getStacksMainnetNetwork ( ) { const stacksNetwork = new StacksMainnet ( ) ; stacksNetwork . coreApiUrl = `http://${ getCoreNodeEndpoint ( ) }` ; return stacksNetwork ; } export function getStacksNetwork ( ) { const configuredChainID = parseInt ( process . env [ 'STACKS_CHAIN_ID' ] as string ) ; if ( ChainID . Mainnet == configuredChainID ) { return getStacksMainnetNetwork ( ) ; } return getStacksTestnetNetwork ( ) ; } export function verifySignature ( message , publicAddress , signature ) { const { r , s } = parseRecoverableSignature ( signature . data ) ; try { const ec = new EC ( 'secp256k1' ) ; const publicKeyPair = ec . keyFromPublic ( publicAddress , 'hex' ) ; const isVerified = publicKeyPair . verify ( message , { r , s } ) ; return isVerified ; } catch ( error ) { return false ; } } export function makePresignHash ( transaction ) { if ( ! transaction . auth . authType || ! transaction . auth . spendingCondition ?. nonce === undefined ) { return undefined ; } return makeSigHashPreSign ( transaction . verifyBegin ( ) , transaction . auth . authType , transaction . auth . spendingCondition ?. fee , transaction . auth . spendingCondition ?. nonce ) ; } export function getSignature ( transaction ) { if ( transaction . auth . spendingCondition && isSingleSig ( transaction . auth . spendingCondition ) ) { return transaction . auth . spendingCondition . signature ; } return undefined ; }