Files
pump-parser/rawtx_binary.go

1743 lines
50 KiB
Go
Raw Permalink Normal View History

2026-04-24 18:00:44 +08:00
package pump_parser
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"iter"
"math"
"math/big"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
const rawTxBinarySchemaVersionCurrent uint16 = 7
var rawTxBinaryMagic = [4]byte{'P', 'R', 'T', 'X'}
var rawTxsBinaryMagic = [4]byte{'P', 'R', 'T', 'S'}
var rawTxBlocksBinaryMagic = [4]byte{'P', 'R', 'B', 'S'}
type RawTxBinary struct {
SchemaVersion uint16
AddressTable []solana.PublicKey
BlockTime int64
IndexWithinBlock uint32
Slot uint64
Version uint8
AccountList []uint32
AccountKeyCount uint32
Meta RawTxMetaBinary
Transaction RawTxTransactionBinary
}
type RawTxsBinary struct {
SchemaVersion uint16
AddressTable []solana.PublicKey
BlockTime int64
Txs []RawTxBinary
}
type RawTxBlocksBinary struct {
SchemaVersion uint16
AddressTable []solana.PublicKey
BlockTimes []int64
BlockTxCounts []uint32
Txs []RawTxBinary
}
type RawTxMetaBinary struct {
Err *TransactionParsedError
Fee uint64
InnerInstructions []InnerInstructions
PostBalances []uint64
PreBalances []uint64
TokenBalances []RawTxTokenBalanceBinary
ComputeUnitsConsumed uint64
}
type RawTxTokenBalanceBinary struct {
AccountIndex uint16
MintAccount uint16
OwnerAccount uint16
HasOwnerAccount bool
ProgramIDAccount uint16
Decimals uint8
HasPreAmount bool
PreAmount string
HasPostAmount bool
PostAmount string
}
type RawTxTransactionBinary struct {
Message RawTxMessageBinary
Signature solana.Signature
HasSignature bool
}
type RawTxMessageBinary struct {
Header Header
Instructions []Instruction
AddressTableLookups []RawTxAddressTableLookupBinary
}
type RawTxAddressTableLookupBinary struct {
AccountKey uint32
WritableIndexes []uint8
ReadonlyIndexes []uint8
}
func NewRawTxBinary(tx *RawTx) (*RawTxBinary, error) {
if tx == nil {
return nil, fmt.Errorf("raw tx is nil")
}
addressTable, err := rawTxBinaryBuildAddressTable([]*RawTx{tx})
if err != nil {
return nil, err
}
addressIndex, err := newTxBinaryAddressIndex(addressTable)
if err != nil {
return nil, err
}
return newRawTxBinaryWithAddressTable(tx, addressTable, addressIndex)
}
func NewRawTxsBinary(txs []RawTx) (*RawTxsBinary, error) {
txPtrs := make([]*RawTx, 0, len(txs))
for i := range txs {
txPtrs = append(txPtrs, &txs[i])
}
addressTable, err := rawTxBinaryBuildAddressTable(txPtrs)
if err != nil {
return nil, err
}
addressIndex, err := newTxBinaryAddressIndex(addressTable)
if err != nil {
return nil, err
}
blockTime, err := rawTxBinarySharedBlockTime(txPtrs, "txs")
if err != nil {
return nil, err
}
out := &RawTxsBinary{
SchemaVersion: rawTxBinarySchemaVersionCurrent,
AddressTable: addressTable,
BlockTime: blockTime,
Txs: make([]RawTxBinary, 0, len(txPtrs)),
}
for i, tx := range txPtrs {
binaryTx, err := newRawTxBinaryWithAddressTable(tx, addressTable, addressIndex)
if err != nil {
return nil, fmt.Errorf("tx[%d], %s: %w", i, tx.TxHash(), err)
}
out.Txs = append(out.Txs, *binaryTx)
}
return out, nil
}
func NewRawTxBlocksBinary(blocks [][]RawTx) (*RawTxBlocksBinary, error) {
var txPtrs []*RawTx
for blockIndex := range blocks {
for txIndex := range blocks[blockIndex] {
txPtrs = append(txPtrs, &blocks[blockIndex][txIndex])
}
}
addressTable, err := rawTxBinaryBuildAddressTable(txPtrs)
if err != nil {
return nil, err
}
addressIndex, err := newTxBinaryAddressIndex(addressTable)
if err != nil {
return nil, err
}
out := &RawTxBlocksBinary{
SchemaVersion: rawTxBinarySchemaVersionCurrent,
AddressTable: addressTable,
BlockTimes: make([]int64, 0, len(blocks)),
BlockTxCounts: make([]uint32, 0, len(blocks)),
Txs: make([]RawTxBinary, 0, len(txPtrs)),
}
for blockIndex := range blocks {
if uint64(len(blocks[blockIndex])) > uint64(math.MaxUint32) {
return nil, fmt.Errorf("block[%d] tx count exceeds uint32 capacity", blockIndex)
}
blockTime := int64(0)
if len(blocks[blockIndex]) > 0 {
blockTime = blocks[blockIndex][0].BlockTime
for txIndex := range blocks[blockIndex] {
if blocks[blockIndex][txIndex].BlockTime != blockTime {
return nil, fmt.Errorf("block[%d].tx[%d] block time mismatch: got %d want %d", blockIndex, txIndex, blocks[blockIndex][txIndex].BlockTime, blockTime)
}
}
}
out.BlockTimes = append(out.BlockTimes, blockTime)
out.BlockTxCounts = append(out.BlockTxCounts, uint32(len(blocks[blockIndex])))
for txIndex := range blocks[blockIndex] {
binaryTx, err := newRawTxBinaryWithAddressTable(&blocks[blockIndex][txIndex], addressTable, addressIndex)
if err != nil {
return nil, fmt.Errorf("block[%d].tx[%d], %s: %w", blockIndex, txIndex, blocks[blockIndex][txIndex].TxHash(), err)
}
out.Txs = append(out.Txs, *binaryTx)
}
}
return out, nil
}
func EncodeRawTxBinary(tx *RawTx) ([]byte, error) {
binaryTx, err := NewRawTxBinary(tx)
if err != nil {
return nil, err
}
return binaryTx.MarshalBinary()
}
func EncodeRawTxsBinary(txs []RawTx) ([]byte, error) {
binaryTxs, err := NewRawTxsBinary(txs)
if err != nil {
return nil, err
}
return binaryTxs.MarshalBinary()
}
func EncodeRawTxBlocksBinary(blocks [][]RawTx) ([]byte, error) {
binaryBlocks, err := NewRawTxBlocksBinary(blocks)
if err != nil {
return nil, err
}
return binaryBlocks.MarshalBinary()
}
func DecodeRawTxBinary(data []byte) (*RawTx, error) {
var binaryTx RawTxBinary
if err := binaryTx.UnmarshalBinary(data); err != nil {
return nil, err
}
return binaryTx.ToRawTx()
}
func DecodeRawTxsBinary(data []byte) ([]*RawTx, error) {
var binaryTxs RawTxsBinary
if err := binaryTxs.UnmarshalBinary(data); err != nil {
return nil, err
}
return binaryTxs.ToRawTxs()
}
func DecodeRawTxBlocksBinary(data []byte) ([][]*RawTx, error) {
var binaryBlocks RawTxBlocksBinary
if err := binaryBlocks.UnmarshalBinary(data); err != nil {
return nil, err
}
return binaryBlocks.ToRawTxBlocks()
}
func DecodeRawTxsBinaryReader(r io.Reader) iter.Seq2[*RawTx, error] {
return func(yield func(*RawTx, error) bool) {
if r == nil {
yield(nil, fmt.Errorf("raw txs binary reader is nil"))
return
}
dec := txBinaryStreamDecoder{reader: r}
header, err := rawTxBinaryReadTxsHeader(&dec)
if err != nil {
yield(nil, err)
return
}
for i := uint32(0); i < header.count; i++ {
tx := RawTxBinary{
SchemaVersion: header.schemaVersion,
AddressTable: header.addressTable,
BlockTime: header.blockTime,
}
if err := rawTxBinaryReadTxBody(&dec, &tx, header.addressTable, &header.blockTime); err != nil {
yield(nil, fmt.Errorf("tx[%d]: %w", i, err))
return
}
decodedTx, err := tx.ToRawTx()
if err != nil {
yield(nil, fmt.Errorf("tx[%d]: %w", i, err))
return
}
if !yield(decodedTx, nil) {
return
}
}
}
}
func newRawTxBinaryWithAddressTable(tx *RawTx, addressTable []solana.PublicKey, addressIndex *txBinaryAddressIndex) (*RawTxBinary, error) {
accountList := tx.getAccountList()
if uint64(len(accountList)) > uint64(math.MaxUint32) {
return nil, fmt.Errorf("account list exceeds uint32 capacity")
}
if uint64(len(tx.Transaction.Message.AccountKeys)) > uint64(math.MaxUint32) {
return nil, fmt.Errorf("message account key count exceeds uint32 capacity")
}
if tx.IndexWithinBlock < 0 || uint64(tx.IndexWithinBlock) > uint64(math.MaxUint32) {
return nil, fmt.Errorf("index within block overflows uint32: %d", tx.IndexWithinBlock)
}
out := &RawTxBinary{
SchemaVersion: rawTxBinarySchemaVersionCurrent,
AddressTable: addressTable,
BlockTime: tx.BlockTime,
IndexWithinBlock: uint32(tx.IndexWithinBlock),
Slot: tx.Slot,
Version: rawTxBinaryVersionID(tx.Version),
AccountList: make([]uint32, 0, len(accountList)),
AccountKeyCount: uint32(len(tx.Transaction.Message.AccountKeys)),
}
for i, account := range accountList {
ref, err := addressIndex.id(account)
if err != nil {
return nil, fmt.Errorf("account_list[%d]: %w", i, err)
}
out.AccountList = append(out.AccountList, ref)
}
meta, err := rawTxMetaToBinary(&tx.Meta, addressIndex)
if err != nil {
return nil, err
}
out.Meta = meta
message, err := rawTxMessageToBinary(&tx.Transaction.Message, addressIndex)
if err != nil {
return nil, err
}
out.Transaction = RawTxTransactionBinary{Message: message}
if len(tx.Transaction.Signatures) > 0 {
out.Transaction.Signature = tx.Transaction.Signatures[0]
out.Transaction.HasSignature = true
}
return out, nil
}
func (tx *RawTxBinary) MarshalBinary() ([]byte, error) {
if tx == nil {
return nil, fmt.Errorf("raw tx binary is nil")
}
enc := txBinaryEncoder{}
enc.writeBytes(rawTxBinaryMagic[:])
if err := rawTxBinaryWriteHeader(&enc, tx.SchemaVersion, tx.AddressTable); err != nil {
return nil, err
}
if err := rawTxBinaryWriteTxBody(&enc, tx, true); err != nil {
return nil, err
}
return enc.bytes(), nil
}
func (txs *RawTxsBinary) MarshalBinary() ([]byte, error) {
if txs == nil {
return nil, fmt.Errorf("raw txs binary is nil")
}
enc := txBinaryEncoder{}
enc.writeBytes(rawTxsBinaryMagic[:])
if err := rawTxBinaryWriteHeader(&enc, txs.SchemaVersion, txs.AddressTable); err != nil {
return nil, err
}
enc.writeUint64(uint64(txs.BlockTime))
enc.writeUint32(uint32(len(txs.Txs)))
for i := range txs.Txs {
if txs.Txs[i].BlockTime != txs.BlockTime {
return nil, fmt.Errorf("tx[%d] block time mismatch: got %d want %d", i, txs.Txs[i].BlockTime, txs.BlockTime)
}
if err := rawTxBinaryWriteTxBody(&enc, &txs.Txs[i], false); err != nil {
return nil, fmt.Errorf("tx[%d]: %w", i, err)
}
}
return enc.bytes(), nil
}
func (blocks *RawTxBlocksBinary) MarshalBinary() ([]byte, error) {
if blocks == nil {
return nil, fmt.Errorf("raw tx blocks binary is nil")
}
enc := txBinaryEncoder{}
enc.writeBytes(rawTxBlocksBinaryMagic[:])
if err := rawTxBinaryWriteHeader(&enc, blocks.SchemaVersion, blocks.AddressTable); err != nil {
return nil, err
}
if len(blocks.BlockTimes) != len(blocks.BlockTxCounts) {
return nil, fmt.Errorf("raw tx blocks block time count mismatch: block_times=%d counts=%d", len(blocks.BlockTimes), len(blocks.BlockTxCounts))
}
enc.writeUint32(uint32(len(blocks.BlockTxCounts)))
for i, count := range blocks.BlockTxCounts {
enc.writeUint64(uint64(blocks.BlockTimes[i]))
enc.writeUint32(count)
}
enc.writeUint32(uint32(len(blocks.Txs)))
txOffset := 0
for blockIndex, count := range blocks.BlockTxCounts {
for txIndex := uint32(0); txIndex < count; txIndex++ {
if txOffset >= len(blocks.Txs) {
return nil, fmt.Errorf("block[%d].tx[%d]: tx offset out of range", blockIndex, txIndex)
}
if blocks.Txs[txOffset].BlockTime != blocks.BlockTimes[blockIndex] {
return nil, fmt.Errorf("block[%d].tx[%d] block time mismatch: got %d want %d", blockIndex, txIndex, blocks.Txs[txOffset].BlockTime, blocks.BlockTimes[blockIndex])
}
if err := rawTxBinaryWriteTxBody(&enc, &blocks.Txs[txOffset], false); err != nil {
return nil, fmt.Errorf("block[%d].tx[%d]: %w", blockIndex, txIndex, err)
}
txOffset++
}
}
if txOffset != len(blocks.Txs) {
return nil, fmt.Errorf("raw tx blocks unused tx payloads: %d", len(blocks.Txs)-txOffset)
}
return enc.bytes(), nil
}
func (tx *RawTxBinary) UnmarshalBinary(data []byte) error {
dec := txBinaryDecoder{reader: bytes.NewReader(data)}
magic, err := dec.readN(len(rawTxBinaryMagic))
if err != nil {
return err
}
if !bytes.Equal(magic, rawTxBinaryMagic[:]) {
return fmt.Errorf("invalid raw tx binary magic")
}
schemaVersion, addressTable, err := rawTxBinaryReadHeader(&dec)
if err != nil {
return err
}
tx.SchemaVersion = schemaVersion
tx.AddressTable = addressTable
if err := rawTxBinaryReadTxBody(&dec, tx, addressTable, nil); err != nil {
return err
}
if dec.reader.Len() != 0 {
return fmt.Errorf("unexpected trailing raw tx binary data: %d bytes", dec.reader.Len())
}
return nil
}
func (txs *RawTxsBinary) UnmarshalBinary(data []byte) error {
dec := txBinaryDecoder{reader: bytes.NewReader(data)}
header, err := rawTxBinaryReadTxsHeader(&dec)
if err != nil {
return err
}
txs.SchemaVersion = header.schemaVersion
txs.AddressTable = header.addressTable
txs.BlockTime = header.blockTime
txs.Txs = make([]RawTxBinary, 0, header.count)
for i := uint32(0); i < header.count; i++ {
tx := RawTxBinary{SchemaVersion: header.schemaVersion, AddressTable: header.addressTable, BlockTime: header.blockTime}
if err := rawTxBinaryReadTxBody(&dec, &tx, header.addressTable, &header.blockTime); err != nil {
return fmt.Errorf("tx[%d]: %w", i, err)
}
txs.Txs = append(txs.Txs, tx)
}
if dec.reader.Len() != 0 {
return fmt.Errorf("unexpected trailing raw txs binary data: %d bytes", dec.reader.Len())
}
return nil
}
func (blocks *RawTxBlocksBinary) UnmarshalBinary(data []byte) error {
dec := txBinaryDecoder{reader: bytes.NewReader(data)}
magic, err := dec.readN(len(rawTxBlocksBinaryMagic))
if err != nil {
return err
}
if !bytes.Equal(magic, rawTxBlocksBinaryMagic[:]) {
return fmt.Errorf("invalid raw tx blocks binary magic")
}
schemaVersion, addressTable, err := rawTxBinaryReadHeader(&dec)
if err != nil {
return err
}
blocks.SchemaVersion = schemaVersion
blocks.AddressTable = addressTable
blockCount, err := dec.readUint32()
if err != nil {
return err
}
blocks.BlockTimes = make([]int64, 0, blockCount)
blocks.BlockTxCounts = make([]uint32, 0, blockCount)
var totalTxCount uint64
for i := uint32(0); i < blockCount; i++ {
blockTime, err := readInt64(&dec)
if err != nil {
return err
}
count, err := dec.readUint32()
if err != nil {
return err
}
blocks.BlockTimes = append(blocks.BlockTimes, blockTime)
blocks.BlockTxCounts = append(blocks.BlockTxCounts, count)
totalTxCount += uint64(count)
if totalTxCount > uint64(math.MaxUint32) {
return fmt.Errorf("raw tx blocks total tx count exceeds uint32 capacity")
}
}
txCount, err := dec.readUint32()
if err != nil {
return err
}
if uint64(txCount) != totalTxCount {
return fmt.Errorf("raw tx blocks tx count mismatch: header=%d blocks=%d", txCount, totalTxCount)
}
blocks.Txs = make([]RawTxBinary, 0, txCount)
var txOffset uint32
for blockIndex, count := range blocks.BlockTxCounts {
blockTime := blocks.BlockTimes[blockIndex]
for txIndex := uint32(0); txIndex < count; txIndex++ {
tx := RawTxBinary{SchemaVersion: schemaVersion, AddressTable: addressTable, BlockTime: blockTime}
if err := rawTxBinaryReadTxBody(&dec, &tx, addressTable, &blockTime); err != nil {
return fmt.Errorf("block[%d].tx[%d]: %w", blockIndex, txIndex, err)
}
blocks.Txs = append(blocks.Txs, tx)
txOffset++
}
}
if txOffset != txCount {
return fmt.Errorf("raw tx blocks decoded tx count mismatch: got %d want %d", txOffset, txCount)
}
if dec.reader.Len() != 0 {
return fmt.Errorf("unexpected trailing raw tx blocks binary data: %d bytes", dec.reader.Len())
}
return nil
}
func (tx *RawTxBinary) ToRawTx() (*RawTx, error) {
if tx == nil {
return nil, nil
}
accountList, err := rawTxBinaryResolveAccountList(tx.AddressTable, tx.AccountList)
if err != nil {
return nil, err
}
out := &RawTx{
accountList: append([]solana.PublicKey(nil), accountList...),
BlockTime: tx.BlockTime,
IndexWithinBlock: int64(tx.IndexWithinBlock),
Slot: tx.Slot,
Version: rawTxBinaryVersionValue(tx.Version),
Meta: rawTxMetaFromBinary(tx.Meta, tx.AddressTable),
Transaction: rawTxTransactionFromBinary(tx.Transaction, tx.AddressTable),
}
if tx.AccountKeyCount > 0 && tx.AccountKeyCount <= uint32(len(accountList)) {
out.Transaction.Message.AccountKeys = append(solana.PublicKeySlice(nil), accountList[:tx.AccountKeyCount]...)
} else {
out.Transaction.Message.AccountKeys = append(solana.PublicKeySlice(nil), accountList...)
}
return out, nil
}
func (txs *RawTxsBinary) ToRawTxs() ([]*RawTx, error) {
if txs == nil {
return nil, nil
}
out := make([]*RawTx, 0, len(txs.Txs))
for i := range txs.Txs {
txs.Txs[i].AddressTable = txs.AddressTable
tx, err := txs.Txs[i].ToRawTx()
if err != nil {
return nil, fmt.Errorf("tx[%d]: %w", i, err)
}
out = append(out, tx)
}
return out, nil
}
func (blocks *RawTxBlocksBinary) ToRawTxBlocks() ([][]*RawTx, error) {
if blocks == nil {
return nil, nil
}
out := make([][]*RawTx, 0, len(blocks.BlockTxCounts))
txOffset := 0
for blockIndex, count := range blocks.BlockTxCounts {
block := make([]*RawTx, 0, count)
for txIndex := uint32(0); txIndex < count; txIndex++ {
if txOffset >= len(blocks.Txs) {
return nil, fmt.Errorf("block[%d].tx[%d]: tx offset out of range", blockIndex, txIndex)
}
blocks.Txs[txOffset].AddressTable = blocks.AddressTable
tx, err := blocks.Txs[txOffset].ToRawTx()
if err != nil {
return nil, fmt.Errorf("block[%d].tx[%d]: %w", blockIndex, txIndex, err)
}
block = append(block, tx)
txOffset++
}
out = append(out, block)
}
if txOffset != len(blocks.Txs) {
return nil, fmt.Errorf("raw tx blocks unused tx payloads: %d", len(blocks.Txs)-txOffset)
}
return out, nil
}
type rawTxsBinaryHeader struct {
schemaVersion uint16
addressTable []solana.PublicKey
blockTime int64
count uint32
}
func rawTxBinaryWriteHeader(enc *txBinaryEncoder, schemaVersion uint16, addressTable []solana.PublicKey) error {
if schemaVersion != rawTxBinarySchemaVersionCurrent {
return fmt.Errorf("unsupported raw tx binary schema version: %d", schemaVersion)
}
enc.writeUint16(schemaVersion)
return enc.writeAddressTable(addressTable)
}
func rawTxBinaryReadHeader(dec txBinaryBodyReader) (uint16, []solana.PublicKey, error) {
schemaVersion, err := dec.readUint16()
if err != nil {
return 0, nil, err
}
if schemaVersion != rawTxBinarySchemaVersionCurrent {
return 0, nil, fmt.Errorf("unsupported raw tx binary schema version: %d", schemaVersion)
}
addressTable, err := txBinaryReadAddressTable(dec)
if err != nil {
return 0, nil, err
}
return schemaVersion, addressTable, nil
}
func rawTxBinaryReadTxsHeader(dec txBinaryBodyReader) (*rawTxsBinaryHeader, error) {
magic, err := dec.readN(len(rawTxsBinaryMagic))
if err != nil {
return nil, err
}
if !bytes.Equal(magic, rawTxsBinaryMagic[:]) {
return nil, fmt.Errorf("invalid raw txs binary magic")
}
schemaVersion, addressTable, err := rawTxBinaryReadHeader(dec)
if err != nil {
return nil, err
}
blockTime, err := readInt64(dec)
if err != nil {
return nil, err
}
count, err := dec.readUint32()
if err != nil {
return nil, err
}
return &rawTxsBinaryHeader{schemaVersion: schemaVersion, addressTable: addressTable, blockTime: blockTime, count: count}, nil
}
func rawTxBinaryWriteTxBody(enc *txBinaryEncoder, tx *RawTxBinary, includeBlockTime bool) error {
if includeBlockTime {
enc.writeUint64(uint64(tx.BlockTime))
}
enc.writeUint32(tx.IndexWithinBlock)
enc.writeUint64(tx.Slot)
enc.writeUint8(tx.Version)
enc.writeUint32(tx.AccountKeyCount)
writeUint32Slice(enc, tx.AccountList)
if err := writeRawTxMetaBinary(enc, tx.Meta); err != nil {
return err
}
return writeRawTxTransactionBinary(enc, tx.Transaction)
}
func rawTxBinaryReadTxBody(dec txBinaryBodyReader, tx *RawTxBinary, addressTable []solana.PublicKey, blockTime *int64) error {
var err error
if blockTime == nil {
if tx.BlockTime, err = readInt64(dec); err != nil {
return err
}
} else {
tx.BlockTime = *blockTime
}
if tx.IndexWithinBlock, err = dec.readUint32(); err != nil {
return err
}
if tx.Slot, err = dec.readUint64(); err != nil {
return err
}
if tx.Version, err = dec.readUint8(); err != nil {
return err
}
if tx.AccountKeyCount, err = dec.readUint32(); err != nil {
return err
}
if tx.AccountList, err = readUint32Slice(dec); err != nil {
return err
}
if tx.Meta, err = readRawTxMetaBinary(dec); err != nil {
return err
}
if tx.Transaction, err = readRawTxTransactionBinary(dec); err != nil {
return err
}
tx.SchemaVersion = rawTxBinarySchemaVersionCurrent
tx.AddressTable = addressTable
return nil
}
func rawTxMetaToBinary(meta *Meta, addressIndex *txBinaryAddressIndex) (RawTxMetaBinary, error) {
out := RawTxMetaBinary{
Err: cloneTransactionParsedError(meta.Err),
Fee: meta.Fee,
InnerInstructions: cloneInnerInstructions(meta.InnerInstructions),
PostBalances: append([]uint64(nil), meta.PostBalances...),
PreBalances: append([]uint64(nil), meta.PreBalances...),
ComputeUnitsConsumed: meta.ComputeUnitsConsumed,
}
var err error
out.TokenBalances, err = rawTxTokenBalancesToBinary(meta.PreTokenBalances, meta.PostTokenBalances, addressIndex)
if err != nil {
return out, fmt.Errorf("token_balances: %w", err)
}
return out, nil
}
func rawTxMessageToBinary(message *Message, addressIndex *txBinaryAddressIndex) (RawTxMessageBinary, error) {
out := RawTxMessageBinary{
Header: message.Header,
Instructions: cloneInstructions(message.Instructions),
}
out.AddressTableLookups = make([]RawTxAddressTableLookupBinary, 0, len(message.AddressTableLookups))
for i, lookup := range message.AddressTableLookups {
accountKey, err := addressIndex.id(lookup.AccountKey)
if err != nil {
return out, fmt.Errorf("address_table_lookups[%d].account_key: %w", i, err)
}
out.AddressTableLookups = append(out.AddressTableLookups, RawTxAddressTableLookupBinary{
AccountKey: accountKey,
WritableIndexes: append([]uint8(nil), lookup.WritableIndexes...),
ReadonlyIndexes: append([]uint8(nil), lookup.ReadonlyIndexes...),
})
}
return out, nil
}
func rawTxTokenBalancesToBinary(preBalances []TokenBalance, postBalances []TokenBalance, addressIndex *txBinaryAddressIndex) ([]RawTxTokenBalanceBinary, error) {
out := make([]RawTxTokenBalanceBinary, 0, len(preBalances)+len(postBalances))
byAccountIndex := make(map[uint16]int, len(preBalances)+len(postBalances))
for i, balance := range preBalances {
encoded, err := rawTxTokenBalanceToBinary(balance, addressIndex)
if err != nil {
return nil, fmt.Errorf("pre[%d]: %w", i, err)
}
if _, exists := byAccountIndex[encoded.AccountIndex]; exists {
return nil, fmt.Errorf("pre[%d].account_index duplicate: %d", i, encoded.AccountIndex)
}
encoded.HasPreAmount = true
encoded.PreAmount = balance.UITokenAmount.Amount
byAccountIndex[encoded.AccountIndex] = len(out)
out = append(out, encoded)
}
for i, balance := range postBalances {
encoded, err := rawTxTokenBalanceToBinary(balance, addressIndex)
if err != nil {
return nil, fmt.Errorf("post[%d]: %w", i, err)
}
if existingIndex, exists := byAccountIndex[encoded.AccountIndex]; exists {
if out[existingIndex].HasPostAmount {
return nil, fmt.Errorf("post[%d].account_index duplicate: %d", i, encoded.AccountIndex)
}
if !rawTxTokenBalanceBinarySameIdentity(out[existingIndex], encoded) {
return nil, fmt.Errorf("post[%d].account_index %d identity mismatch", i, encoded.AccountIndex)
}
out[existingIndex].HasPostAmount = true
out[existingIndex].PostAmount = balance.UITokenAmount.Amount
continue
}
encoded.HasPostAmount = true
encoded.PostAmount = balance.UITokenAmount.Amount
byAccountIndex[encoded.AccountIndex] = len(out)
out = append(out, encoded)
}
return out, nil
}
func rawTxTokenBalanceToBinary(balance TokenBalance, addressIndex *txBinaryAddressIndex) (RawTxTokenBalanceBinary, error) {
mintAccount, ownerAccount, programIDAccount, err := rawTxBinaryTokenBalanceAccounts(balance)
if err != nil {
return RawTxTokenBalanceBinary{}, err
}
mint, err := addressIndex.id(mintAccount)
if err != nil {
return RawTxTokenBalanceBinary{}, fmt.Errorf("mint: %w", err)
}
programID, err := addressIndex.id(programIDAccount)
if err != nil {
return RawTxTokenBalanceBinary{}, fmt.Errorf("program_id: %w", err)
}
if mint > math.MaxUint16 {
return RawTxTokenBalanceBinary{}, fmt.Errorf("mint ref overflows uint16: %d", mint)
}
if programID > math.MaxUint16 {
return RawTxTokenBalanceBinary{}, fmt.Errorf("program_id ref overflows uint16: %d", programID)
}
if balance.UITokenAmount.Decimals > math.MaxUint8 {
return RawTxTokenBalanceBinary{}, fmt.Errorf("decimals overflows uint8: %d", balance.UITokenAmount.Decimals)
}
if balance.AccountIndex < 0 || balance.AccountIndex > math.MaxUint16 {
return RawTxTokenBalanceBinary{}, fmt.Errorf("account_index overflows uint16: %d", balance.AccountIndex)
}
encoded := RawTxTokenBalanceBinary{
AccountIndex: uint16(balance.AccountIndex),
MintAccount: uint16(mint),
ProgramIDAccount: uint16(programID),
Decimals: uint8(balance.UITokenAmount.Decimals),
}
if ownerAccount != nil {
owner, err := addressIndex.id(*ownerAccount)
if err != nil {
return RawTxTokenBalanceBinary{}, fmt.Errorf("owner: %w", err)
}
if owner > math.MaxUint16 {
return RawTxTokenBalanceBinary{}, fmt.Errorf("owner ref overflows uint16: %d", owner)
}
encoded.OwnerAccount = uint16(owner)
encoded.HasOwnerAccount = true
}
return encoded, nil
}
func rawTxTokenBalanceBinarySameIdentity(a, b RawTxTokenBalanceBinary) bool {
return a.AccountIndex == b.AccountIndex &&
a.MintAccount == b.MintAccount &&
a.OwnerAccount == b.OwnerAccount &&
a.HasOwnerAccount == b.HasOwnerAccount &&
a.ProgramIDAccount == b.ProgramIDAccount &&
a.Decimals == b.Decimals
}
func rawTxMetaFromBinary(meta RawTxMetaBinary, addressTable []solana.PublicKey) Meta {
preTokenBalances, postTokenBalances := rawTxTokenBalancesFromBinary(meta.TokenBalances, addressTable)
return Meta{
Err: cloneTransactionParsedError(meta.Err),
Fee: meta.Fee,
InnerInstructions: cloneInnerInstructions(meta.InnerInstructions),
PostBalances: append([]uint64(nil), meta.PostBalances...),
PostTokenBalances: postTokenBalances,
PreBalances: append([]uint64(nil), meta.PreBalances...),
PreTokenBalances: preTokenBalances,
Rewards: nil,
ComputeUnitsConsumed: meta.ComputeUnitsConsumed,
}
}
func rawTxTransactionFromBinary(tx RawTxTransactionBinary, addressTable []solana.PublicKey) Transaction {
out := Transaction{
Message: Message{
Header: tx.Message.Header,
AddressTableLookups: rawTxAddressTableLookupsFromBinary(tx.Message.AddressTableLookups, addressTable),
Instructions: cloneInstructions(tx.Message.Instructions),
},
}
if tx.HasSignature {
out.Signatures = []solana.Signature{tx.Signature}
}
return out
}
func rawTxTokenBalancesFromBinary(balances []RawTxTokenBalanceBinary, addressTable []solana.PublicKey) ([]TokenBalance, []TokenBalance) {
pre := make([]TokenBalance, 0, len(balances))
post := make([]TokenBalance, 0, len(balances))
for _, balance := range balances {
mint, _ := txBinaryAddressAt(addressTable, uint32(balance.MintAccount), "token_balance.mint")
programID, _ := txBinaryAddressAt(addressTable, uint32(balance.ProgramIDAccount), "token_balance.program_id")
var owner *solana.PublicKey
if balance.HasOwnerAccount {
ownerKey, _ := txBinaryAddressAt(addressTable, uint32(balance.OwnerAccount), "token_balance.owner")
owner = &ownerKey
}
if balance.HasPreAmount {
tb := rawTxTokenBalanceFromBinary(balance, balance.PreAmount, mint, owner, programID)
pre = append(pre, tb)
}
if balance.HasPostAmount {
tb := rawTxTokenBalanceFromBinary(balance, balance.PostAmount, mint, owner, programID)
post = append(post, tb)
}
}
return pre, post
}
func rawTxTokenBalanceFromBinary(balance RawTxTokenBalanceBinary, amount string, mint solana.PublicKey, owner *solana.PublicKey, programID solana.PublicKey) TokenBalance {
uiAmountDecimal := rawTxBinaryUIAmountDecimal(amount, balance.Decimals)
uiAmount, _ := uiAmountDecimal.Float64()
tb := TokenBalance{
AccountIndex: int(balance.AccountIndex),
MintAccount: mint,
OwnerAccount: owner,
ProgramIDAccount: programID,
UITokenAmount: UITokenAmount{
Amount: amount,
Decimals: uint64(balance.Decimals),
UIAmount: uiAmount,
UIAmountString: uiAmountDecimal.String(),
},
}
tb.ParseAccount()
return tb
}
func rawTxAddressTableLookupsFromBinary(lookups []RawTxAddressTableLookupBinary, addressTable []solana.PublicKey) solana.MessageAddressTableLookupSlice {
out := make(solana.MessageAddressTableLookupSlice, 0, len(lookups))
for _, lookup := range lookups {
accountKey, _ := txBinaryAddressAt(addressTable, lookup.AccountKey, "address_table_lookup.account_key")
out = append(out, solana.MessageAddressTableLookup{
AccountKey: accountKey,
WritableIndexes: append([]uint8(nil), lookup.WritableIndexes...),
ReadonlyIndexes: append([]uint8(nil), lookup.ReadonlyIndexes...),
})
}
return out
}
func writeRawTxMetaBinary(enc *txBinaryEncoder, meta RawTxMetaBinary) error {
writeTransactionParsedError(enc, meta.Err)
enc.writeUint64(meta.Fee)
if err := writeInnerInstructions(enc, meta.InnerInstructions); err != nil {
return fmt.Errorf("inner_instructions: %w", err)
}
if err := writeLamportBalances(enc, meta.PreBalances, meta.PostBalances); err != nil {
return fmt.Errorf("balances: %w", err)
}
if err := writeTokenBalances(enc, meta.TokenBalances); err != nil {
return fmt.Errorf("token_balances: %w", err)
}
enc.writeUint64(meta.ComputeUnitsConsumed)
return nil
}
func readRawTxMetaBinary(dec txBinaryBodyReader) (RawTxMetaBinary, error) {
var out RawTxMetaBinary
var err error
if out.Err, err = readTransactionParsedError(dec); err != nil {
return out, err
}
if out.Fee, err = dec.readUint64(); err != nil {
return out, err
}
if out.InnerInstructions, err = readInnerInstructions(dec); err != nil {
return out, err
}
if out.PreBalances, out.PostBalances, err = readLamportBalances(dec); err != nil {
return out, err
}
if out.TokenBalances, err = readTokenBalances(dec); err != nil {
return out, err
}
if out.ComputeUnitsConsumed, err = dec.readUint64(); err != nil {
return out, err
}
return out, nil
}
func writeRawTxTransactionBinary(enc *txBinaryEncoder, tx RawTxTransactionBinary) error {
enc.writeBool(tx.HasSignature)
if tx.HasSignature {
enc.writeBytes(tx.Signature[:])
}
writeHeader(enc, tx.Message.Header)
if err := writeInstructions(enc, tx.Message.Instructions); err != nil {
return fmt.Errorf("instructions: %w", err)
}
enc.writeUint32(uint32(len(tx.Message.AddressTableLookups)))
for _, lookup := range tx.Message.AddressTableLookups {
enc.writeUint32(lookup.AccountKey)
writeUint8Slice(enc, lookup.WritableIndexes)
writeUint8Slice(enc, lookup.ReadonlyIndexes)
}
return nil
}
func readRawTxTransactionBinary(dec txBinaryBodyReader) (RawTxTransactionBinary, error) {
var out RawTxTransactionBinary
hasSignature, err := dec.readBool()
if err != nil {
return out, err
}
out.HasSignature = hasSignature
if hasSignature {
raw, err := dec.readN(64)
if err != nil {
return out, err
}
out.Signature = solana.SignatureFromBytes(raw)
}
if out.Message.Header, err = readHeader(dec); err != nil {
return out, err
}
if out.Message.Instructions, err = readInstructions(dec); err != nil {
return out, err
}
lookupCount, err := dec.readUint32()
if err != nil {
return out, err
}
out.Message.AddressTableLookups = make([]RawTxAddressTableLookupBinary, 0, lookupCount)
for i := uint32(0); i < lookupCount; i++ {
lookup := RawTxAddressTableLookupBinary{}
if lookup.AccountKey, err = dec.readUint32(); err != nil {
return out, err
}
if lookup.WritableIndexes, err = readUint8Slice(dec); err != nil {
return out, err
}
if lookup.ReadonlyIndexes, err = readUint8Slice(dec); err != nil {
return out, err
}
out.Message.AddressTableLookups = append(out.Message.AddressTableLookups, lookup)
}
return out, nil
}
func writeTransactionParsedError(enc *txBinaryEncoder, errValue *TransactionParsedError) {
enc.writeBool(errValue != nil)
if errValue == nil {
return
}
enc.writeUint8(errValue.Index)
enc.writeUint32(uint32(errValue.Variant))
enc.writeUint32(uint32(errValue.Enum))
enc.writeUint32(errValue.CustomCode)
}
func readTransactionParsedError(dec txBinaryBodyReader) (*TransactionParsedError, error) {
hasErr, err := dec.readBool()
if err != nil {
return nil, err
}
if !hasErr {
return nil, nil
}
out := &TransactionParsedError{}
if out.Index, err = dec.readUint8(); err != nil {
return nil, err
}
var variant uint32
if variant, err = dec.readUint32(); err != nil {
return nil, err
}
out.Variant = TransactionErrorVariant(variant)
var enum uint32
if enum, err = dec.readUint32(); err != nil {
return nil, err
}
out.Enum = InstructionErrorVariant(enum)
if out.CustomCode, err = dec.readUint32(); err != nil {
return nil, err
}
return out, nil
}
func writeInnerInstructions(enc *txBinaryEncoder, values []InnerInstructions) error {
enc.writeUint32(uint32(len(values)))
for i, value := range values {
enc.writeUint32(uint32(value.Index))
if err := writeInstructions(enc, value.Instructions); err != nil {
return fmt.Errorf("[%d].instructions: %w", i, err)
}
}
return nil
}
func readInnerInstructions(dec txBinaryBodyReader) ([]InnerInstructions, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
out := make([]InnerInstructions, 0, count)
for i := uint32(0); i < count; i++ {
index, err := dec.readUint32()
if err != nil {
return nil, err
}
instructions, err := readInstructions(dec)
if err != nil {
return nil, err
}
out = append(out, InnerInstructions{Index: int(index), Instructions: instructions})
}
return out, nil
}
func writeInstructions(enc *txBinaryEncoder, values []Instruction) error {
enc.writeUint32(uint32(len(values)))
for i, value := range values {
if value.ProgramIDIndex < 0 || value.ProgramIDIndex > math.MaxUint16 {
return fmt.Errorf("[%d].program_id_index overflows uint16: %d", i, value.ProgramIDIndex)
}
enc.writeUint16(uint16(value.ProgramIDIndex))
if err := writeAccountIndexSlice(enc, value.Accounts); err != nil {
return fmt.Errorf("[%d].accounts: %w", i, err)
}
writeByteSlice(enc, value.Data)
enc.writeBool(value.StackHeight != nil)
if value.StackHeight != nil {
enc.writeUint32(uint32(*value.StackHeight))
}
}
return nil
}
func readInstructions(dec txBinaryBodyReader) ([]Instruction, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
out := make([]Instruction, 0, count)
for i := uint32(0); i < count; i++ {
programIDIndex, err := dec.readUint16()
if err != nil {
return nil, err
}
accounts, err := readAccountIndexSlice(dec)
if err != nil {
return nil, err
}
data, err := readByteSlice(dec)
if err != nil {
return nil, err
}
hasStackHeight, err := dec.readBool()
if err != nil {
return nil, err
}
var stackHeight *int
if hasStackHeight {
rawStackHeight, err := dec.readUint32()
if err != nil {
return nil, err
}
sh := int(rawStackHeight)
stackHeight = &sh
}
out = append(out, Instruction{
ProgramIDIndex: int(programIDIndex),
Accounts: accounts,
Data: solana.Base58(data),
StackHeight: stackHeight,
})
}
return out, nil
}
func writeTokenBalances(enc *txBinaryEncoder, values []RawTxTokenBalanceBinary) error {
enc.writeUint32(uint32(len(values)))
for i, value := range values {
enc.writeUint16(value.AccountIndex)
enc.writeUint16(value.MintAccount)
enc.writeBool(value.HasOwnerAccount)
if value.HasOwnerAccount {
enc.writeUint16(value.OwnerAccount)
}
enc.writeUint16(value.ProgramIDAccount)
enc.writeUint8(value.Decimals)
enc.writeBool(value.HasPreAmount)
if value.HasPreAmount {
if err := writeUint256String(enc, value.PreAmount); err != nil {
return fmt.Errorf("[%d].pre_amount: %w", i, err)
}
}
enc.writeBool(value.HasPostAmount)
if value.HasPostAmount {
if err := writeUint256String(enc, value.PostAmount); err != nil {
return fmt.Errorf("[%d].post_amount: %w", i, err)
}
}
}
return nil
}
func readTokenBalances(dec txBinaryBodyReader) ([]RawTxTokenBalanceBinary, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
out := make([]RawTxTokenBalanceBinary, 0, count)
for i := uint32(0); i < count; i++ {
value := RawTxTokenBalanceBinary{}
if value.AccountIndex, err = dec.readUint16(); err != nil {
return nil, err
}
if value.MintAccount, err = dec.readUint16(); err != nil {
return nil, err
}
if value.HasOwnerAccount, err = dec.readBool(); err != nil {
return nil, err
}
if value.HasOwnerAccount {
if value.OwnerAccount, err = dec.readUint16(); err != nil {
return nil, err
}
}
if value.ProgramIDAccount, err = dec.readUint16(); err != nil {
return nil, err
}
if value.Decimals, err = dec.readUint8(); err != nil {
return nil, err
}
if value.HasPreAmount, err = dec.readBool(); err != nil {
return nil, err
}
if value.HasPreAmount {
if value.PreAmount, err = readUint256String(dec); err != nil {
return nil, err
}
}
if value.HasPostAmount, err = dec.readBool(); err != nil {
return nil, err
}
if value.HasPostAmount {
if value.PostAmount, err = readUint256String(dec); err != nil {
return nil, err
}
}
out = append(out, value)
}
return out, nil
}
func writeHeader(enc *txBinaryEncoder, header Header) {
enc.writeUint32(uint32(header.NumReadonlySignedAccounts))
enc.writeUint32(uint32(header.NumReadonlyUnsignedAccounts))
enc.writeUint32(uint32(header.NumRequiredSignatures))
}
func readHeader(dec txBinaryBodyReader) (Header, error) {
var out Header
value, err := dec.readUint32()
if err != nil {
return out, err
}
out.NumReadonlySignedAccounts = int(value)
value, err = dec.readUint32()
if err != nil {
return out, err
}
out.NumReadonlyUnsignedAccounts = int(value)
value, err = dec.readUint32()
if err != nil {
return out, err
}
out.NumRequiredSignatures = int(value)
return out, nil
}
func writeString(enc *txBinaryEncoder, value string) {
writeByteSlice(enc, []byte(value))
}
func readString(dec txBinaryBodyReader) (string, error) {
raw, err := readByteSlice(dec)
if err != nil {
return "", err
}
return string(raw), nil
}
func writeStringSlice(enc *txBinaryEncoder, values []string) {
enc.writeUint32(uint32(len(values)))
for _, value := range values {
writeString(enc, value)
}
}
func readStringSlice(dec txBinaryBodyReader) ([]string, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
out := make([]string, 0, count)
for i := uint32(0); i < count; i++ {
value, err := readString(dec)
if err != nil {
return nil, err
}
out = append(out, value)
}
return out, nil
}
func writeByteSlice(enc *txBinaryEncoder, values []byte) {
enc.writeUint32(uint32(len(values)))
enc.writeBytes(values)
}
func readByteSlice(dec txBinaryBodyReader) ([]byte, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
return dec.readN(int(count))
}
func writeUint256String(enc *txBinaryEncoder, value string) error {
amount, err := rawTxBinaryParseUint256(value)
if err != nil {
return err
}
raw := amount.Bytes()
enc.writeUint8(uint8(len(raw)))
enc.writeBytes(raw)
return nil
}
func readUint256String(dec txBinaryBodyReader) (string, error) {
length, err := dec.readUint8()
if err != nil {
return "", err
}
if length > 32 {
return "", fmt.Errorf("uint256 length exceeds 32 bytes: %d", length)
}
if length == 0 {
return "0", nil
}
raw, err := dec.readN(int(length))
if err != nil {
return "", err
}
return new(big.Int).SetBytes(raw).String(), nil
}
func writeUint8Slice(enc *txBinaryEncoder, values []uint8) {
writeByteSlice(enc, values)
}
func readUint8Slice(dec txBinaryBodyReader) ([]uint8, error) {
return readByteSlice(dec)
}
func writeUint32Slice(enc *txBinaryEncoder, values []uint32) {
enc.writeUint32(uint32(len(values)))
for _, value := range values {
enc.writeUint32(value)
}
}
func readUint32Slice(dec txBinaryBodyReader) ([]uint32, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
out := make([]uint32, 0, count)
for i := uint32(0); i < count; i++ {
value, err := dec.readUint32()
if err != nil {
return nil, err
}
out = append(out, value)
}
return out, nil
}
func writeAccountIndexSlice(enc *txBinaryEncoder, values []int) error {
enc.writeUint32(uint32(len(values)))
for i, value := range values {
if value < 0 || value > math.MaxUint16 {
return fmt.Errorf("[%d] overflows uint16: %d", i, value)
}
enc.writeUint16(uint16(value))
}
return nil
}
func readAccountIndexSlice(dec txBinaryBodyReader) ([]int, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
out := make([]int, 0, count)
for i := uint32(0); i < count; i++ {
value, err := dec.readUint16()
if err != nil {
return nil, err
}
out = append(out, int(value))
}
return out, nil
}
func writeLamportBalances(enc *txBinaryEncoder, preBalances []uint64, postBalances []uint64) error {
if len(preBalances) != len(postBalances) {
return fmt.Errorf("pre/post balance length mismatch: pre=%d post=%d", len(preBalances), len(postBalances))
}
writeUvarint(enc, uint64(len(preBalances)))
for _, value := range preBalances {
writeUvarint(enc, value)
}
changed := 0
for i := range preBalances {
if preBalances[i] != postBalances[i] {
changed++
}
}
writeUvarint(enc, uint64(changed))
for i := range preBalances {
if preBalances[i] == postBalances[i] {
continue
}
writeUvarint(enc, uint64(i))
if err := writeLamportDelta(enc, preBalances[i], postBalances[i]); err != nil {
return fmt.Errorf("balance[%d]: %w", i, err)
}
}
return nil
}
func readLamportBalances(dec txBinaryBodyReader) ([]uint64, []uint64, error) {
count, err := readUvarint(dec)
if err != nil {
return nil, nil, err
}
if count > uint64(math.MaxUint32) {
return nil, nil, fmt.Errorf("balance count too large: %d", count)
}
preBalances := make([]uint64, 0, count)
for i := uint64(0); i < count; i++ {
value, err := readUvarint(dec)
if err != nil {
return nil, nil, err
}
preBalances = append(preBalances, value)
}
postBalances := append([]uint64(nil), preBalances...)
changed, err := readUvarint(dec)
if err != nil {
return nil, nil, err
}
for i := uint64(0); i < changed; i++ {
index, err := readUvarint(dec)
if err != nil {
return nil, nil, err
}
if index >= uint64(len(postBalances)) {
return nil, nil, fmt.Errorf("post balance changed index out of range: %d", index)
}
value, err := readLamportDelta(dec, preBalances[index])
if err != nil {
return nil, nil, fmt.Errorf("balance[%d]: %w", index, err)
}
postBalances[index] = value
}
return preBalances, postBalances, nil
}
func writeLamportDelta(enc *txBinaryEncoder, pre uint64, post uint64) error {
const maxInt64Uint = uint64(1<<63 - 1)
var encoded uint64
if post >= pre {
diff := post - pre
if diff > maxInt64Uint {
return fmt.Errorf("positive delta overflows int64: %d", diff)
}
encoded = diff << 1
} else {
diff := pre - post
if diff > maxInt64Uint {
return fmt.Errorf("negative delta overflows int64: %d", diff)
}
encoded = (diff << 1) | 1
}
writeUvarint(enc, encoded)
return nil
}
func readLamportDelta(dec txBinaryBodyReader, pre uint64) (uint64, error) {
encoded, err := readUvarint(dec)
if err != nil {
return 0, err
}
diff := encoded >> 1
if encoded&1 == 0 {
if ^uint64(0)-pre < diff {
return 0, fmt.Errorf("positive delta overflows uint64: pre=%d delta=%d", pre, diff)
}
return pre + diff, nil
}
if pre < diff {
return 0, fmt.Errorf("negative delta underflows uint64: pre=%d delta=%d", pre, diff)
}
return pre - diff, nil
}
func writeUvarint(enc *txBinaryEncoder, value uint64) {
var raw [binary.MaxVarintLen64]byte
n := binary.PutUvarint(raw[:], value)
enc.writeBytes(raw[:n])
}
func readUvarint(dec txBinaryBodyReader) (uint64, error) {
var value uint64
for shift := uint(0); shift < 64; shift += 7 {
b, err := dec.readUint8()
if err != nil {
return 0, err
}
if b < 0x80 {
if shift == 63 && b > 1 {
return 0, fmt.Errorf("uvarint overflows uint64")
}
return value | uint64(b)<<shift, nil
}
value |= uint64(b&0x7f) << shift
}
return 0, fmt.Errorf("uvarint overflows uint64")
}
func writeUint64Slice(enc *txBinaryEncoder, values []uint64) {
enc.writeUint32(uint32(len(values)))
for _, value := range values {
enc.writeUint64(value)
}
}
func readUint64Slice(dec txBinaryBodyReader) ([]uint64, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
out := make([]uint64, 0, count)
for i := uint32(0); i < count; i++ {
value, err := dec.readUint64()
if err != nil {
return nil, err
}
out = append(out, value)
}
return out, nil
}
func writeIntSlice(enc *txBinaryEncoder, values []int) {
enc.writeUint32(uint32(len(values)))
for _, value := range values {
enc.writeUint32(uint32(value))
}
}
func readIntSlice(dec txBinaryBodyReader) ([]int, error) {
count, err := dec.readUint32()
if err != nil {
return nil, err
}
out := make([]int, 0, count)
for i := uint32(0); i < count; i++ {
value, err := dec.readUint32()
if err != nil {
return nil, err
}
out = append(out, int(value))
}
return out, nil
}
func readInt64(dec txBinaryBodyReader) (int64, error) {
value, err := dec.readUint64()
if err != nil {
return 0, err
}
return int64(value), nil
}
func rawTxBinaryBuildAddressTable(txs []*RawTx) ([]solana.PublicKey, error) {
builder := txBinaryAddressTableBuilder{index: make(map[solana.PublicKey]struct{})}
for txIndex, tx := range txs {
if tx == nil {
return nil, fmt.Errorf("tx[%d] is nil", txIndex)
}
for accountIndex, account := range tx.getAccountList() {
if err := builder.add(account); err != nil {
return nil, fmt.Errorf("tx[%d].account_list[%d]: %w", txIndex, accountIndex, err)
}
}
for lookupIndex, lookup := range tx.Transaction.Message.AddressTableLookups {
if err := builder.add(lookup.AccountKey); err != nil {
return nil, fmt.Errorf("tx[%d].address_table_lookups[%d].account_key: %w", txIndex, lookupIndex, err)
}
}
for balanceIndex, balance := range tx.Meta.PreTokenBalances {
if err := rawTxBinaryAddTokenBalanceAddresses(&builder, balance); err != nil {
return nil, fmt.Errorf("tx[%d].pre_token_balances[%d]: %w", txIndex, balanceIndex, err)
}
}
for balanceIndex, balance := range tx.Meta.PostTokenBalances {
if err := rawTxBinaryAddTokenBalanceAddresses(&builder, balance); err != nil {
return nil, fmt.Errorf("tx[%d].post_token_balances[%d]: %w", txIndex, balanceIndex, err)
}
}
}
return builder.addresses, nil
}
func rawTxBinarySharedBlockTime(txs []*RawTx, field string) (int64, error) {
if len(txs) == 0 {
return 0, nil
}
blockTime := txs[0].BlockTime
for i, tx := range txs {
if tx.BlockTime != blockTime {
return 0, fmt.Errorf("%s[%d] block time mismatch: got %d want %d", field, i, tx.BlockTime, blockTime)
}
}
return blockTime, nil
}
func rawTxBinaryAddTokenBalanceAddresses(builder *txBinaryAddressTableBuilder, balance TokenBalance) error {
mintAccount, ownerAccount, programIDAccount, err := rawTxBinaryTokenBalanceAccounts(balance)
if err != nil {
return err
}
if err := builder.add(mintAccount); err != nil {
return fmt.Errorf("mint: %w", err)
}
if ownerAccount != nil {
if err := builder.add(*ownerAccount); err != nil {
return fmt.Errorf("owner: %w", err)
}
}
if err := builder.add(programIDAccount); err != nil {
return fmt.Errorf("program_id: %w", err)
}
return nil
}
func rawTxBinaryTokenBalanceAccounts(balance TokenBalance) (solana.PublicKey, *solana.PublicKey, solana.PublicKey, error) {
mintAccount := balance.MintAccount
if mintAccount.IsZero() && balance.Mint != "" {
parsed, err := solana.PublicKeyFromBase58(balance.Mint)
if err != nil {
return solana.PublicKey{}, nil, solana.PublicKey{}, fmt.Errorf("mint: %w", err)
}
mintAccount = parsed
}
ownerAccount := balance.OwnerAccount
if ownerAccount == nil && balance.Owner != "" {
parsed, err := solana.PublicKeyFromBase58(balance.Owner)
if err != nil {
return solana.PublicKey{}, nil, solana.PublicKey{}, fmt.Errorf("owner: %w", err)
}
ownerAccount = &parsed
}
programIDAccount := balance.ProgramIDAccount
if programIDAccount.IsZero() && balance.ProgramID != "" {
parsed, err := solana.PublicKeyFromBase58(balance.ProgramID)
if err != nil {
return solana.PublicKey{}, nil, solana.PublicKey{}, fmt.Errorf("program_id: %w", err)
}
programIDAccount = parsed
}
return mintAccount, ownerAccount, programIDAccount, nil
}
func rawTxBinaryResolveAccountList(addressTable []solana.PublicKey, refs []uint32) ([]solana.PublicKey, error) {
out := make([]solana.PublicKey, 0, len(refs))
for i, ref := range refs {
address, err := txBinaryAddressAt(addressTable, ref, fmt.Sprintf("account_list[%d]", i))
if err != nil {
return nil, err
}
out = append(out, address)
}
return out, nil
}
func cloneTransactionParsedError(errValue *TransactionParsedError) *TransactionParsedError {
if errValue == nil {
return nil
}
out := *errValue
return &out
}
func cloneInnerInstructions(values []InnerInstructions) []InnerInstructions {
out := make([]InnerInstructions, 0, len(values))
for _, value := range values {
out = append(out, InnerInstructions{
Index: value.Index,
Instructions: cloneInstructions(value.Instructions),
})
}
return out
}
func cloneInstructions(values []Instruction) []Instruction {
out := make([]Instruction, 0, len(values))
for _, value := range values {
cloned := Instruction{
Accounts: append([]int(nil), value.Accounts...),
Data: append(solana.Base58(nil), value.Data...),
ProgramIDIndex: value.ProgramIDIndex,
}
if value.StackHeight != nil {
stackHeight := *value.StackHeight
cloned.StackHeight = &stackHeight
}
out = append(out, cloned)
}
return out
}
func rawTxBinaryVersionID(version interface{}) uint8 {
switch value := version.(type) {
case solana.MessageVersion:
if value == solana.MessageVersionV0 {
return 1
}
return 0
case int:
if value == int(solana.MessageVersionV0) {
return 1
}
case uint64:
if value == uint64(solana.MessageVersionV0) {
return 1
}
case float64:
if value == float64(solana.MessageVersionV0) {
return 1
}
case string:
if value == "0" {
return 1
}
}
return 0
}
func rawTxBinaryVersionValue(version uint8) interface{} {
if version == 1 {
return solana.MessageVersionV0
}
return solana.MessageVersionLegacy
}
func rawTxBinaryParseUint256(value string) (*big.Int, error) {
if value == "" {
value = "0"
}
amount, ok := new(big.Int).SetString(value, 10)
if !ok {
return nil, fmt.Errorf("invalid uint256 decimal: %q", value)
}
if amount.Sign() < 0 {
return nil, fmt.Errorf("uint256 must be >= 0: %s", value)
}
if amount.BitLen() > 256 {
return nil, fmt.Errorf("uint256 overflow: %s", value)
}
return amount, nil
}
func rawTxBinaryUIAmountDecimal(amount string, decimals uint8) decimal.Decimal {
rawAmount, err := rawTxBinaryParseUint256(amount)
if err != nil {
return decimal.Zero
}
return decimal.NewFromBigInt(rawAmount, -int32(decimals))
}