Skip to content

Commit

Permalink
feat: support decode psbtHex & txHex detail.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhangguiguang committed Apr 15, 2024
1 parent 59c707e commit 01f72a4
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 2 deletions.
4 changes: 2 additions & 2 deletions core/btc/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ func TestAccount_SignPsbt(t *testing.T) {
acc, err := NewAccountWithMnemonic(testcase.M1, ChainMainnet)
require.NoError(t, err)

psbtHex := "010203"
psbtHex := "70736274ff01007d010000000158d38c41272f90e01d8dd3c5f9f6c4d37892ddcbaee155ecd826113c163003700000000000fdffffff02ea0500000000000022512073c9f2168a01fb0f0caa8a3fb7889ce4ab2cec67bfb16d272af4eea91fbd83e011cc240000000000160014f23a59a174bf387281535e2c0f6a395b77eb04a3000000000001011f2dd3240000000000160014f23a59a174bf387281535e2c0f6a395b77eb04a3000000"
txn, err := acc.SignPsbt(psbtHex)
require.NoError(t, err)
require.True(t, txn.Packet.IsComplete())
Expand All @@ -361,7 +361,7 @@ func TestChain_PushPsbt(t *testing.T) {
chain, err := NewChainWithChainnet(ChainTestnet)
require.NoError(t, err)

psbtHex := "010203"
psbtHex := "70736274ff01007d010000000158d38c41272f90e01d8dd3c5f9f6c4d37892ddcbaee155ecd826113c163003700000000000fdffffff02ea0500000000000022512073c9f2168a01fb0f0caa8a3fb7889ce4ab2cec67bfb16d272af4eea91fbd83e011cc240000000000160014f23a59a174bf387281535e2c0f6a395b77eb04a3000000000001011f2dd3240000000000160014f23a59a174bf387281535e2c0f6a395b77eb04a3000000"
hash, err := chain.PushPsbt(psbtHex)
require.NoError(t, err)
t.Log("txn hash = ", hash.Value)
Expand Down
198 changes: 198 additions & 0 deletions core/btc/transaction_decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package btc

import (
"fmt"
"strconv"

"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/coming-chat/wallet-SDK/core/base"
"github.com/coming-chat/wallet-SDK/core/base/inter"
)

type TxOut struct {
Hash string `json:"hash,omitempty"`
Index int64 `json:"index,omitempty"`

Value int64 `json:"value,omitempty"`
Address string `json:"address,omitempty"`
}

type TxOutArray struct {
inter.AnyArray[*TxOut]
}

func (ta *TxOutArray) detailDesc() string {
desc := ""
for idx, out := range ta.AnyArray {
if out.Address != "" {
addr := out.Address
if len(addr) > 13 {
addr = addr[:5] + "..." + addr[len(addr)-5:]
}
valueBTC := btcutil.Amount(out.Value).String()
if idx == 0 {
desc += "\t" + addr + "\t" + valueBTC
} else {
desc += "\n\t" + addr + "\t" + valueBTC
}
} else {
if idx == 0 {
desc += "\thash: " + out.Hash + "\n" + "\tindex: " + strconv.FormatInt(out.Index, 10)
} else {
desc += "\n\thash: " + out.Hash + "\n" + "\tindex: " + strconv.FormatInt(out.Index, 10)
}
}
}
return desc
}

type TransactionDetail struct {
NetworkFee int64 `json:"networkFee"`
FeeRate float64 `json:"feeRate"`
Inputs *TxOutArray `json:"inputs"`
Outputs *TxOutArray `json:"outputs"`
}

func (td *TransactionDetail) JsonString() (*base.OptionalString, error) {
return base.JsonString(td)
}

func (td *TransactionDetail) Desc() string {
feeBTC := ""
feeRate := ""
if td.NetworkFee == 0 {
feeBTC = "Unknow"
feeRate = "Unknow"
} else {
feeBTC = btcutil.Amount(td.NetworkFee).String()
feeRate = fmt.Sprintf("%.2f sat/vB", td.FeeRate)
}

return fmt.Sprintf(`Network Fee:
%v
Network FeeRate:
%v
Inputs:
%v
Outputs:
%v`, feeBTC, feeRate, td.Inputs.detailDesc(), td.Outputs.detailDesc())
}

func DecodePsbtTransactionDetail(psbtHex string, chainnet string) (d *TransactionDetail, err error) {
chainParams, err := netParamsOf(chainnet)
if err != nil {
return
}
packet, err := DecodePsbtTxToPacket(psbtHex)
if err != nil {
return
}
txFee, err := packet.GetTxFee()
if err != nil {
return
}
feeFloat := txFee.ToUnit(btcutil.AmountSatoshi)
vSize := virtualSize(packet.UnsignedTx)
feeRate := feeFloat / float64(vSize)

inputs := make([]*TxOut, len(packet.Inputs))
for idx, in := range packet.Inputs {
switch {
case in.WitnessUtxo != nil:
inputs[idx], err = txOutFromWireTxOut(in.WitnessUtxo, chainParams)
if err != nil {
return
}
case in.NonWitnessUtxo != nil:
utxOuts := in.NonWitnessUtxo.TxOut
txIn := packet.UnsignedTx.TxIn[idx]
opIdx := txIn.PreviousOutPoint.Index
txOut := utxOuts[opIdx]
inputs[idx], err = txOutFromWireTxOut(txOut, chainParams)
if err != nil {
return
}
default:
return nil, fmt.Errorf("input %d has no UTXO information",
idx)
}
}

outputs := make([]*TxOut, len(packet.UnsignedTx.TxOut))
for idx, out := range packet.UnsignedTx.TxOut {
outputs[idx], err = txOutFromWireTxOut(out, chainParams)
if err != nil {
return
}
}

return &TransactionDetail{
NetworkFee: int64(feeFloat),
FeeRate: float64(feeRate),
Inputs: &TxOutArray{inputs},
Outputs: &TxOutArray{outputs},
}, nil
}

func DecodeTxHexTransactionDetail(txHex string, chainnet string) (detail *TransactionDetail, err error) {
chainParams, err := netParamsOf(chainnet)
if err != nil {
return
}
msgTx, err := DecodeTx(txHex)
if err != nil {
return nil, err
}
// msgTx cannot get `fee`, `feeRate`

inputs := make([]*TxOut, len(msgTx.TxIn))
for idx, in := range msgTx.TxIn {
inputs[idx] = &TxOut{
Hash: in.PreviousOutPoint.Hash.String(),
Index: int64(in.PreviousOutPoint.Index),
}
}
outputs := make([]*TxOut, len(msgTx.TxOut))
for idx, out := range msgTx.TxOut {
outputs[idx], err = txOutFromWireTxOut(out, chainParams)
if err != nil {
return
}
}

return &TransactionDetail{
NetworkFee: 0,
FeeRate: 0,
Inputs: &TxOutArray{inputs},
Outputs: &TxOutArray{outputs},
}, nil
}

func txOutFromWireTxOut(txout *wire.TxOut, params *chaincfg.Params) (*TxOut, error) {
pkobj, err := txscript.ParsePkScript(txout.PkScript)
if err != nil {
return nil, err
}
addr, err := pkobj.Address(params)
if err != nil {
return nil, err
}
return &TxOut{
Value: txout.Value,
Address: addr.EncodeAddress(),
}, nil
}

// calculation reference:
// https://github.com/btcsuite/btcd/blob/569155bc6a502f45b4a514bc6b9d5f814a980b6c/mempool/policy.go#L382
func virtualSize(tx *wire.MsgTx) int64 {
// vSize := (((baseSize * 3) + totalSize) + 3) / 4
baseSize := int64(tx.SerializeSizeStripped())
totalSize := int64(tx.SerializeSize())
weight := (baseSize * (blockchain.WitnessScaleFactor - 1)) + totalSize
return (weight + blockchain.WitnessScaleFactor - 1) / blockchain.WitnessScaleFactor
}
22 changes: 22 additions & 0 deletions core/btc/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,25 @@ func getPsbtPacketWithSegwitV1(t *testing.T) *psbt.Packet {
require.NoError(t, err)
return packet
}

func TestDecodePsbtHex_DecodeTxHex(t *testing.T) {
psbtHex := "70736274ff01007d010000000158d38c41272f90e01d8dd3c5f9f6c4d37892ddcbaee155ecd826113c163003700000000000fdffffff02ea0500000000000022512073c9f2168a01fb0f0caa8a3fb7889ce4ab2cec67bfb16d272af4eea91fbd83e011cc240000000000160014f23a59a174bf387281535e2c0f6a395b77eb04a3000000000001011f2dd3240000000000160014f23a59a174bf387281535e2c0f6a395b77eb04a3000000"
dd, err := DecodePsbtTransactionDetail(psbtHex, ChainMainnet)
require.NoError(t, err)
json, _ := dd.JsonString()
t.Logf("Json:\n%v", json.Value)
t.Logf("Desc:\n%v", dd.Desc())

packet, err := DecodePsbtTxToPacket(psbtHex)
require.NoError(t, err)

buf := bytes.Buffer{}
err = packet.UnsignedTx.Serialize(&buf)
require.NoError(t, err)
txHex := hex.EncodeToString(buf.Bytes())
dd2, err := DecodeTxHexTransactionDetail(txHex, ChainMainnet)
require.NoError(t, err)
json, _ = dd2.JsonString()
t.Logf("Json:\n%v", json.Value)
t.Logf("Desc:\n%v", dd2.Desc())
}

0 comments on commit 01f72a4

Please sign in to comment.