diff --git a/changelog.md b/changelog.md index 39fff5b0d5..c31c3fe3f4 100644 --- a/changelog.md +++ b/changelog.md @@ -43,8 +43,9 @@ * [2518](https://github.com/zeta-chain/node/pull/2518) - add support for Solana address in zetacore * [2483](https://github.com/zeta-chain/node/pull/2483) - add priorityFee (gasTipCap) gas to the state * [2567](https://github.com/zeta-chain/node/pull/2567) - add sign latency metric to zetaclient (zetaclient_sign_latency) -* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envolop parsing +* [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envelop parsing * [2560](https://github.com/zeta-chain/node/pull/2560) - add support for Solana SOL token withdraw +* [2533](https://github.com/zeta-chain/node/pull/2533) - parse memo from both OP_RETURN and inscription ### Refactor diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 470c56d86f..15a3bfdc99 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -477,39 +477,3 @@ func GetBtcEvent( } return nil, nil } - -// GetBtcEventWithWitness either returns a valid BTCInboundEvent or nil. -// This method supports data with more than 80 bytes by scanning the witness for possible presence of a tapscript. -// It will first prioritize OP_RETURN over tapscript. -func GetBtcEventWithWitness( - client interfaces.BTCRPCClient, - tx btcjson.TxRawResult, - tssAddress string, - blockNumber uint64, - logger zerolog.Logger, - netParams *chaincfg.Params, - depositorFee float64, -) (*BTCInboundEvent, error) { - // first check for OP_RETURN data - event, err := GetBtcEvent( - client, - tx, - tssAddress, - blockNumber, - logger, - netParams, - depositorFee, - ) - - if err != nil { - return nil, errors.Wrap(err, "unable to get btc event") - } - - if event != nil { - return event, nil - } - - // TODO: integrate parsing script - - return nil, nil -} diff --git a/zetaclient/chains/bitcoin/observer/witness.go b/zetaclient/chains/bitcoin/observer/witness.go new file mode 100644 index 0000000000..0af55c62a9 --- /dev/null +++ b/zetaclient/chains/bitcoin/observer/witness.go @@ -0,0 +1,187 @@ +package observer + +import ( + "encoding/hex" + "fmt" + + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/chaincfg" + "github.com/pkg/errors" + "github.com/rs/zerolog" + + "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" +) + +// GetBtcEventWithWitness either returns a valid BTCInboundEvent or nil. +// This method supports data with more than 80 bytes by scanning the witness for possible presence of a tapscript. +// It will first prioritize OP_RETURN over tapscript. +func GetBtcEventWithWitness( + client interfaces.BTCRPCClient, + tx btcjson.TxRawResult, + tssAddress string, + blockNumber uint64, + logger zerolog.Logger, + netParams *chaincfg.Params, + depositorFee float64, +) (*BTCInboundEvent, error) { + if len(tx.Vout) < 1 { + logger.Debug().Msgf("no output %s", tx.Txid) + return nil, nil + } + if len(tx.Vin) == 0 { + logger.Debug().Msgf("no input found for inbound: %s", tx.Txid) + return nil, nil + } + + if err := isValidRecipient(tx.Vout[0].ScriptPubKey.Hex, tssAddress, netParams); err != nil { + logger.Debug().Msgf("irrelevant recipient %s for tx %s, err: %s", tx.Vout[0].ScriptPubKey.Hex, tx.Txid, err) + return nil, nil + } + + isAmountValid, amount := isValidAmount(tx.Vout[0].Value, depositorFee) + if !isAmountValid { + logger.Info(). + Msgf("GetBtcEventWithWitness: btc deposit amount %v in txid %s is less than depositor fee %v", tx.Vout[0].Value, tx.Txid, depositorFee) + return nil, nil + } + + // Try to extract the memo from the BTC txn. First try to extract from OP_RETURN + // if not found then try to extract from inscription. Return nil if the above two + // cannot find the memo. + var memo []byte + if candidate := tryExtractOpRet(tx, logger); candidate != nil { + memo = candidate + logger.Debug(). + Msgf("GetBtcEventWithWitness: found OP_RETURN memo %s in tx %s", hex.EncodeToString(memo), tx.Txid) + } else if candidate = tryExtractInscription(tx, logger); candidate != nil { + memo = candidate + logger.Debug().Msgf("GetBtcEventWithWitness: found inscription memo %s in tx %s", hex.EncodeToString(memo), tx.Txid) + } else { + return nil, errors.Errorf("error getting memo for inbound: %s", tx.Txid) + } + + // event found, get sender address + fromAddress, err := GetSenderAddressByVin(client, tx.Vin[0], netParams) + if err != nil { + return nil, errors.Wrapf(err, "error getting sender address for inbound: %s", tx.Txid) + } + + return &BTCInboundEvent{ + FromAddress: fromAddress, + ToAddress: tssAddress, + Value: amount, + MemoBytes: memo, + BlockNumber: blockNumber, + TxHash: tx.Txid, + }, nil +} + +// ParseScriptFromWitness attempts to parse the script from the witness data. Ideally it should be handled by +// bitcoin library, however, it's not found in existing library version. Replace this with actual library implementation +// if libraries are updated. +func ParseScriptFromWitness(witness []string, logger zerolog.Logger) []byte { + length := len(witness) + + if length == 0 { + return nil + } + + lastElement, err := hex.DecodeString(witness[length-1]) + if err != nil { + logger.Debug().Msgf("invalid witness element") + return nil + } + + // From BIP341: + // If there are at least two witness elements, and the first byte of + // the last element is 0x50, this last element is called annex a + // and is removed from the witness stack. + if length >= 2 && len(lastElement) > 0 && lastElement[0] == 0x50 { + // account for the extra item removed from the end + witness = witness[:length-1] + } + + if len(witness) < 2 { + logger.Debug().Msgf("not script path spending detected, ignore") + return nil + } + + // only the script is the focus here, ignore checking control block or whatever else + script, err := hex.DecodeString(witness[len(witness)-2]) + if err != nil { + logger.Debug().Msgf("witness script cannot be decoded from hex, ignore") + return nil + } + return script +} + +// / Try to extract the memo from the OP_RETURN +func tryExtractOpRet(tx btcjson.TxRawResult, logger zerolog.Logger) []byte { + if len(tx.Vout) < 2 { + logger.Debug().Msgf("txn %s has fewer than 2 outputs, not target OP_RETURN txn", tx.Txid) + return nil + } + + memo, found, err := bitcoin.DecodeOpReturnMemo(tx.Vout[1].ScriptPubKey.Hex, tx.Txid) + if err != nil { + logger.Error().Err(err).Msgf("tryExtractOpRet: error decoding OP_RETURN memo: %s", tx.Vout[1].ScriptPubKey.Hex) + return nil + } + + if found { + return memo + } + return nil +} + +// / Try to extract the memo from inscription +func tryExtractInscription(tx btcjson.TxRawResult, logger zerolog.Logger) []byte { + for i, input := range tx.Vin { + script := ParseScriptFromWitness(input.Witness, logger) + if script == nil { + continue + } + + logger.Debug().Msgf("potential witness script, tx %s, input idx %d", tx.Txid, i) + + memo, found, err := bitcoin.DecodeScript(script) + if err != nil || !found { + logger.Debug().Msgf("invalid witness script, tx %s, input idx %d", tx.Txid, i) + continue + } + + logger.Debug().Msgf("found memo in inscription, tx %s, input idx %d", tx.Txid, i) + return memo + } + + return nil +} + +func isValidAmount( + incoming float64, + minimal float64, +) (bool, float64) { + if incoming < minimal { + return false, 0 + } + return true, incoming - minimal +} + +func isValidRecipient( + script string, + tssAddress string, + netParams *chaincfg.Params, +) error { + receiver, err := bitcoin.DecodeScriptP2WPKH(script, netParams) + if err != nil { + return fmt.Errorf("invalid p2wpkh script detected, %s", err) + } + + // skip irrelevant tx to us + if receiver != tssAddress { + return fmt.Errorf("irrelevant recipient, %s", receiver) + } + + return nil +} diff --git a/zetaclient/chains/bitcoin/observer/witness_test.go b/zetaclient/chains/bitcoin/observer/witness_test.go new file mode 100644 index 0000000000..4e93fb5cf1 --- /dev/null +++ b/zetaclient/chains/bitcoin/observer/witness_test.go @@ -0,0 +1,238 @@ +package observer_test + +import ( + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" + "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" + clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" + "github.com/zeta-chain/zetacore/zetaclient/testutils" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" +) + +func TestParseScriptFromWitness(t *testing.T) { + t.Run("decode script ok", func(t *testing.T) { + witness := [3]string{ + "3a4b32aef0e6ecc62d185594baf4df186c6d48ec15e72515bf81c1bcc1f04c758f4d54486bc2e7c280e649761d9084dbd2e7cdfb20708a7f8d0f82e5277bba2b", + "20888269c4f0b7f6fe95d0cba364e2b1b879d9b00735d19cfab4b8d87096ce2b3cac00634d0802000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068", + "c0888269c4f0b7f6fe95d0cba364e2b1b879d9b00735d19cfab4b8d87096ce2b3c", + } + expected := "20888269c4f0b7f6fe95d0cba364e2b1b879d9b00735d19cfab4b8d87096ce2b3cac00634d0802000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068" + + script := observer.ParseScriptFromWitness(witness[:], log.Logger) + require.NotNil(t, script) + require.Equal(t, hex.EncodeToString(script), expected) + }) + + t.Run("no witness", func(t *testing.T) { + witness := [0]string{} + script := observer.ParseScriptFromWitness(witness[:], log.Logger) + require.Nil(t, script) + }) + + t.Run("ignore key spending path", func(t *testing.T) { + witness := [1]string{ + "134896c42cd95680b048845847c8054756861ffab7d4abab72f6508d67d1ec0c590287ec2161dd7884983286e1cd56ce65c08a24ee0476ede92678a93b1b180c", + } + script := observer.ParseScriptFromWitness(witness[:], log.Logger) + require.Nil(t, script) + }) +} + +func TestGetBtcEventFromInscription(t *testing.T) { + // load archived inbound P2WPKH raw result + // https://mempool.space/tx/847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa + txHash := "847139aa65aa4a5ee896375951cbf7417cfc8a4d6f277ec11f40cd87319f04aa" + chain := chains.BitcoinMainnet + + tssAddress := testutils.TSSAddressBTCMainnet + blockNumber := uint64(835640) + net := &chaincfg.MainNetParams + // 2.992e-05, see avgFeeRate https://mempool.space/api/v1/blocks/835640 + depositorFee := bitcoin.DepositorFee(22 * clientcommon.BTCOutboundGasPriceMultiplier) + + t.Run("decode OP_RETURN ok", func(t *testing.T) { + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) + + // https://mempool.space/tx/c5d224963832fc0b9a597251c2342a17b25e481a88cc9119008e8f8296652697 + preHash := "c5d224963832fc0b9a597251c2342a17b25e481a88cc9119008e8f8296652697" + tx.Vin[0].Txid = preHash + tx.Vin[0].Vout = 2 + + memo, _ := hex.DecodeString(tx.Vout[1].ScriptPubKey.Hex[4:]) + eventExpected := &observer.BTCInboundEvent{ + FromAddress: "bc1q68kxnq52ahz5vd6c8czevsawu0ux9nfrzzrh6e", + ToAddress: tssAddress, + Value: tx.Vout[0].Value - depositorFee, + MemoBytes: memo, + BlockNumber: blockNumber, + TxHash: tx.Txid, + } + + // load previous raw tx so so mock rpc client can return it + rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) + + // get BTC event + event, err := observer.GetBtcEventWithWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) + require.NoError(t, err) + require.Equal(t, eventExpected, event) + }) + + t.Run("decode inscription ok", func(t *testing.T) { + txHash2 := "37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8" + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash2, false) + + preHash := "c5d224963832fc0b9a597251c2342a17b25e481a88cc9119008e8f8296652697" + rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) + + eventExpected := &observer.BTCInboundEvent{ + FromAddress: "bc1qm24wp577nk8aacckv8np465z3dvmu7ry45el6y", + ToAddress: tssAddress, + Value: tx.Vout[0].Value - depositorFee, + MemoBytes: make([]byte, 600), + BlockNumber: blockNumber, + TxHash: tx.Txid, + } + + // get BTC event + event, err := observer.GetBtcEventWithWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) + require.NoError(t, err) + require.Equal(t, event, eventExpected) + }) + + t.Run("decode inscription ok - mainnet", func(t *testing.T) { + // The input data is from the below mainnet, but output is modified for test case + txHash2 := "7a57f987a3cb605896a5909d9ef2bf7afbf0c78f21e4118b85d00d9e4cce0c2c" + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash2, false) + + preHash := "c5d224963832fc0b9a597251c2342a17b25e481a88cc9119008e8f8296652697" + tx.Vin[0].Txid = preHash + tx.Vin[0].Sequence = 2 + rpcClient := createRPCClientAndLoadTx(t, chain.ChainId, preHash) + + memo, _ := hex.DecodeString( + "72f080c854647755d0d9e6f6821f6931f855b9acffd53d87433395672756d58822fd143360762109ab898626556b1c3b8d3096d2361f1297df4a41c1b429471a9aa2fc9be5f27c13b3863d6ac269e4b587d8389f8fd9649859935b0d48dea88cdb40f20c", + ) + eventExpected := &observer.BTCInboundEvent{ + FromAddress: "bc1qm24wp577nk8aacckv8np465z3dvmu7ry45el6y", + ToAddress: tssAddress, + Value: tx.Vout[0].Value - depositorFee, + MemoBytes: memo, + BlockNumber: blockNumber, + TxHash: tx.Txid, + } + + // get BTC event + event, err := observer.GetBtcEventWithWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) + require.NoError(t, err) + require.Equal(t, event, eventExpected) + }) + + t.Run("should skip tx if receiver address is not TSS address", func(t *testing.T) { + // load tx and modify receiver address to any non-tss address: bc1qw8wrek2m7nlqldll66ajnwr9mh64syvkt67zlu + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) + tx.Vout[0].ScriptPubKey.Hex = "001471dc3cd95bf4fe0fb7ffd6bb29b865ddf5581196" + + // get BTC event + rpcClient := mocks.NewMockBTCRPCClient() + event, err := observer.GetBtcEventWithWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) + require.NoError(t, err) + require.Nil(t, event) + }) + + t.Run("should skip tx if amount is less than depositor fee", func(t *testing.T) { + // load tx and modify amount to less than depositor fee + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) + tx.Vout[0].Value = depositorFee - 1.0/1e8 // 1 satoshi less than depositor fee + + // get BTC event + rpcClient := mocks.NewMockBTCRPCClient() + event, err := observer.GetBtcEventWithWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) + require.NoError(t, err) + require.Nil(t, event) + }) + + t.Run("should return error if RPC client fails to get raw tx", func(t *testing.T) { + // load tx and leave rpc client without preloaded tx + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) + rpcClient := mocks.NewMockBTCRPCClient() + + // get BTC event + event, err := observer.GetBtcEventWithWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) + require.Error(t, err) + require.Nil(t, event) + }) + + t.Run("should return error if RPC client fails to get raw tx", func(t *testing.T) { + // load tx and leave rpc client without preloaded tx + tx := testutils.LoadBTCInboundRawResult(t, TestDataDir, chain.ChainId, txHash, false) + rpcClient := mocks.NewMockBTCRPCClient() + + // get BTC event + event, err := observer.GetBtcEventWithWitness( + rpcClient, + *tx, + tssAddress, + blockNumber, + log.Logger, + net, + depositorFee, + ) + require.Error(t, err) + require.Nil(t, event) + }) +} diff --git a/zetaclient/testdata/btc/chain_8332_inbound_raw_result_37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8.json b/zetaclient/testdata/btc/chain_8332_inbound_raw_result_37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8.json new file mode 100644 index 0000000000..a4e964500d --- /dev/null +++ b/zetaclient/testdata/btc/chain_8332_inbound_raw_result_37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8.json @@ -0,0 +1,42 @@ +{ + "hex": "020000000001027bc0bba407bc67178f100e352bf6e047fae4cbf960d783586cb5e430b3b700e70000000000feffffff7bc0bba407bc67178f100e352bf6e047fae4cbf960d783586cb5e430b3b700e70100000000feffffff01b4ba0e0000000000160014173fd310e9db2c7e9550ce0f03f1e6c01d833aa90140134896c42cd95680b048845847c8054756861ffab7d4abab72f6508d67d1ec0c590287ec2161dd7884983286e1cd56ce65c08a24ee0476ede92678a93b1b180c03407b5d614a4610bf9196775791fcc589597ca066dcd10048e004cd4c7341bb4bb90cee4705192f3f7db524e8067a5222c7f09baf29ef6b805b8327ecd1e5ab83ca2220f5b059b9a72298ccbefff59d9b943f7e0fc91d8a3b944a95e7b6390cc99eb5f4ac41c0d9dfdf0fe3c83e9870095d67fff59a8056dad28c6dfb944bb71cf64b90ace9a7776b22a1185fb2dc9524f6b178e2693189bf01655d7f38f043923668dc5af45bffd30a00", + "txid": "37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8", + "version": 2, + "locktime": 0, + "vin": [ + { + "vout": 0, + "scriptSig": { + "asm": "", + "hex": "" + }, + "sequence": 4294967294, + "txid": "e700b7b330e4b56c5883d760f9cbe4fa47e0f62b350e108f1767bc07a4bbc07b", + "txinwitness": [ + "134896c42cd95680b048845847c8054756861ffab7d4abab72f6508d67d1ec0c590287ec2161dd7884983286e1cd56ce65c08a24ee0476ede92678a93b1b180c" + ] + }, + { + "scriptSig": {"asm": "", "hex": ""}, + "sequence": 4294967294, + "txid": "c5d224963832fc0b9a597251c2342a17b25e481a88cc9119008e8f8296652697", + "vout": 2, + "txinwitness": [ + "3a4b32aef0e6ecc62d185594baf4df186c6d48ec15e72515bf81c1bcc1f04c758f4d54486bc2e7c280e649761d9084dbd2e7cdfb20708a7f8d0f82e5277bba2b", + "20888269c4f0b7f6fe95d0cba364e2b1b879d9b00735d19cfab4b8d87096ce2b3cac00634d0802000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004c50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068", + "c0888269c4f0b7f6fe95d0cba364e2b1b879d9b00735d19cfab4b8d87096ce2b3c" + ] + } + ], + "vout": [ + { + "value": 0.36557203, + "n": 0, + "scriptPubKey": { + "asm": "0 daaae0d3de9d8fdee31661e61aea828b59be7864", + "hex": "0014daaae0d3de9d8fdee31661e61aea828b59be7864", + "type": "witness_v0_keyhash" + } + } + ] +} diff --git a/zetaclient/testdata/btc/chain_8332_inbound_raw_result_7a57f987a3cb605896a5909d9ef2bf7afbf0c78f21e4118b85d00d9e4cce0c2c.json b/zetaclient/testdata/btc/chain_8332_inbound_raw_result_7a57f987a3cb605896a5909d9ef2bf7afbf0c78f21e4118b85d00d9e4cce0c2c.json new file mode 100644 index 0000000000..ab20339421 --- /dev/null +++ b/zetaclient/testdata/btc/chain_8332_inbound_raw_result_7a57f987a3cb605896a5909d9ef2bf7afbf0c78f21e4118b85d00d9e4cce0c2c.json @@ -0,0 +1,32 @@ +{ + "txid": "7a57f987a3cb605896a5909d9ef2bf7afbf0c78f21e4118b85d00d9e4cce0c2c", + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "213403e1efb29349a48ea9717096cf20d6e19091e496052ab591f310f0deebd6", + "vout": 0, + "scriptSig": { + "asm": "", + "hex": "" + }, + "txinwitness": [ + "7a8d20a4bb100ffd6399dc4fa1972e405e0e245775be1fcd7df3d5212d62c8d2e4b5534b3ae508a1f974d8995aac759454de9645f78245b8bee3b90ade86ea70", + "20a7172b841ddb8716fd0afa3400d18bfe1105df132e6938cca651b65ee3e64853ac00634c6472f080c854647755d0d9e6f6821f6931f855b9acffd53d87433395672756d58822fd143360762109ab898626556b1c3b8d3096d2361f1297df4a41c1b429471a9aa2fc9be5f27c13b3863d6ac269e4b587d8389f8fd9649859935b0d48dea88cdb40f20c68", + "c1a7172b841ddb8716fd0afa3400d18bfe1105df132e6938cca651b65ee3e64853" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "scriptPubKey": { + "asm": "0 daaae0d3de9d8fdee31661e61aea828b59be7864", + "hex": "0014daaae0d3de9d8fdee31661e61aea828b59be7864", + "type": "witness_v0_keyhash" + }, + "value": 0.45, + "n": 0 + } + ] +} \ No newline at end of file