1813 lines
53 KiB
Go
1813 lines
53 KiB
Go
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 = 10
|
|
|
|
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 uint8
|
|
MintAccount uint8
|
|
OwnerAccount uint8
|
|
HasOwnerAccount bool
|
|
ProgramIDAccount uint8
|
|
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, err := rawTxBinaryEffectiveAccountList(tx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if uint64(len(accountList)) > uint64(math.MaxUint32) {
|
|
return nil, fmt.Errorf("account list exceeds uint32 capacity")
|
|
}
|
|
accountListIndex, err := newRawTxBinaryAccountListIndex(accountList)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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, accountListIndex)
|
|
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, accountList),
|
|
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, accountListIndex map[solana.PublicKey]uint8) (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, accountListIndex)
|
|
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, accountListIndex map[solana.PublicKey]uint8) ([]RawTxTokenBalanceBinary, error) {
|
|
out := make([]RawTxTokenBalanceBinary, 0, len(preBalances)+len(postBalances))
|
|
preByAccountIndex := make(map[uint8]int, len(preBalances))
|
|
postSeenByAccountIndex := make(map[uint8]struct{}, len(postBalances))
|
|
for i, balance := range preBalances {
|
|
encoded, err := rawTxTokenBalanceToBinary(balance, accountListIndex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("pre[%d]: %w", i, err)
|
|
}
|
|
if _, exists := preByAccountIndex[encoded.AccountIndex]; exists {
|
|
return nil, fmt.Errorf("pre[%d].account_index duplicate: %d", i, encoded.AccountIndex)
|
|
}
|
|
encoded.HasPreAmount = true
|
|
encoded.PreAmount = balance.UITokenAmount.Amount
|
|
preByAccountIndex[encoded.AccountIndex] = len(out)
|
|
out = append(out, encoded)
|
|
}
|
|
for i, balance := range postBalances {
|
|
encoded, err := rawTxTokenBalanceToBinary(balance, accountListIndex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("post[%d]: %w", i, err)
|
|
}
|
|
if _, exists := postSeenByAccountIndex[encoded.AccountIndex]; exists {
|
|
return nil, fmt.Errorf("post[%d].account_index duplicate: %d", i, encoded.AccountIndex)
|
|
}
|
|
postSeenByAccountIndex[encoded.AccountIndex] = struct{}{}
|
|
if existingIndex, exists := preByAccountIndex[encoded.AccountIndex]; exists && rawTxTokenBalanceBinarySameIdentity(out[existingIndex], encoded) {
|
|
out[existingIndex].HasPostAmount = true
|
|
out[existingIndex].PostAmount = balance.UITokenAmount.Amount
|
|
continue
|
|
}
|
|
encoded.HasPostAmount = true
|
|
encoded.PostAmount = balance.UITokenAmount.Amount
|
|
out = append(out, encoded)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func rawTxTokenBalanceToBinary(balance TokenBalance, accountListIndex map[solana.PublicKey]uint8) (RawTxTokenBalanceBinary, error) {
|
|
mintAccount, ownerAccount, programIDAccount, err := rawTxBinaryTokenBalanceAccounts(balance)
|
|
if err != nil {
|
|
return RawTxTokenBalanceBinary{}, err
|
|
}
|
|
mint, ok := accountListIndex[mintAccount]
|
|
if !ok {
|
|
return RawTxTokenBalanceBinary{}, fmt.Errorf("mint account not found in account_list: %s", mintAccount)
|
|
}
|
|
programID, ok := accountListIndex[programIDAccount]
|
|
if !ok {
|
|
return RawTxTokenBalanceBinary{}, fmt.Errorf("program_id account not found in account_list: %s", programIDAccount)
|
|
}
|
|
if balance.UITokenAmount.Decimals > math.MaxUint8 {
|
|
return RawTxTokenBalanceBinary{}, fmt.Errorf("decimals overflows uint8: %d", balance.UITokenAmount.Decimals)
|
|
}
|
|
if balance.AccountIndex < 0 || balance.AccountIndex > math.MaxUint8 {
|
|
return RawTxTokenBalanceBinary{}, fmt.Errorf("account_index overflows uint8: %d", balance.AccountIndex)
|
|
}
|
|
encoded := RawTxTokenBalanceBinary{
|
|
AccountIndex: uint8(balance.AccountIndex),
|
|
MintAccount: mint,
|
|
ProgramIDAccount: programID,
|
|
Decimals: uint8(balance.UITokenAmount.Decimals),
|
|
}
|
|
if ownerAccount != nil {
|
|
owner, ok := accountListIndex[*ownerAccount]
|
|
if !ok {
|
|
return RawTxTokenBalanceBinary{}, fmt.Errorf("owner account not found in account_list: %s", *ownerAccount)
|
|
}
|
|
encoded.OwnerAccount = 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, accountList []solana.PublicKey) Meta {
|
|
preTokenBalances, postTokenBalances := rawTxTokenBalancesFromBinary(meta.TokenBalances, accountList)
|
|
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, accountList []solana.PublicKey) ([]TokenBalance, []TokenBalance) {
|
|
pre := make([]TokenBalance, 0, len(balances))
|
|
post := make([]TokenBalance, 0, len(balances))
|
|
for _, balance := range balances {
|
|
mint, _ := txBinaryAddressAt(accountList, uint32(balance.MintAccount), "token_balance.mint")
|
|
programID, _ := txBinaryAddressAt(accountList, uint32(balance.ProgramIDAccount), "token_balance.program_id")
|
|
var owner *solana.PublicKey
|
|
if balance.HasOwnerAccount {
|
|
ownerKey, _ := txBinaryAddressAt(accountList, 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.MaxUint8 {
|
|
return fmt.Errorf("[%d].program_id_index overflows uint8: %d", i, value.ProgramIDIndex)
|
|
}
|
|
enc.writeUint8(uint8(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))
|
|
}
|
|
enc.writeUint32(uint32(len(value.LogEvents)))
|
|
for _, event := range value.LogEvents {
|
|
writeByteSlice(enc, event)
|
|
}
|
|
}
|
|
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.readUint8()
|
|
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
|
|
}
|
|
logEventCount, err := dec.readUint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logEvents := make([]solana.Base64, 0, logEventCount)
|
|
for j := uint32(0); j < logEventCount; j++ {
|
|
eventData, err := readByteSlice(dec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logEvents = append(logEvents, solana.Base64(eventData))
|
|
}
|
|
out = append(out, Instruction{
|
|
ProgramIDIndex: int(programIDIndex),
|
|
Accounts: accounts,
|
|
Data: solana.Base58(data),
|
|
StackHeight: stackHeight,
|
|
LogEvents: logEvents,
|
|
})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func writeTokenBalances(enc *txBinaryEncoder, values []RawTxTokenBalanceBinary) error {
|
|
enc.writeUint32(uint32(len(values)))
|
|
for i, value := range values {
|
|
enc.writeUint8(value.AccountIndex)
|
|
enc.writeUint8(value.MintAccount)
|
|
enc.writeBool(value.HasOwnerAccount)
|
|
if value.HasOwnerAccount {
|
|
enc.writeUint8(value.OwnerAccount)
|
|
}
|
|
enc.writeUint8(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.readUint8(); err != nil {
|
|
return nil, err
|
|
}
|
|
if value.MintAccount, err = dec.readUint8(); err != nil {
|
|
return nil, err
|
|
}
|
|
if value.HasOwnerAccount, err = dec.readBool(); err != nil {
|
|
return nil, err
|
|
}
|
|
if value.HasOwnerAccount {
|
|
if value.OwnerAccount, err = dec.readUint8(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if value.ProgramIDAccount, err = dec.readUint8(); 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.MaxUint8 {
|
|
return fmt.Errorf("[%d] overflows uint8: %d", i, value)
|
|
}
|
|
enc.writeUint8(uint8(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.readUint8()
|
|
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)
|
|
}
|
|
accountList, err := rawTxBinaryEffectiveAccountList(tx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tx[%d].account_list: %w", txIndex, err)
|
|
}
|
|
for accountIndex, account := range accountList {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
return builder.addresses, nil
|
|
}
|
|
|
|
func rawTxBinaryEffectiveAccountList(tx *RawTx) ([]solana.PublicKey, error) {
|
|
accountList := append([]solana.PublicKey(nil), tx.getAccountList()...)
|
|
seen := make(map[solana.PublicKey]struct{}, len(accountList))
|
|
for _, account := range accountList {
|
|
seen[account] = struct{}{}
|
|
}
|
|
for balanceIndex, balance := range tx.Meta.PreTokenBalances {
|
|
if err := rawTxBinaryAppendTokenBalanceAccounts(&accountList, seen, balance); err != nil {
|
|
return nil, fmt.Errorf("pre_token_balances[%d]: %w", balanceIndex, err)
|
|
}
|
|
}
|
|
for balanceIndex, balance := range tx.Meta.PostTokenBalances {
|
|
if err := rawTxBinaryAppendTokenBalanceAccounts(&accountList, seen, balance); err != nil {
|
|
return nil, fmt.Errorf("post_token_balances[%d]: %w", balanceIndex, err)
|
|
}
|
|
}
|
|
return accountList, nil
|
|
}
|
|
|
|
func rawTxBinaryAppendTokenBalanceAccounts(accountList *[]solana.PublicKey, seen map[solana.PublicKey]struct{}, balance TokenBalance) error {
|
|
mintAccount, ownerAccount, programIDAccount, err := rawTxBinaryTokenBalanceAccounts(balance)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rawTxBinaryAppendAccountIfMissing(accountList, seen, mintAccount)
|
|
if ownerAccount != nil {
|
|
rawTxBinaryAppendAccountIfMissing(accountList, seen, *ownerAccount)
|
|
}
|
|
rawTxBinaryAppendAccountIfMissing(accountList, seen, programIDAccount)
|
|
return nil
|
|
}
|
|
|
|
func rawTxBinaryAppendAccountIfMissing(accountList *[]solana.PublicKey, seen map[solana.PublicKey]struct{}, account solana.PublicKey) {
|
|
if _, exists := seen[account]; exists {
|
|
return
|
|
}
|
|
seen[account] = struct{}{}
|
|
*accountList = append(*accountList, account)
|
|
}
|
|
|
|
func newRawTxBinaryAccountListIndex(accountList []solana.PublicKey) (map[solana.PublicKey]uint8, error) {
|
|
out := make(map[solana.PublicKey]uint8, len(accountList))
|
|
for i, account := range accountList {
|
|
if i > math.MaxUint8 {
|
|
return nil, fmt.Errorf("account_list index overflows uint8: %d", i)
|
|
}
|
|
if _, exists := out[account]; exists {
|
|
continue
|
|
}
|
|
out[account] = uint8(i)
|
|
}
|
|
return out, 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,
|
|
LogEvents: cloneBase64Slice(value.LogEvents),
|
|
}
|
|
if value.StackHeight != nil {
|
|
stackHeight := *value.StackHeight
|
|
cloned.StackHeight = &stackHeight
|
|
}
|
|
out = append(out, cloned)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cloneBase64Slice(values []solana.Base64) []solana.Base64 {
|
|
out := make([]solana.Base64, 0, len(values))
|
|
for _, value := range values {
|
|
out = append(out, append(solana.Base64(nil), value...))
|
|
}
|
|
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))
|
|
}
|