2026-04-16 16:40:21 +08:00
|
|
|
package pump_parser
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-16 17:56:17 +08:00
|
|
|
"bufio"
|
2026-04-16 16:40:21 +08:00
|
|
|
"bytes"
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"iter"
|
|
|
|
|
"math"
|
|
|
|
|
"sort"
|
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
|
|
"github.com/gagliardetto/solana-go"
|
2026-04-20 16:26:55 +08:00
|
|
|
"github.com/mr-tron/base58"
|
2026-04-16 16:40:21 +08:00
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2026-06-05 10:35:49 +08:00
|
|
|
txBinarySchemaVersionV3 uint16 = 3
|
|
|
|
|
txBinarySchemaVersionCurrent uint16 = 4
|
2026-04-16 16:40:21 +08:00
|
|
|
txBinaryEnumVersionV1 uint16 = 1
|
|
|
|
|
|
|
|
|
|
txBinarySOLScale int32 = 9
|
|
|
|
|
txBinaryCUPriceScale int32 = 6
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var txBinaryMagic = [4]byte{'P', 'T', 'X', 'B'}
|
|
|
|
|
var txsBinaryMagic = [4]byte{'P', 'T', 'X', 'S'}
|
|
|
|
|
|
2026-06-05 10:35:49 +08:00
|
|
|
func txBinarySchemaVersionSupported(version uint16) bool {
|
|
|
|
|
return version >= txBinarySchemaVersionV3 && version <= txBinarySchemaVersionCurrent
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
type TxBinary struct {
|
|
|
|
|
SchemaVersion uint16
|
|
|
|
|
EnumVersion uint16
|
|
|
|
|
AddressTable []solana.PublicKey
|
|
|
|
|
Signer uint32
|
|
|
|
|
Block uint64
|
|
|
|
|
BlockIndex uint64
|
2026-06-05 10:35:49 +08:00
|
|
|
BlockAt int64
|
2026-04-16 16:40:21 +08:00
|
|
|
TxHash *[64]byte
|
|
|
|
|
CuFee uint64
|
|
|
|
|
Swaps []SwapBinary
|
|
|
|
|
Platform []PlatformBinary
|
|
|
|
|
MevAgent []MevAgentBinary
|
|
|
|
|
CUPrice uint64
|
|
|
|
|
BeforeSolBalance float64
|
|
|
|
|
AfterSOLBalance float64
|
|
|
|
|
ComputeUnitsConsumed uint64
|
|
|
|
|
CuLimit uint32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type SwapBinary struct {
|
|
|
|
|
Program string
|
|
|
|
|
Event string
|
|
|
|
|
|
|
|
|
|
TxIndex int32
|
|
|
|
|
|
|
|
|
|
InstrIdx uint8
|
|
|
|
|
InnerIdx uint8
|
|
|
|
|
|
|
|
|
|
Pool uint32
|
|
|
|
|
BaseMint uint32
|
|
|
|
|
QuoteMint uint32
|
|
|
|
|
BaseTokenProgram uint32
|
|
|
|
|
QuoteTokenProgram uint32
|
|
|
|
|
Creator uint32
|
|
|
|
|
|
|
|
|
|
BaseMintDecimals uint8
|
|
|
|
|
QuoteMintDecimals uint8
|
|
|
|
|
|
|
|
|
|
User uint32
|
|
|
|
|
BaseAmount uint64
|
|
|
|
|
QuoteAmount uint64
|
|
|
|
|
|
|
|
|
|
SwapMode SwapMode
|
|
|
|
|
FixedAmount uint64
|
|
|
|
|
FixedAmountSide SwapAmountSide
|
|
|
|
|
FixedMint uint32
|
|
|
|
|
LimitAmountType SwapLimitType
|
|
|
|
|
LimitAmount uint64
|
|
|
|
|
LimitAmountSide SwapAmountSide
|
|
|
|
|
LimitMint uint32
|
|
|
|
|
ActualLimitAmount uint64
|
|
|
|
|
ActualLimitAmountSide SwapAmountSide
|
|
|
|
|
SlippageBps uint64
|
|
|
|
|
|
2026-04-20 15:09:42 +08:00
|
|
|
BaseReserve float64
|
|
|
|
|
QuoteReserve float64
|
2026-04-16 16:40:21 +08:00
|
|
|
Mayhem bool
|
|
|
|
|
Cashback bool
|
|
|
|
|
|
|
|
|
|
UserBaseBalance uint64
|
|
|
|
|
UserQuoteBalance uint64
|
|
|
|
|
EntryContract uint32
|
|
|
|
|
|
|
|
|
|
MigrateToPool uint32
|
|
|
|
|
MigrateTopProgram uint32
|
|
|
|
|
|
|
|
|
|
LpMint uint32
|
|
|
|
|
|
|
|
|
|
AfterSOLBalance float64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TxsBinary struct {
|
|
|
|
|
SchemaVersion uint16
|
|
|
|
|
EnumVersion uint16
|
|
|
|
|
AddressTable []solana.PublicKey
|
|
|
|
|
Txs []TxBinary
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TxsBinaryReaderSource interface {
|
|
|
|
|
OpenTxsBinaryReader() (io.ReadCloser, error)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 17:56:17 +08:00
|
|
|
type TxsBinaryBatchHeaderContext struct {
|
|
|
|
|
SourceIndex int
|
|
|
|
|
BatchIndex int
|
|
|
|
|
Reader *bufio.Reader
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TxsBinaryBatchHeaderFunc func(ctx *TxsBinaryBatchHeaderContext) (skip bool, err error)
|
|
|
|
|
|
|
|
|
|
type TxsBinaryMergeOptions struct {
|
|
|
|
|
BatchHeaderFunc TxsBinaryBatchHeaderFunc
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
type PlatformBinary struct {
|
|
|
|
|
Platform string
|
|
|
|
|
PlatformFee uint64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type MevAgentBinary struct {
|
|
|
|
|
MevAgent string
|
|
|
|
|
MevAgentFee uint64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txBinaryBytesSource struct {
|
|
|
|
|
data []byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txsBinaryMergePlan struct {
|
|
|
|
|
schemaVersion uint16
|
|
|
|
|
enumVersion uint16
|
|
|
|
|
enumTable *txBinaryEnumTable
|
|
|
|
|
addressTable []solana.PublicKey
|
|
|
|
|
addressIndex *txBinaryAddressIndex
|
|
|
|
|
txCount uint32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s txBinaryBytesSource) OpenTxsBinaryReader() (io.ReadCloser, error) {
|
|
|
|
|
return io.NopCloser(bytes.NewReader(s.data)), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewTxBinary(tx *Tx) (*TxBinary, error) {
|
|
|
|
|
if tx == nil {
|
|
|
|
|
return nil, fmt.Errorf("tx is nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addressTable, err := txBinaryBuildAddressTable([]*Tx{tx})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
addressIndex, err := newTxBinaryAddressIndex(addressTable)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return newTxBinaryWithAddressTable(tx, addressTable, addressIndex)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewTxsBinary(txs []Tx) (*TxsBinary, error) {
|
|
|
|
|
txPtrs := make([]*Tx, 0, len(txs))
|
|
|
|
|
for i := range txs {
|
|
|
|
|
txPtrs = append(txPtrs, &txs[i])
|
|
|
|
|
}
|
|
|
|
|
addressTable, err := txBinaryBuildAddressTable(txPtrs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
addressIndex, err := newTxBinaryAddressIndex(addressTable)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out := &TxsBinary{
|
|
|
|
|
SchemaVersion: txBinarySchemaVersionCurrent,
|
|
|
|
|
EnumVersion: txBinaryEnumVersionV1,
|
|
|
|
|
AddressTable: addressTable,
|
|
|
|
|
Txs: make([]TxBinary, 0, len(txPtrs)),
|
|
|
|
|
}
|
|
|
|
|
for i, tx := range txPtrs {
|
|
|
|
|
binaryTx, err := newTxBinaryWithAddressTable(tx, addressTable, addressIndex)
|
|
|
|
|
if err != nil {
|
2026-04-20 16:26:55 +08:00
|
|
|
return nil, fmt.Errorf("tx[%d], %s: %w", i, base58.Encode(tx.TxHash[:]), err)
|
2026-04-16 16:40:21 +08:00
|
|
|
}
|
|
|
|
|
out.Txs = append(out.Txs, *binaryTx)
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newTxBinaryWithAddressTable(tx *Tx, addressTable []solana.PublicKey, addressIndex *txBinaryAddressIndex) (*TxBinary, error) {
|
|
|
|
|
if tx == nil {
|
|
|
|
|
return nil, fmt.Errorf("tx is nil")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out := &TxBinary{
|
|
|
|
|
SchemaVersion: txBinarySchemaVersionCurrent,
|
|
|
|
|
EnumVersion: txBinaryEnumVersionV1,
|
|
|
|
|
Block: tx.Block,
|
|
|
|
|
BlockIndex: tx.BlockIndex,
|
2026-06-05 10:35:49 +08:00
|
|
|
BlockAt: tx.BlockAt,
|
2026-04-16 16:40:21 +08:00
|
|
|
CuLimit: tx.CuLimit,
|
|
|
|
|
ComputeUnitsConsumed: tx.ComputeUnitsConsumed,
|
|
|
|
|
}
|
|
|
|
|
if tx.TxHash != nil {
|
|
|
|
|
txHash := *tx.TxHash
|
|
|
|
|
out.TxHash = &txHash
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
if out.CuFee, err = txBinaryDecimalToUint64(tx.CuFee, "tx.cu_fee"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if out.CUPrice, err = txBinaryScaledDecimalToUint64(tx.CUPrice, txBinaryCUPriceScale, "tx.cu_price"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if out.BeforeSolBalance, err = txBinaryDecimalToFloat64(tx.BeforeSolBalance, txBinarySOLScale, "tx.before_sol_balance"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if out.AfterSOLBalance, err = txBinaryDecimalToFloat64(tx.AfterSOLBalance, txBinarySOLScale, "tx.after_sol_balance"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out.Platform, err = txBinaryPlatformsFromTx(tx.Platform)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out.MevAgent, err = txBinaryMevAgentsFromTx(tx.MevAgent)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out.AddressTable = addressTable
|
|
|
|
|
if out.Signer, err = addressIndex.id(tx.Signer); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("tx.signer: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out.Swaps = make([]SwapBinary, 0, len(tx.Swaps))
|
|
|
|
|
for i, swap := range tx.Swaps {
|
|
|
|
|
encodedSwap, err := newSwapBinary(swap, i, addressIndex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out.Swaps = append(out.Swaps, encodedSwap)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func EncodeTxBinary(tx *Tx) ([]byte, error) {
|
|
|
|
|
binaryTx, err := NewTxBinary(tx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return binaryTx.MarshalBinary()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func EncodeTxsBinary(txs []Tx) ([]byte, error) {
|
|
|
|
|
binaryTxs, err := NewTxsBinary(txs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return binaryTxs.MarshalBinary()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DecodeTxBinary(data []byte) (*Tx, error) {
|
|
|
|
|
var binaryTx TxBinary
|
|
|
|
|
if err := binaryTx.UnmarshalBinary(data); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return binaryTx.ToTx()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DecodeTxsBinary(data []byte) ([]*Tx, error) {
|
|
|
|
|
var binaryTxs TxsBinary
|
|
|
|
|
if err := binaryTxs.UnmarshalBinary(data); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return binaryTxs.ToTxs()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DecodeTxsBinaryReader(r io.Reader) iter.Seq2[*Tx, error] {
|
|
|
|
|
return func(yield func(*Tx, error) bool) {
|
|
|
|
|
if r == nil {
|
|
|
|
|
yield(nil, fmt.Errorf("txs binary reader is nil"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dec := txBinaryStreamDecoder{reader: r}
|
|
|
|
|
header, err := dec.readTxsBinaryHeader()
|
|
|
|
|
if err != nil {
|
|
|
|
|
yield(nil, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := uint32(0); i < header.count; i++ {
|
|
|
|
|
tx := TxBinary{
|
|
|
|
|
SchemaVersion: header.schemaVersion,
|
|
|
|
|
EnumVersion: header.enumVersion,
|
|
|
|
|
AddressTable: header.addressTable,
|
|
|
|
|
}
|
|
|
|
|
if err := txBinaryReadTxBody(&dec, &tx, header.enumTable, header.addressTable); err != nil {
|
|
|
|
|
yield(nil, fmt.Errorf("tx[%d]: %w", i, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
decodedTx, err := tx.ToTx()
|
|
|
|
|
if err != nil {
|
|
|
|
|
yield(nil, fmt.Errorf("tx[%d]: %w", i, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !yield(decodedTx, nil) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func MergeTxsBinaryBytes(encodedBatches [][]byte) ([]byte, error) {
|
2026-04-16 17:56:17 +08:00
|
|
|
return MergeTxsBinaryBytesWithOptions(encodedBatches, TxsBinaryMergeOptions{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func MergeTxsBinaryBytesWithOptions(encodedBatches [][]byte, opts TxsBinaryMergeOptions) ([]byte, error) {
|
2026-04-16 16:40:21 +08:00
|
|
|
sources := make([]TxsBinaryReaderSource, 0, len(encodedBatches))
|
|
|
|
|
for _, encoded := range encodedBatches {
|
|
|
|
|
sources = append(sources, txBinaryBytesSource{data: encoded})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var out bytes.Buffer
|
2026-04-16 17:56:17 +08:00
|
|
|
if err := MergeTxsBinarySourcesToWriterWithOptions(sources, &out, opts); err != nil {
|
2026-04-16 16:40:21 +08:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return out.Bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func MergeTxsBinarySourcesToWriter(sources []TxsBinaryReaderSource, w io.Writer) error {
|
2026-04-16 17:56:17 +08:00
|
|
|
return MergeTxsBinarySourcesToWriterWithOptions(sources, w, TxsBinaryMergeOptions{})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func MergeTxsBinarySourcesToWriterWithOptions(sources []TxsBinaryReaderSource, w io.Writer, opts TxsBinaryMergeOptions) error {
|
2026-04-16 16:40:21 +08:00
|
|
|
if w == nil {
|
|
|
|
|
return fmt.Errorf("txs binary writer is nil")
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 17:56:17 +08:00
|
|
|
plan, err := txBinaryBuildMergePlan(sources, opts)
|
2026-04-16 16:40:21 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
headerBytes, err := txBinaryMarshalTxsHeader(plan.schemaVersion, plan.enumVersion, plan.addressTable, plan.txCount)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := txBinaryWriteAll(w, headerBytes); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for sourceIndex, source := range sources {
|
|
|
|
|
reader, err := source.OpenTxsBinaryReader()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("source[%d]: open reader: %w", sourceIndex, err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 17:56:17 +08:00
|
|
|
bufferedReader := bufio.NewReader(reader)
|
|
|
|
|
dec := txBinaryStreamDecoder{reader: bufferedReader}
|
2026-04-16 16:40:21 +08:00
|
|
|
batchIndex := 0
|
|
|
|
|
for {
|
2026-04-16 17:56:17 +08:00
|
|
|
skipBatch, err := txBinaryApplyMergeBatchHeader(bufferedReader, opts, sourceIndex, batchIndex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeErr := reader.Close()
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
if closeErr != nil {
|
|
|
|
|
return fmt.Errorf("source[%d]: close reader: %w", sourceIndex, closeErr)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("source[%d].batch[%d]: %w", sourceIndex, batchIndex, err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
header, err := dec.readTxsBinaryHeaderOrEOF()
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeErr := reader.Close()
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
if closeErr != nil {
|
|
|
|
|
return fmt.Errorf("source[%d]: close reader: %w", sourceIndex, closeErr)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("source[%d].batch[%d]: %w", sourceIndex, batchIndex, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for txIndex := uint32(0); txIndex < header.count; txIndex++ {
|
|
|
|
|
tx := TxBinary{
|
|
|
|
|
SchemaVersion: header.schemaVersion,
|
|
|
|
|
EnumVersion: header.enumVersion,
|
|
|
|
|
AddressTable: header.addressTable,
|
|
|
|
|
}
|
|
|
|
|
if err := txBinaryReadTxBody(&dec, &tx, header.enumTable, header.addressTable); err != nil {
|
|
|
|
|
reader.Close()
|
|
|
|
|
return fmt.Errorf("source[%d].batch[%d].tx[%d]: %w", sourceIndex, batchIndex, txIndex, err)
|
|
|
|
|
}
|
2026-04-16 17:56:17 +08:00
|
|
|
if skipBatch {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-04-16 16:40:21 +08:00
|
|
|
if err := txBinaryRemapTxAddressTable(&tx, header.addressTable, plan.addressTable, plan.addressIndex); err != nil {
|
|
|
|
|
reader.Close()
|
|
|
|
|
return fmt.Errorf("source[%d].batch[%d].tx[%d]: %w", sourceIndex, batchIndex, txIndex, err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 10:35:49 +08:00
|
|
|
tx.SchemaVersion = plan.schemaVersion
|
|
|
|
|
tx.EnumVersion = plan.enumVersion
|
2026-04-16 16:40:21 +08:00
|
|
|
bodyBytes, err := txBinaryMarshalTxBody(&tx, plan.enumTable)
|
|
|
|
|
if err != nil {
|
|
|
|
|
reader.Close()
|
|
|
|
|
return fmt.Errorf("source[%d].batch[%d].tx[%d]: %w", sourceIndex, batchIndex, txIndex, err)
|
|
|
|
|
}
|
|
|
|
|
if err := txBinaryWriteAll(w, bodyBytes); err != nil {
|
|
|
|
|
reader.Close()
|
|
|
|
|
return fmt.Errorf("source[%d].batch[%d].tx[%d]: write merged body: %w", sourceIndex, batchIndex, txIndex, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
batchIndex++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *TxBinary) MarshalBinary() ([]byte, error) {
|
|
|
|
|
if tx == nil {
|
|
|
|
|
return nil, fmt.Errorf("tx binary is nil")
|
|
|
|
|
}
|
2026-06-05 10:35:49 +08:00
|
|
|
if !txBinarySchemaVersionSupported(tx.SchemaVersion) {
|
2026-04-16 16:40:21 +08:00
|
|
|
return nil, fmt.Errorf("unsupported tx binary schema version: %d", tx.SchemaVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enumTable, err := txBinaryEnumTableByVersion(tx.EnumVersion)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
addressTable := tx.AddressTable
|
|
|
|
|
|
|
|
|
|
enc := txBinaryEncoder{}
|
|
|
|
|
enc.writeBytes(txBinaryMagic[:])
|
|
|
|
|
enc.writeUint16(tx.SchemaVersion)
|
|
|
|
|
enc.writeUint16(tx.EnumVersion)
|
|
|
|
|
if err := enc.writeAddressTable(addressTable); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if err := enc.writeTxBinaryBody(tx, enumTable); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return enc.bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (txs *TxsBinary) MarshalBinary() ([]byte, error) {
|
|
|
|
|
if txs == nil {
|
|
|
|
|
return nil, fmt.Errorf("txs binary is nil")
|
|
|
|
|
}
|
2026-06-05 10:35:49 +08:00
|
|
|
if !txBinarySchemaVersionSupported(txs.SchemaVersion) {
|
2026-04-16 16:40:21 +08:00
|
|
|
return nil, fmt.Errorf("unsupported tx binary schema version: %d", txs.SchemaVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enumTable, err := txBinaryEnumTableByVersion(txs.EnumVersion)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enc := txBinaryEncoder{}
|
|
|
|
|
enc.writeBytes(txsBinaryMagic[:])
|
|
|
|
|
enc.writeUint16(txs.SchemaVersion)
|
|
|
|
|
enc.writeUint16(txs.EnumVersion)
|
|
|
|
|
if err := enc.writeAddressTable(txs.AddressTable); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
enc.writeUint32(uint32(len(txs.Txs)))
|
|
|
|
|
for i := range txs.Txs {
|
2026-06-05 10:35:49 +08:00
|
|
|
tx := txs.Txs[i]
|
|
|
|
|
tx.SchemaVersion = txs.SchemaVersion
|
|
|
|
|
tx.EnumVersion = txs.EnumVersion
|
|
|
|
|
if err := enc.writeTxBinaryBody(&tx, enumTable); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("tx[%d], %s: %w", i, base58.Encode(tx.TxHash[:]), err)
|
2026-04-16 16:40:21 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return enc.bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryMarshalTxsHeader(schemaVersion uint16, enumVersion uint16, addressTable []solana.PublicKey, txCount uint32) ([]byte, error) {
|
|
|
|
|
enc := txBinaryEncoder{}
|
|
|
|
|
enc.writeBytes(txsBinaryMagic[:])
|
|
|
|
|
enc.writeUint16(schemaVersion)
|
|
|
|
|
enc.writeUint16(enumVersion)
|
|
|
|
|
if err := enc.writeAddressTable(addressTable); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
enc.writeUint32(txCount)
|
|
|
|
|
return enc.bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryMarshalTxBody(tx *TxBinary, enumTable *txBinaryEnumTable) ([]byte, error) {
|
|
|
|
|
enc := txBinaryEncoder{}
|
|
|
|
|
if err := enc.writeTxBinaryBody(tx, enumTable); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return enc.bytes(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *TxBinary) UnmarshalBinary(data []byte) error {
|
|
|
|
|
dec := txBinaryDecoder{reader: bytes.NewReader(data)}
|
|
|
|
|
|
|
|
|
|
magic, err := dec.readN(len(txBinaryMagic))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(magic, txBinaryMagic[:]) {
|
|
|
|
|
return fmt.Errorf("invalid tx binary magic")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.SchemaVersion, err = dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-06-05 10:35:49 +08:00
|
|
|
if !txBinarySchemaVersionSupported(tx.SchemaVersion) {
|
2026-04-16 16:40:21 +08:00
|
|
|
return fmt.Errorf("unsupported tx binary schema version: %d", tx.SchemaVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.EnumVersion, err = dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
enumTable, err := txBinaryEnumTableByVersion(tx.EnumVersion)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
tx.AddressTable, err = dec.readAddressTable()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := txBinaryReadTxBody(&dec, tx, enumTable, tx.AddressTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if dec.reader.Len() != 0 {
|
|
|
|
|
return fmt.Errorf("unexpected trailing tx binary data: %d bytes", dec.reader.Len())
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (txs *TxsBinary) UnmarshalBinary(data []byte) error {
|
|
|
|
|
dec := txBinaryDecoder{reader: bytes.NewReader(data)}
|
|
|
|
|
|
|
|
|
|
magic, err := dec.readN(len(txsBinaryMagic))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(magic, txsBinaryMagic[:]) {
|
|
|
|
|
return fmt.Errorf("invalid txs binary magic")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
txs.SchemaVersion, err = dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-06-05 10:35:49 +08:00
|
|
|
if !txBinarySchemaVersionSupported(txs.SchemaVersion) {
|
2026-04-16 16:40:21 +08:00
|
|
|
return fmt.Errorf("unsupported tx binary schema version: %d", txs.SchemaVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
txs.EnumVersion, err = dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
enumTable, err := txBinaryEnumTableByVersion(txs.EnumVersion)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
txs.AddressTable, err = dec.readAddressTable()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
txs.Txs = make([]TxBinary, 0, count)
|
|
|
|
|
for i := uint32(0); i < count; i++ {
|
|
|
|
|
tx := TxBinary{
|
|
|
|
|
SchemaVersion: txs.SchemaVersion,
|
|
|
|
|
EnumVersion: txs.EnumVersion,
|
|
|
|
|
AddressTable: txs.AddressTable,
|
|
|
|
|
}
|
|
|
|
|
if err := txBinaryReadTxBody(&dec, &tx, enumTable, txs.AddressTable); 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 txs binary data: %d bytes", dec.reader.Len())
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (tx *TxBinary) ToTx() (*Tx, error) {
|
|
|
|
|
if tx == nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signer, err := txBinaryAddressAt(tx.AddressTable, tx.Signer, "tx.signer")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out := &Tx{
|
|
|
|
|
Signer: signer,
|
|
|
|
|
Block: tx.Block,
|
|
|
|
|
BlockIndex: tx.BlockIndex,
|
2026-06-05 10:35:49 +08:00
|
|
|
BlockAt: tx.BlockAt,
|
2026-04-16 16:40:21 +08:00
|
|
|
CuFee: decimal.NewFromUint64(tx.CuFee),
|
|
|
|
|
CUPrice: decimal.NewFromUint64(tx.CUPrice).Shift(-txBinaryCUPriceScale),
|
|
|
|
|
BeforeSolBalance: txBinaryFloat64ToDecimal(tx.BeforeSolBalance, txBinarySOLScale),
|
|
|
|
|
AfterSOLBalance: txBinaryFloat64ToDecimal(tx.AfterSOLBalance, txBinarySOLScale),
|
|
|
|
|
ComputeUnitsConsumed: tx.ComputeUnitsConsumed,
|
|
|
|
|
CuLimit: tx.CuLimit,
|
|
|
|
|
}
|
|
|
|
|
if tx.TxHash != nil {
|
|
|
|
|
txHash := *tx.TxHash
|
|
|
|
|
out.TxHash = &txHash
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(tx.Platform) > 0 {
|
|
|
|
|
out.Platform = make(map[string]platformInfo, len(tx.Platform))
|
|
|
|
|
for _, platform := range tx.Platform {
|
|
|
|
|
out.Platform[platform.Platform] = platformInfo{
|
|
|
|
|
Platform: platform.Platform,
|
|
|
|
|
PlatformFee: decimal.NewFromUint64(platform.PlatformFee).Shift(-txBinarySOLScale),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(tx.MevAgent) > 0 {
|
|
|
|
|
out.MevAgent = make(map[string]mevInfo, len(tx.MevAgent))
|
|
|
|
|
for _, mevAgent := range tx.MevAgent {
|
|
|
|
|
out.MevAgent[mevAgent.MevAgent] = mevInfo{
|
|
|
|
|
MevAgent: mevAgent.MevAgent,
|
|
|
|
|
MevAgentFee: decimal.NewFromUint64(mevAgent.MevAgentFee).Shift(-txBinarySOLScale),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(tx.Swaps) > 0 {
|
|
|
|
|
out.Swaps = make([]Swap, 0, len(tx.Swaps))
|
|
|
|
|
for i, swap := range tx.Swaps {
|
|
|
|
|
decodedSwap, err := swap.toSwap(tx.AddressTable, i)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out.Swaps = append(out.Swaps, decodedSwap)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (txs *TxsBinary) ToTxs() ([]*Tx, error) {
|
|
|
|
|
if txs == nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
out := make([]*Tx, 0, len(txs.Txs))
|
|
|
|
|
for i := range txs.Txs {
|
|
|
|
|
txs.Txs[i].AddressTable = txs.AddressTable
|
|
|
|
|
tx, err := txs.Txs[i].ToTx()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("tx[%d]: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
out = append(out, tx)
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newSwapBinary(swap Swap, index int, addressIndex *txBinaryAddressIndex) (SwapBinary, error) {
|
|
|
|
|
pool, err := addressIndex.id(swap.Pool)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].pool: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
baseMint, err := addressIndex.id(swap.BaseMint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].base_mint: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
quoteMint, err := addressIndex.id(swap.QuoteMint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].quote_mint: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
baseTokenProgram, err := addressIndex.id(swap.BaseTokenProgram)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].base_token_program: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
quoteTokenProgram, err := addressIndex.id(swap.QuoteTokenProgram)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].quote_token_program: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
creator, err := addressIndex.id(swap.Creator)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].creator: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
user, err := addressIndex.id(swap.User)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].user: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
fixedMint, err := addressIndex.id(swap.FixedMint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].fixed_mint: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
limitMint, err := addressIndex.id(swap.LimitMint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].limit_mint: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
entryContract, err := addressIndex.id(swap.EntryContract)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].entry_contract: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
migrateToPool, err := addressIndex.id(swap.MigrateToPool)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].migrate_to_pool: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
migrateTopProgram, err := addressIndex.id(swap.MigrateTopProgram)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].migrate_top_program: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
lpMint, err := addressIndex.id(swap.LpMint)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].lp_mint: %w", index, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out := SwapBinary{
|
|
|
|
|
Program: swap.Program,
|
2026-04-20 15:25:08 +08:00
|
|
|
Event: txBinaryCanonicalEvent(swap.Event),
|
2026-04-16 16:40:21 +08:00
|
|
|
TxIndex: int32(swap.TxIndex),
|
|
|
|
|
InstrIdx: swap.InstrIdx,
|
|
|
|
|
InnerIdx: swap.InnerIdx,
|
|
|
|
|
Pool: pool,
|
|
|
|
|
BaseMint: baseMint,
|
|
|
|
|
QuoteMint: quoteMint,
|
|
|
|
|
BaseTokenProgram: baseTokenProgram,
|
|
|
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
|
|
|
Creator: creator,
|
|
|
|
|
BaseMintDecimals: swap.BaseMintDecimals,
|
|
|
|
|
QuoteMintDecimals: swap.QuoteMintDecimals,
|
|
|
|
|
User: user,
|
|
|
|
|
SwapMode: swap.SwapMode,
|
|
|
|
|
FixedAmountSide: swap.FixedAmountSide,
|
|
|
|
|
FixedMint: fixedMint,
|
|
|
|
|
LimitAmountType: swap.LimitAmountType,
|
|
|
|
|
LimitAmountSide: swap.LimitAmountSide,
|
|
|
|
|
LimitMint: limitMint,
|
|
|
|
|
ActualLimitAmountSide: swap.ActualLimitAmountSide,
|
|
|
|
|
Mayhem: swap.Mayhem,
|
|
|
|
|
Cashback: swap.Cashback,
|
|
|
|
|
EntryContract: entryContract,
|
|
|
|
|
MigrateToPool: migrateToPool,
|
|
|
|
|
MigrateTopProgram: migrateTopProgram,
|
|
|
|
|
LpMint: lpMint,
|
|
|
|
|
}
|
|
|
|
|
if swap.TxIndex > math.MaxInt32 || swap.TxIndex < math.MinInt32 {
|
|
|
|
|
return SwapBinary{}, fmt.Errorf("swap[%d].tx_index overflows int32: %d", index, swap.TxIndex)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if out.BaseAmount, err = txBinaryDecimalToUint64(swap.BaseAmount, fmt.Sprintf("swap[%d].base_amount", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
if out.QuoteAmount, err = txBinaryDecimalToUint64(swap.QuoteAmount, fmt.Sprintf("swap[%d].quote_amount", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
if out.FixedAmount, err = txBinaryDecimalToUint64(swap.FixedAmount, fmt.Sprintf("swap[%d].fixed_amount", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
if out.LimitAmount, err = txBinaryDecimalToUint64(swap.LimitAmount, fmt.Sprintf("swap[%d].limit_amount", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
if out.ActualLimitAmount, err = txBinaryDecimalToUint64(swap.ActualLimitAmount, fmt.Sprintf("swap[%d].actual_limit_amount", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
if out.SlippageBps, err = txBinaryRoundedDecimalToUint64(swap.SlippageBps, fmt.Sprintf("swap[%d].slippage_bps", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
2026-04-20 15:09:42 +08:00
|
|
|
if out.BaseReserve, err = txBinaryDecimalToFloat64Raw(swap.BaseReserve, fmt.Sprintf("swap[%d].base_reserve", index)); err != nil {
|
2026-04-16 16:40:21 +08:00
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
2026-04-20 15:09:42 +08:00
|
|
|
if out.QuoteReserve, err = txBinaryDecimalToFloat64Raw(swap.QuoteReserve, fmt.Sprintf("swap[%d].quote_reserve", index)); err != nil {
|
2026-04-16 16:40:21 +08:00
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
if out.UserBaseBalance, err = txBinaryDecimalToUint64(swap.UserBaseBalance, fmt.Sprintf("swap[%d].user_base_balance", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
if out.UserQuoteBalance, err = txBinaryDecimalToUint64(swap.UserQuoteBalance, fmt.Sprintf("swap[%d].user_quote_balance", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
if out.AfterSOLBalance, err = txBinaryDecimalToFloat64(swap.AfterSOLBalance, txBinarySOLScale, fmt.Sprintf("swap[%d].after_sol_balance", index)); err != nil {
|
|
|
|
|
return SwapBinary{}, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (swap SwapBinary) toSwap(addressTable []solana.PublicKey, index int) (Swap, error) {
|
|
|
|
|
pool, err := txBinaryAddressAt(addressTable, swap.Pool, fmt.Sprintf("swap[%d].pool", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
baseMint, err := txBinaryAddressAt(addressTable, swap.BaseMint, fmt.Sprintf("swap[%d].base_mint", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
quoteMint, err := txBinaryAddressAt(addressTable, swap.QuoteMint, fmt.Sprintf("swap[%d].quote_mint", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
baseTokenProgram, err := txBinaryAddressAt(addressTable, swap.BaseTokenProgram, fmt.Sprintf("swap[%d].base_token_program", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
quoteTokenProgram, err := txBinaryAddressAt(addressTable, swap.QuoteTokenProgram, fmt.Sprintf("swap[%d].quote_token_program", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
creator, err := txBinaryAddressAt(addressTable, swap.Creator, fmt.Sprintf("swap[%d].creator", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
user, err := txBinaryAddressAt(addressTable, swap.User, fmt.Sprintf("swap[%d].user", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
fixedMint, err := txBinaryAddressAt(addressTable, swap.FixedMint, fmt.Sprintf("swap[%d].fixed_mint", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
limitMint, err := txBinaryAddressAt(addressTable, swap.LimitMint, fmt.Sprintf("swap[%d].limit_mint", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
entryContract, err := txBinaryAddressAt(addressTable, swap.EntryContract, fmt.Sprintf("swap[%d].entry_contract", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
migrateToPool, err := txBinaryAddressAt(addressTable, swap.MigrateToPool, fmt.Sprintf("swap[%d].migrate_to_pool", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
migrateTopProgram, err := txBinaryAddressAt(addressTable, swap.MigrateTopProgram, fmt.Sprintf("swap[%d].migrate_top_program", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
lpMint, err := txBinaryAddressAt(addressTable, swap.LpMint, fmt.Sprintf("swap[%d].lp_mint", index))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Swap{}, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Swap{
|
|
|
|
|
Program: swap.Program,
|
|
|
|
|
Event: swap.Event,
|
|
|
|
|
TxIndex: int(swap.TxIndex),
|
|
|
|
|
InstrIdx: swap.InstrIdx,
|
|
|
|
|
InnerIdx: swap.InnerIdx,
|
|
|
|
|
Pool: pool,
|
|
|
|
|
BaseMint: baseMint,
|
|
|
|
|
QuoteMint: quoteMint,
|
|
|
|
|
BaseTokenProgram: baseTokenProgram,
|
|
|
|
|
QuoteTokenProgram: quoteTokenProgram,
|
|
|
|
|
Creator: creator,
|
|
|
|
|
BaseMintDecimals: swap.BaseMintDecimals,
|
|
|
|
|
QuoteMintDecimals: swap.QuoteMintDecimals,
|
|
|
|
|
User: user,
|
|
|
|
|
BaseAmount: decimal.NewFromUint64(swap.BaseAmount),
|
|
|
|
|
QuoteAmount: decimal.NewFromUint64(swap.QuoteAmount),
|
|
|
|
|
SwapMode: swap.SwapMode,
|
|
|
|
|
FixedAmount: decimal.NewFromUint64(swap.FixedAmount),
|
|
|
|
|
FixedAmountSide: swap.FixedAmountSide,
|
|
|
|
|
FixedMint: fixedMint,
|
|
|
|
|
LimitAmountType: swap.LimitAmountType,
|
|
|
|
|
LimitAmount: decimal.NewFromUint64(swap.LimitAmount),
|
|
|
|
|
LimitAmountSide: swap.LimitAmountSide,
|
|
|
|
|
LimitMint: limitMint,
|
|
|
|
|
ActualLimitAmount: decimal.NewFromUint64(swap.ActualLimitAmount),
|
|
|
|
|
ActualLimitAmountSide: swap.ActualLimitAmountSide,
|
|
|
|
|
SlippageBps: decimal.NewFromUint64(swap.SlippageBps),
|
2026-04-20 15:09:42 +08:00
|
|
|
BaseReserve: txBinaryFloat64ToDecimalRaw(swap.BaseReserve),
|
|
|
|
|
QuoteReserve: txBinaryFloat64ToDecimalRaw(swap.QuoteReserve),
|
2026-04-16 16:40:21 +08:00
|
|
|
Mayhem: swap.Mayhem,
|
|
|
|
|
Cashback: swap.Cashback,
|
|
|
|
|
UserBaseBalance: decimal.NewFromUint64(swap.UserBaseBalance),
|
|
|
|
|
UserQuoteBalance: decimal.NewFromUint64(swap.UserQuoteBalance),
|
|
|
|
|
EntryContract: entryContract,
|
|
|
|
|
MigrateToPool: migrateToPool,
|
|
|
|
|
MigrateTopProgram: migrateTopProgram,
|
|
|
|
|
LpMint: lpMint,
|
|
|
|
|
AfterSOLBalance: txBinaryFloat64ToDecimal(swap.AfterSOLBalance, txBinarySOLScale),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryPlatformsFromTx(platforms map[string]platformInfo) ([]PlatformBinary, error) {
|
|
|
|
|
if len(platforms) == 0 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keys := make([]string, 0, len(platforms))
|
|
|
|
|
for key := range platforms {
|
|
|
|
|
keys = append(keys, key)
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
|
|
out := make([]PlatformBinary, 0, len(keys))
|
|
|
|
|
for _, key := range keys {
|
|
|
|
|
platform := platforms[key]
|
|
|
|
|
platformFee, err := txBinaryScaledDecimalToUint64(platform.PlatformFee, txBinarySOLScale, fmt.Sprintf("platform[%s].fee", key))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out = append(out, PlatformBinary{
|
|
|
|
|
Platform: key,
|
|
|
|
|
PlatformFee: platformFee,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 15:25:08 +08:00
|
|
|
func txBinaryCanonicalEvent(event string) string {
|
|
|
|
|
switch event {
|
|
|
|
|
case "add_liquidity_on_side":
|
|
|
|
|
return TxEventAddLiquidityOneSide
|
|
|
|
|
case "remove_liquidity_on_side":
|
|
|
|
|
return TxEventRemoveLiquidityOneSide
|
|
|
|
|
default:
|
|
|
|
|
return event
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
func txBinaryMevAgentsFromTx(mevAgents map[string]mevInfo) ([]MevAgentBinary, error) {
|
|
|
|
|
if len(mevAgents) == 0 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keys := make([]string, 0, len(mevAgents))
|
|
|
|
|
for key := range mevAgents {
|
|
|
|
|
keys = append(keys, key)
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
|
|
out := make([]MevAgentBinary, 0, len(keys))
|
|
|
|
|
for _, key := range keys {
|
|
|
|
|
mevAgent := mevAgents[key]
|
|
|
|
|
mevFee, err := txBinaryScaledDecimalToUint64(mevAgent.MevAgentFee, txBinarySOLScale, fmt.Sprintf("mev_agent[%s].fee", key))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out = append(out, MevAgentBinary{
|
|
|
|
|
MevAgent: key,
|
|
|
|
|
MevAgentFee: mevFee,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryBuildAddressTable(txs []*Tx) ([]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)
|
|
|
|
|
}
|
|
|
|
|
if err := builder.add(tx.Signer); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("tx[%d].signer: %w", txIndex, err)
|
|
|
|
|
}
|
|
|
|
|
for swapIndex, swap := range tx.Swaps {
|
|
|
|
|
for _, address := range []solana.PublicKey{
|
|
|
|
|
swap.Pool,
|
|
|
|
|
swap.BaseMint,
|
|
|
|
|
swap.QuoteMint,
|
|
|
|
|
swap.BaseTokenProgram,
|
|
|
|
|
swap.QuoteTokenProgram,
|
|
|
|
|
swap.Creator,
|
|
|
|
|
swap.User,
|
|
|
|
|
swap.FixedMint,
|
|
|
|
|
swap.LimitMint,
|
|
|
|
|
swap.EntryContract,
|
|
|
|
|
swap.MigrateToPool,
|
|
|
|
|
swap.MigrateTopProgram,
|
|
|
|
|
swap.LpMint,
|
|
|
|
|
} {
|
|
|
|
|
if err := builder.add(address); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("tx[%d].swap[%d] address table: %w", txIndex, swapIndex, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return builder.addresses, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txBinaryAddressTableBuilder struct {
|
|
|
|
|
addresses []solana.PublicKey
|
|
|
|
|
index map[solana.PublicKey]struct{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *txBinaryAddressTableBuilder) add(address solana.PublicKey) error {
|
|
|
|
|
if _, ok := b.index[address]; ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if uint64(len(b.addresses)) >= uint64(math.MaxUint32) {
|
|
|
|
|
return fmt.Errorf("address table exceeds uint32 capacity")
|
|
|
|
|
}
|
|
|
|
|
b.addresses = append(b.addresses, address)
|
|
|
|
|
b.index[address] = struct{}{}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txBinaryAddressIndex struct {
|
|
|
|
|
index map[solana.PublicKey]uint32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newTxBinaryAddressIndex(addresses []solana.PublicKey) (*txBinaryAddressIndex, error) {
|
|
|
|
|
if uint64(len(addresses)) > uint64(math.MaxUint32) {
|
|
|
|
|
return nil, fmt.Errorf("address table exceeds uint32 capacity")
|
|
|
|
|
}
|
|
|
|
|
index := make(map[solana.PublicKey]uint32, len(addresses))
|
|
|
|
|
for i, address := range addresses {
|
|
|
|
|
if _, exists := index[address]; exists {
|
|
|
|
|
return nil, fmt.Errorf("duplicate address table entry: %s", address.String())
|
|
|
|
|
}
|
|
|
|
|
index[address] = uint32(i)
|
|
|
|
|
}
|
|
|
|
|
return &txBinaryAddressIndex{index: index}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (idx *txBinaryAddressIndex) id(address solana.PublicKey) (uint32, error) {
|
|
|
|
|
id, ok := idx.index[address]
|
|
|
|
|
if !ok {
|
|
|
|
|
return 0, fmt.Errorf("address not found in address table: %s", address.String())
|
|
|
|
|
}
|
|
|
|
|
return id, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryAddressAt(addressTable []solana.PublicKey, index uint32, field string) (solana.PublicKey, error) {
|
|
|
|
|
if int(index) >= len(addressTable) {
|
|
|
|
|
return solana.PublicKey{}, fmt.Errorf("%s address index out of range: %d", field, index)
|
|
|
|
|
}
|
|
|
|
|
return addressTable[index], nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryDecimalToUint64(value decimal.Decimal, field string) (uint64, error) {
|
|
|
|
|
if value.IsNegative() {
|
|
|
|
|
return 0, fmt.Errorf("%s must be >= 0, got %s", field, value.String())
|
|
|
|
|
}
|
|
|
|
|
if !value.Equal(value.Truncate(0)) {
|
|
|
|
|
return 0, fmt.Errorf("%s must be an integer, got %s", field, value.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bigInt := value.BigInt()
|
|
|
|
|
if !bigInt.IsUint64() {
|
|
|
|
|
return 0, fmt.Errorf("%s overflows uint64: %s", field, value.String())
|
|
|
|
|
}
|
|
|
|
|
return bigInt.Uint64(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryScaledDecimalToUint64(value decimal.Decimal, scale int32, field string) (uint64, error) {
|
|
|
|
|
return txBinaryDecimalToUint64(value.Shift(scale), field)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryRoundedDecimalToUint64(value decimal.Decimal, field string) (uint64, error) {
|
|
|
|
|
return txBinaryDecimalToUint64(value.Round(0), field)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryDecimalToFloat64(value decimal.Decimal, scale int32, field string) (float64, error) {
|
|
|
|
|
rounded := value.Round(scale)
|
|
|
|
|
f, exact := rounded.Float64()
|
|
|
|
|
if !exact && math.IsInf(f, 0) {
|
|
|
|
|
return 0, fmt.Errorf("%s cannot be represented as float64: %s", field, value.String())
|
|
|
|
|
}
|
|
|
|
|
return f, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 15:09:42 +08:00
|
|
|
func txBinaryDecimalToFloat64Raw(value decimal.Decimal, field string) (float64, error) {
|
|
|
|
|
f, exact := value.Float64()
|
|
|
|
|
if !exact && math.IsInf(f, 0) {
|
|
|
|
|
return 0, fmt.Errorf("%s cannot be represented as float64: %s", field, value.String())
|
|
|
|
|
}
|
|
|
|
|
return f, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
func txBinaryFloat64ToDecimal(value float64, scale int32) decimal.Decimal {
|
|
|
|
|
formatted := strconv.FormatFloat(value, 'f', int(scale), 64)
|
|
|
|
|
out, err := decimal.NewFromString(formatted)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return decimal.Zero
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 15:09:42 +08:00
|
|
|
func txBinaryFloat64ToDecimalRaw(value float64) decimal.Decimal {
|
|
|
|
|
formatted := strconv.FormatFloat(value, 'f', -1, 64)
|
|
|
|
|
out, err := decimal.NewFromString(formatted)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return decimal.Zero
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
type txBinaryEncoder struct {
|
|
|
|
|
buf bytes.Buffer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) bytes() []byte {
|
|
|
|
|
return enc.buf.Bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeBool(value bool) {
|
|
|
|
|
if value {
|
|
|
|
|
enc.writeUint8(1)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
enc.writeUint8(0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeUint8(value uint8) {
|
|
|
|
|
enc.buf.WriteByte(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeUint16(value uint16) {
|
|
|
|
|
var raw [2]byte
|
|
|
|
|
binary.LittleEndian.PutUint16(raw[:], value)
|
|
|
|
|
enc.buf.Write(raw[:])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeUint32(value uint32) {
|
|
|
|
|
var raw [4]byte
|
|
|
|
|
binary.LittleEndian.PutUint32(raw[:], value)
|
|
|
|
|
enc.buf.Write(raw[:])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeUint64(value uint64) {
|
|
|
|
|
var raw [8]byte
|
|
|
|
|
binary.LittleEndian.PutUint64(raw[:], value)
|
|
|
|
|
enc.buf.Write(raw[:])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeFloat64(value float64) {
|
|
|
|
|
enc.writeUint64(math.Float64bits(value))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeInt32(value int32) {
|
|
|
|
|
enc.writeUint32(uint32(value))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeBytes(value []byte) {
|
|
|
|
|
enc.buf.Write(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeAddressTable(addresses []solana.PublicKey) error {
|
|
|
|
|
if uint64(len(addresses)) > uint64(math.MaxUint32) {
|
|
|
|
|
return fmt.Errorf("address table exceeds uint32 capacity")
|
|
|
|
|
}
|
|
|
|
|
enc.writeUint32(uint32(len(addresses)))
|
|
|
|
|
for _, address := range addresses {
|
|
|
|
|
enc.writeBytes(address[:])
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeTxBinaryBody(tx *TxBinary, enumTable *txBinaryEnumTable) error {
|
|
|
|
|
enc.writeUint32(tx.Signer)
|
|
|
|
|
enc.writeUint64(tx.Block)
|
|
|
|
|
enc.writeUint64(tx.BlockIndex)
|
2026-06-05 10:35:49 +08:00
|
|
|
if tx.SchemaVersion >= txBinarySchemaVersionCurrent {
|
|
|
|
|
enc.writeUint64(uint64(tx.BlockAt))
|
|
|
|
|
}
|
2026-04-16 16:40:21 +08:00
|
|
|
enc.writeBool(tx.TxHash != nil)
|
|
|
|
|
if tx.TxHash != nil {
|
|
|
|
|
enc.writeBytes(tx.TxHash[:])
|
|
|
|
|
}
|
|
|
|
|
enc.writeUint64(tx.CuFee)
|
|
|
|
|
enc.writeUint64(tx.CUPrice)
|
|
|
|
|
enc.writeFloat64(tx.BeforeSolBalance)
|
|
|
|
|
enc.writeFloat64(tx.AfterSOLBalance)
|
|
|
|
|
enc.writeUint64(tx.ComputeUnitsConsumed)
|
|
|
|
|
enc.writeUint32(tx.CuLimit)
|
|
|
|
|
if err := enc.writePlatformEntries(tx.Platform, enumTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := enc.writeMevAgentEntries(tx.MevAgent, enumTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := enc.writeSwaps(tx.Swaps, enumTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writePlatformEntries(entries []PlatformBinary, enumTable *txBinaryEnumTable) error {
|
|
|
|
|
enc.writeUint32(uint32(len(entries)))
|
|
|
|
|
for i, entry := range entries {
|
2026-05-13 17:30:06 +08:00
|
|
|
enumID, err := enumTable.platforms.idOrFallback(entry.Platform, PlatformNone)
|
2026-04-16 16:40:21 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("platform[%d]: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
enc.writeUint16(enumID)
|
|
|
|
|
enc.writeUint64(entry.PlatformFee)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeMevAgentEntries(entries []MevAgentBinary, enumTable *txBinaryEnumTable) error {
|
|
|
|
|
enc.writeUint32(uint32(len(entries)))
|
|
|
|
|
for i, entry := range entries {
|
2026-05-13 17:30:06 +08:00
|
|
|
enumID, err := enumTable.mevAgents.idOrFallback(entry.MevAgent, MevAgentUnknown)
|
2026-04-16 16:40:21 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("mev_agent[%d]: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
enc.writeUint16(enumID)
|
|
|
|
|
enc.writeUint64(entry.MevAgentFee)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (enc *txBinaryEncoder) writeSwaps(swaps []SwapBinary, enumTable *txBinaryEnumTable) error {
|
|
|
|
|
enc.writeUint32(uint32(len(swaps)))
|
|
|
|
|
for i, swap := range swaps {
|
|
|
|
|
programID, err := enumTable.programs.id(swap.Program)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("swap[%d].program: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
eventID, err := enumTable.events.id(swap.Event)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("swap[%d].event: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enc.writeUint16(programID)
|
|
|
|
|
enc.writeUint16(eventID)
|
|
|
|
|
enc.writeInt32(swap.TxIndex)
|
|
|
|
|
enc.writeUint8(swap.InstrIdx)
|
|
|
|
|
enc.writeUint8(swap.InnerIdx)
|
|
|
|
|
enc.writeUint32(swap.Pool)
|
|
|
|
|
enc.writeUint32(swap.BaseMint)
|
|
|
|
|
enc.writeUint32(swap.QuoteMint)
|
|
|
|
|
enc.writeUint32(swap.BaseTokenProgram)
|
|
|
|
|
enc.writeUint32(swap.QuoteTokenProgram)
|
|
|
|
|
enc.writeUint32(swap.Creator)
|
|
|
|
|
enc.writeUint8(swap.BaseMintDecimals)
|
|
|
|
|
enc.writeUint8(swap.QuoteMintDecimals)
|
|
|
|
|
enc.writeUint32(swap.User)
|
|
|
|
|
enc.writeUint64(swap.BaseAmount)
|
|
|
|
|
enc.writeUint64(swap.QuoteAmount)
|
|
|
|
|
enc.writeUint8(uint8(swap.SwapMode))
|
|
|
|
|
enc.writeUint64(swap.FixedAmount)
|
|
|
|
|
enc.writeUint8(uint8(swap.FixedAmountSide))
|
|
|
|
|
enc.writeUint32(swap.FixedMint)
|
|
|
|
|
enc.writeUint8(uint8(swap.LimitAmountType))
|
|
|
|
|
enc.writeUint64(swap.LimitAmount)
|
|
|
|
|
enc.writeUint8(uint8(swap.LimitAmountSide))
|
|
|
|
|
enc.writeUint32(swap.LimitMint)
|
|
|
|
|
enc.writeUint64(swap.ActualLimitAmount)
|
|
|
|
|
enc.writeUint8(uint8(swap.ActualLimitAmountSide))
|
|
|
|
|
enc.writeUint64(swap.SlippageBps)
|
2026-04-20 15:09:42 +08:00
|
|
|
enc.writeFloat64(swap.BaseReserve)
|
|
|
|
|
enc.writeFloat64(swap.QuoteReserve)
|
2026-04-16 16:40:21 +08:00
|
|
|
enc.writeBool(swap.Mayhem)
|
|
|
|
|
enc.writeBool(swap.Cashback)
|
|
|
|
|
enc.writeUint64(swap.UserBaseBalance)
|
|
|
|
|
enc.writeUint64(swap.UserQuoteBalance)
|
|
|
|
|
enc.writeUint32(swap.EntryContract)
|
|
|
|
|
enc.writeUint32(swap.MigrateToPool)
|
|
|
|
|
enc.writeUint32(swap.MigrateTopProgram)
|
|
|
|
|
enc.writeUint32(swap.LpMint)
|
|
|
|
|
enc.writeFloat64(swap.AfterSOLBalance)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txBinaryDecoder struct {
|
|
|
|
|
reader *bytes.Reader
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txBinaryStreamDecoder struct {
|
|
|
|
|
reader io.Reader
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txBinaryBodyReader interface {
|
|
|
|
|
readBool() (bool, error)
|
|
|
|
|
readUint8() (uint8, error)
|
|
|
|
|
readUint16() (uint16, error)
|
|
|
|
|
readUint32() (uint32, error)
|
|
|
|
|
readUint64() (uint64, error)
|
|
|
|
|
readFloat64() (float64, error)
|
|
|
|
|
readInt32() (int32, error)
|
|
|
|
|
readN(int) ([]byte, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txsBinaryHeader struct {
|
|
|
|
|
schemaVersion uint16
|
|
|
|
|
enumVersion uint16
|
|
|
|
|
addressTable []solana.PublicKey
|
|
|
|
|
enumTable *txBinaryEnumTable
|
|
|
|
|
count uint32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readBool() (bool, error) {
|
|
|
|
|
value, err := dec.readUint8()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
switch value {
|
|
|
|
|
case 0:
|
|
|
|
|
return false, nil
|
|
|
|
|
case 1:
|
|
|
|
|
return true, nil
|
|
|
|
|
default:
|
|
|
|
|
return false, fmt.Errorf("invalid bool value: %d", value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readUint8() (uint8, error) {
|
|
|
|
|
raw, err := dec.readN(1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return raw[0], nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readUint16() (uint16, error) {
|
|
|
|
|
raw, err := dec.readN(2)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return binary.LittleEndian.Uint16(raw), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readUint32() (uint32, error) {
|
|
|
|
|
raw, err := dec.readN(4)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return binary.LittleEndian.Uint32(raw), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readUint64() (uint64, error) {
|
|
|
|
|
raw, err := dec.readN(8)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return binary.LittleEndian.Uint64(raw), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readFloat64() (float64, error) {
|
|
|
|
|
value, err := dec.readUint64()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return math.Float64frombits(value), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readInt32() (int32, error) {
|
|
|
|
|
value, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return int32(value), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readAddressTable() ([]solana.PublicKey, error) {
|
|
|
|
|
return txBinaryReadAddressTable(dec)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readN(n int) ([]byte, error) {
|
|
|
|
|
out := make([]byte, n)
|
|
|
|
|
if _, err := io.ReadFull(dec.reader, out); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readPlatformEntries(enumTable *txBinaryEnumTable) ([]PlatformBinary, error) {
|
|
|
|
|
return txBinaryReadPlatformEntries(dec, enumTable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readMevAgentEntries(enumTable *txBinaryEnumTable) ([]MevAgentBinary, error) {
|
|
|
|
|
return txBinaryReadMevAgentEntries(dec, enumTable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readSwaps(enumTable *txBinaryEnumTable, _ []solana.PublicKey) ([]SwapBinary, error) {
|
|
|
|
|
return txBinaryReadSwaps(dec, enumTable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryDecoder) readTxBinaryBody(tx *TxBinary, enumTable *txBinaryEnumTable, addressTable []solana.PublicKey) error {
|
|
|
|
|
return txBinaryReadTxBody(dec, tx, enumTable, addressTable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readBool() (bool, error) {
|
|
|
|
|
value, err := dec.readUint8()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false, err
|
|
|
|
|
}
|
|
|
|
|
switch value {
|
|
|
|
|
case 0:
|
|
|
|
|
return false, nil
|
|
|
|
|
case 1:
|
|
|
|
|
return true, nil
|
|
|
|
|
default:
|
|
|
|
|
return false, fmt.Errorf("invalid bool value: %d", value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readUint8() (uint8, error) {
|
|
|
|
|
raw, err := dec.readN(1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return raw[0], nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readUint16() (uint16, error) {
|
|
|
|
|
raw, err := dec.readN(2)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return binary.LittleEndian.Uint16(raw), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readUint32() (uint32, error) {
|
|
|
|
|
raw, err := dec.readN(4)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return binary.LittleEndian.Uint32(raw), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readUint64() (uint64, error) {
|
|
|
|
|
raw, err := dec.readN(8)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return binary.LittleEndian.Uint64(raw), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readFloat64() (float64, error) {
|
|
|
|
|
value, err := dec.readUint64()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return math.Float64frombits(value), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readInt32() (int32, error) {
|
|
|
|
|
value, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return int32(value), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readAddressTable() ([]solana.PublicKey, error) {
|
|
|
|
|
return txBinaryReadAddressTable(dec)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readN(n int) ([]byte, error) {
|
|
|
|
|
out := make([]byte, n)
|
|
|
|
|
if _, err := io.ReadFull(dec.reader, out); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readTxsBinaryHeader() (*txsBinaryHeader, error) {
|
|
|
|
|
magic, err := dec.readN(len(txsBinaryMagic))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(magic, txsBinaryMagic[:]) {
|
|
|
|
|
return nil, fmt.Errorf("invalid txs binary magic")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
schemaVersion, err := dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-06-05 10:35:49 +08:00
|
|
|
if !txBinarySchemaVersionSupported(schemaVersion) {
|
2026-04-16 16:40:21 +08:00
|
|
|
return nil, fmt.Errorf("unsupported tx binary schema version: %d", schemaVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enumVersion, err := dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
enumTable, err := txBinaryEnumTableByVersion(enumVersion)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addressTable, err := dec.readAddressTable()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &txsBinaryHeader{
|
|
|
|
|
schemaVersion: schemaVersion,
|
|
|
|
|
enumVersion: enumVersion,
|
|
|
|
|
addressTable: addressTable,
|
|
|
|
|
enumTable: enumTable,
|
|
|
|
|
count: count,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readNOrEOF(n int) ([]byte, error) {
|
|
|
|
|
out := make([]byte, n)
|
|
|
|
|
readN, err := io.ReadFull(dec.reader, out)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err == io.EOF && readN == 0 {
|
|
|
|
|
return nil, io.EOF
|
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dec *txBinaryStreamDecoder) readTxsBinaryHeaderOrEOF() (*txsBinaryHeader, error) {
|
|
|
|
|
magic, err := dec.readNOrEOF(len(txsBinaryMagic))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(magic, txsBinaryMagic[:]) {
|
|
|
|
|
return nil, fmt.Errorf("invalid txs binary magic")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
schemaVersion, err := dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-06-05 10:35:49 +08:00
|
|
|
if !txBinarySchemaVersionSupported(schemaVersion) {
|
2026-04-16 16:40:21 +08:00
|
|
|
return nil, fmt.Errorf("unsupported tx binary schema version: %d", schemaVersion)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enumVersion, err := dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
enumTable, err := txBinaryEnumTableByVersion(enumVersion)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addressTable, err := dec.readAddressTable()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &txsBinaryHeader{
|
|
|
|
|
schemaVersion: schemaVersion,
|
|
|
|
|
enumVersion: enumVersion,
|
|
|
|
|
addressTable: addressTable,
|
|
|
|
|
enumTable: enumTable,
|
|
|
|
|
count: count,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryReadAddressTable(dec txBinaryBodyReader) ([]solana.PublicKey, error) {
|
|
|
|
|
count, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
addresses := make([]solana.PublicKey, 0, count)
|
|
|
|
|
for i := uint32(0); i < count; i++ {
|
|
|
|
|
raw, err := dec.readN(solana.PublicKeyLength)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var publicKey solana.PublicKey
|
|
|
|
|
copy(publicKey[:], raw)
|
|
|
|
|
addresses = append(addresses, publicKey)
|
|
|
|
|
}
|
|
|
|
|
return addresses, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryReadPlatformEntries(dec txBinaryBodyReader, enumTable *txBinaryEnumTable) ([]PlatformBinary, error) {
|
|
|
|
|
count, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out := make([]PlatformBinary, 0, count)
|
|
|
|
|
for i := uint32(0); i < count; i++ {
|
|
|
|
|
enumID, err := dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-05-13 17:30:06 +08:00
|
|
|
platform, err := enumTable.platforms.valueOrFallback(enumID, PlatformNone)
|
2026-04-16 16:40:21 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("platform[%d]: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
fee, err := dec.readUint64()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out = append(out, PlatformBinary{
|
|
|
|
|
Platform: platform,
|
|
|
|
|
PlatformFee: fee,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryReadMevAgentEntries(dec txBinaryBodyReader, enumTable *txBinaryEnumTable) ([]MevAgentBinary, error) {
|
|
|
|
|
count, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out := make([]MevAgentBinary, 0, count)
|
|
|
|
|
for i := uint32(0); i < count; i++ {
|
|
|
|
|
enumID, err := dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-05-13 17:30:06 +08:00
|
|
|
mevAgent, err := enumTable.mevAgents.valueOrFallback(enumID, MevAgentUnknown)
|
2026-04-16 16:40:21 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("mev_agent[%d]: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
fee, err := dec.readUint64()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out = append(out, MevAgentBinary{
|
|
|
|
|
MevAgent: mevAgent,
|
|
|
|
|
MevAgentFee: fee,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryReadSwaps(dec txBinaryBodyReader, enumTable *txBinaryEnumTable) ([]SwapBinary, error) {
|
|
|
|
|
count, err := dec.readUint32()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out := make([]SwapBinary, 0, count)
|
|
|
|
|
for i := uint32(0); i < count; i++ {
|
|
|
|
|
programID, err := dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
program, err := enumTable.programs.value(programID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("swap[%d].program: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
eventID, err := dec.readUint16()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
event, err := enumTable.events.value(eventID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("swap[%d].event: %w", i, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
swap := SwapBinary{
|
|
|
|
|
Program: program,
|
|
|
|
|
Event: event,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if swap.TxIndex, err = dec.readInt32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.InstrIdx, err = dec.readUint8(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.InnerIdx, err = dec.readUint8(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.Pool, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.BaseMint, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.QuoteMint, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.BaseTokenProgram, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.QuoteTokenProgram, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.Creator, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.BaseMintDecimals, err = dec.readUint8(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.QuoteMintDecimals, err = dec.readUint8(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.User, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.BaseAmount, err = dec.readUint64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.QuoteAmount, err = dec.readUint64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
swapMode, err := dec.readUint8()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
swap.SwapMode = SwapMode(swapMode)
|
|
|
|
|
if swap.FixedAmount, err = dec.readUint64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
fixedAmountSide, err := dec.readUint8()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
swap.FixedAmountSide = SwapAmountSide(fixedAmountSide)
|
|
|
|
|
if swap.FixedMint, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
limitType, err := dec.readUint8()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
swap.LimitAmountType = SwapLimitType(limitType)
|
|
|
|
|
if swap.LimitAmount, err = dec.readUint64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
limitAmountSide, err := dec.readUint8()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
swap.LimitAmountSide = SwapAmountSide(limitAmountSide)
|
|
|
|
|
if swap.LimitMint, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.ActualLimitAmount, err = dec.readUint64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
actualLimitAmountSide, err := dec.readUint8()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
swap.ActualLimitAmountSide = SwapAmountSide(actualLimitAmountSide)
|
|
|
|
|
if swap.SlippageBps, err = dec.readUint64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-04-20 15:09:42 +08:00
|
|
|
if swap.BaseReserve, err = dec.readFloat64(); err != nil {
|
2026-04-16 16:40:21 +08:00
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-04-20 15:09:42 +08:00
|
|
|
if swap.QuoteReserve, err = dec.readFloat64(); err != nil {
|
2026-04-16 16:40:21 +08:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.Mayhem, err = dec.readBool(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.Cashback, err = dec.readBool(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.UserBaseBalance, err = dec.readUint64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.UserQuoteBalance, err = dec.readUint64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.EntryContract, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.MigrateToPool, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.MigrateTopProgram, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.LpMint, err = dec.readUint32(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if swap.AfterSOLBalance, err = dec.readFloat64(); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out = append(out, swap)
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryReadTxBody(dec txBinaryBodyReader, tx *TxBinary, enumTable *txBinaryEnumTable, addressTable []solana.PublicKey) error {
|
|
|
|
|
var err error
|
|
|
|
|
tx.AddressTable = addressTable
|
|
|
|
|
if tx.Signer, err = dec.readUint32(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Block, err = dec.readUint64(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.BlockIndex, err = dec.readUint64(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-06-05 10:35:49 +08:00
|
|
|
if tx.SchemaVersion >= txBinarySchemaVersionCurrent {
|
|
|
|
|
blockAt, err := dec.readUint64()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
tx.BlockAt = int64(blockAt)
|
|
|
|
|
}
|
2026-04-16 16:40:21 +08:00
|
|
|
|
|
|
|
|
hasTxHash, err := dec.readBool()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if hasTxHash {
|
|
|
|
|
rawHash, err := dec.readN(64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
var txHash [64]byte
|
|
|
|
|
copy(txHash[:], rawHash)
|
|
|
|
|
tx.TxHash = &txHash
|
|
|
|
|
} else {
|
|
|
|
|
tx.TxHash = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tx.CuFee, err = dec.readUint64(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.CUPrice, err = dec.readUint64(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.BeforeSolBalance, err = dec.readFloat64(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.AfterSOLBalance, err = dec.readFloat64(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.ComputeUnitsConsumed, err = dec.readUint64(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.CuLimit, err = dec.readUint32(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Platform, err = txBinaryReadPlatformEntries(dec, enumTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.MevAgent, err = txBinaryReadMevAgentEntries(dec, enumTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps, err = txBinaryReadSwaps(dec, enumTable); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 17:56:17 +08:00
|
|
|
func txBinaryBuildMergePlan(sources []TxsBinaryReaderSource, opts TxsBinaryMergeOptions) (*txsBinaryMergePlan, error) {
|
2026-04-16 16:40:21 +08:00
|
|
|
if len(sources) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("txs binary sources are empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder := txBinaryAddressTableBuilder{
|
|
|
|
|
index: make(map[solana.PublicKey]struct{}),
|
|
|
|
|
}
|
2026-06-05 10:35:49 +08:00
|
|
|
plan := &txsBinaryMergePlan{
|
|
|
|
|
schemaVersion: txBinarySchemaVersionCurrent,
|
|
|
|
|
}
|
2026-04-16 16:40:21 +08:00
|
|
|
hasBatch := false
|
|
|
|
|
|
|
|
|
|
for sourceIndex, source := range sources {
|
|
|
|
|
if source == nil {
|
|
|
|
|
return nil, fmt.Errorf("source[%d] is nil", sourceIndex)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reader, err := source.OpenTxsBinaryReader()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("source[%d]: open reader: %w", sourceIndex, err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 17:56:17 +08:00
|
|
|
bufferedReader := bufio.NewReader(reader)
|
|
|
|
|
dec := txBinaryStreamDecoder{reader: bufferedReader}
|
2026-04-16 16:40:21 +08:00
|
|
|
batchIndex := 0
|
|
|
|
|
for {
|
2026-04-16 17:56:17 +08:00
|
|
|
skipBatch, err := txBinaryApplyMergeBatchHeader(bufferedReader, opts, sourceIndex, batchIndex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeErr := reader.Close()
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
if closeErr != nil {
|
|
|
|
|
return nil, fmt.Errorf("source[%d]: close reader: %w", sourceIndex, closeErr)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("source[%d].batch[%d]: %w", sourceIndex, batchIndex, err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
header, err := dec.readTxsBinaryHeaderOrEOF()
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeErr := reader.Close()
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
if closeErr != nil {
|
|
|
|
|
return nil, fmt.Errorf("source[%d]: close reader: %w", sourceIndex, closeErr)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("source[%d].batch[%d]: %w", sourceIndex, batchIndex, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !hasBatch {
|
|
|
|
|
plan.enumVersion = header.enumVersion
|
|
|
|
|
plan.enumTable = header.enumTable
|
|
|
|
|
hasBatch = true
|
|
|
|
|
} else {
|
|
|
|
|
if header.enumVersion != plan.enumVersion {
|
|
|
|
|
reader.Close()
|
|
|
|
|
return nil, fmt.Errorf("source[%d].batch[%d]: enum version mismatch: got %d want %d", sourceIndex, batchIndex, header.enumVersion, plan.enumVersion)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for addressIndex, address := range header.addressTable {
|
2026-04-16 17:56:17 +08:00
|
|
|
if !skipBatch {
|
|
|
|
|
if err := builder.add(address); err != nil {
|
|
|
|
|
reader.Close()
|
|
|
|
|
return nil, fmt.Errorf("source[%d].batch[%d].address[%d]: %w", sourceIndex, batchIndex, addressIndex, err)
|
|
|
|
|
}
|
2026-04-16 16:40:21 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 17:56:17 +08:00
|
|
|
if !skipBatch {
|
|
|
|
|
if uint64(plan.txCount)+uint64(header.count) > uint64(math.MaxUint32) {
|
|
|
|
|
reader.Close()
|
|
|
|
|
return nil, fmt.Errorf("merged tx count exceeds uint32 capacity")
|
|
|
|
|
}
|
|
|
|
|
plan.txCount += header.count
|
2026-04-16 16:40:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for txIndex := uint32(0); txIndex < header.count; txIndex++ {
|
|
|
|
|
tx := TxBinary{
|
|
|
|
|
SchemaVersion: header.schemaVersion,
|
|
|
|
|
EnumVersion: header.enumVersion,
|
|
|
|
|
AddressTable: header.addressTable,
|
|
|
|
|
}
|
|
|
|
|
if err := txBinaryReadTxBody(&dec, &tx, header.enumTable, header.addressTable); err != nil {
|
|
|
|
|
reader.Close()
|
|
|
|
|
return nil, fmt.Errorf("source[%d].batch[%d].tx[%d]: %w", sourceIndex, batchIndex, txIndex, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
batchIndex++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !hasBatch {
|
|
|
|
|
return nil, fmt.Errorf("no txs binary batches found")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addressIndex, err := newTxBinaryAddressIndex(builder.addresses)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
plan.addressTable = builder.addresses
|
|
|
|
|
plan.addressIndex = addressIndex
|
|
|
|
|
return plan, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryRemapTxAddressTable(tx *TxBinary, fromAddressTable []solana.PublicKey, toAddressTable []solana.PublicKey, toAddressIndex *txBinaryAddressIndex) error {
|
|
|
|
|
var err error
|
|
|
|
|
if tx.Signer, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Signer, "tx.signer"); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := range tx.Swaps {
|
|
|
|
|
if tx.Swaps[i].Pool, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].Pool, fmt.Sprintf("swap[%d].pool", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].BaseMint, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].BaseMint, fmt.Sprintf("swap[%d].base_mint", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].QuoteMint, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].QuoteMint, fmt.Sprintf("swap[%d].quote_mint", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].BaseTokenProgram, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].BaseTokenProgram, fmt.Sprintf("swap[%d].base_token_program", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].QuoteTokenProgram, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].QuoteTokenProgram, fmt.Sprintf("swap[%d].quote_token_program", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].Creator, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].Creator, fmt.Sprintf("swap[%d].creator", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].User, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].User, fmt.Sprintf("swap[%d].user", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].FixedMint, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].FixedMint, fmt.Sprintf("swap[%d].fixed_mint", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].LimitMint, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].LimitMint, fmt.Sprintf("swap[%d].limit_mint", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].EntryContract, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].EntryContract, fmt.Sprintf("swap[%d].entry_contract", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].MigrateToPool, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].MigrateToPool, fmt.Sprintf("swap[%d].migrate_to_pool", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].MigrateTopProgram, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].MigrateTopProgram, fmt.Sprintf("swap[%d].migrate_top_program", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if tx.Swaps[i].LpMint, err = txBinaryRemapAddressRef(fromAddressTable, toAddressIndex, tx.Swaps[i].LpMint, fmt.Sprintf("swap[%d].lp_mint", i)); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tx.AddressTable = toAddressTable
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryRemapAddressRef(fromAddressTable []solana.PublicKey, toAddressIndex *txBinaryAddressIndex, fromRef uint32, field string) (uint32, error) {
|
|
|
|
|
address, err := txBinaryAddressAt(fromAddressTable, fromRef, field)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return toAddressIndex.id(address)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryWriteAll(w io.Writer, data []byte) error {
|
|
|
|
|
for len(data) > 0 {
|
|
|
|
|
written, err := w.Write(data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if written == 0 {
|
|
|
|
|
return io.ErrShortWrite
|
|
|
|
|
}
|
|
|
|
|
data = data[written:]
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 17:56:17 +08:00
|
|
|
func txBinaryApplyMergeBatchHeader(reader *bufio.Reader, opts TxsBinaryMergeOptions, sourceIndex int, batchIndex int) (bool, error) {
|
|
|
|
|
if opts.BatchHeaderFunc == nil {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
return opts.BatchHeaderFunc(&TxsBinaryBatchHeaderContext{
|
|
|
|
|
SourceIndex: sourceIndex,
|
|
|
|
|
BatchIndex: batchIndex,
|
|
|
|
|
Reader: reader,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
type txBinaryEnumTable struct {
|
|
|
|
|
version uint16
|
|
|
|
|
programs txBinaryEnumSet
|
|
|
|
|
events txBinaryEnumSet
|
|
|
|
|
platforms txBinaryEnumSet
|
|
|
|
|
mevAgents txBinaryEnumSet
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type txBinaryEnumSet struct {
|
|
|
|
|
name string
|
|
|
|
|
values []string
|
|
|
|
|
ids map[string]uint16
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var txBinaryEnumTables = map[uint16]*txBinaryEnumTable{
|
|
|
|
|
txBinaryEnumVersionV1: newTxBinaryEnumTable(
|
|
|
|
|
txBinaryEnumVersionV1,
|
|
|
|
|
"program",
|
|
|
|
|
[]string{
|
|
|
|
|
"",
|
|
|
|
|
SolProgramPump,
|
|
|
|
|
SolProgramRaydiumV4,
|
|
|
|
|
SolProgramRaydiumCLMM,
|
|
|
|
|
SolProgramRaydiumCPMM,
|
|
|
|
|
SolProgramMeteoraDLMM,
|
|
|
|
|
SolProgramOrcaWhirPool,
|
|
|
|
|
SolProgramPumpAMM,
|
|
|
|
|
SolProgramMeteoraAmmV2,
|
|
|
|
|
SolProgramMeteoraBondingCurve,
|
|
|
|
|
SolProgramMeteoraPools,
|
|
|
|
|
SolProgramRaydiumLaunchLab,
|
|
|
|
|
SolProgramRaydiumLaunchLabBonk,
|
|
|
|
|
},
|
|
|
|
|
"event",
|
|
|
|
|
[]string{
|
|
|
|
|
"",
|
|
|
|
|
TxEventAddLP,
|
|
|
|
|
TxEventRemoveLP,
|
|
|
|
|
TxEventBuy,
|
|
|
|
|
TxEventSell,
|
|
|
|
|
TxEventBuyFailed,
|
|
|
|
|
TxEventSellFailed,
|
|
|
|
|
TxEventBurn,
|
2026-04-20 14:16:20 +08:00
|
|
|
TxEventCreate,
|
|
|
|
|
TxEventComplete,
|
|
|
|
|
TxEventMigrate,
|
|
|
|
|
TxEventDeposit,
|
|
|
|
|
TxEventWithdraw,
|
|
|
|
|
TxEventOpen,
|
|
|
|
|
TxEventClose,
|
|
|
|
|
TxEventClaimFee,
|
|
|
|
|
TxEventAddLiquidity,
|
|
|
|
|
TxEventAddLiquidityOneSide,
|
|
|
|
|
TxEventRemoveLiquidity,
|
|
|
|
|
TxEventRemoveLiquidityOneSide,
|
2026-04-16 16:40:21 +08:00
|
|
|
},
|
|
|
|
|
"platform",
|
|
|
|
|
[]string{
|
|
|
|
|
"",
|
|
|
|
|
PlatformGMGN,
|
|
|
|
|
PlatformPhoton,
|
|
|
|
|
PlatformAxiom,
|
|
|
|
|
PlatformPepe,
|
|
|
|
|
PlatformBullX,
|
|
|
|
|
PlatformBanana,
|
|
|
|
|
PlatformTrojan,
|
|
|
|
|
PlatformRaybot,
|
|
|
|
|
PlatformMoonshot,
|
|
|
|
|
PlatformMEVX,
|
|
|
|
|
PlatformTradeWiz,
|
|
|
|
|
PlatformSolTradingBot,
|
|
|
|
|
PlatformMoonshotMoney,
|
|
|
|
|
PlatformMaestro,
|
|
|
|
|
PlatformBonkBot,
|
|
|
|
|
PlatformPadre,
|
|
|
|
|
PlatformDexScreener,
|
|
|
|
|
PlatformFake,
|
|
|
|
|
PlatformNone,
|
|
|
|
|
},
|
|
|
|
|
"mev_agent",
|
|
|
|
|
[]string{
|
|
|
|
|
"",
|
|
|
|
|
MevAgentJito,
|
|
|
|
|
MevAgent0slot,
|
|
|
|
|
MevAgentBlocxRoute,
|
|
|
|
|
MevAgentNozomi,
|
|
|
|
|
MevAgentNextBlock,
|
|
|
|
|
MevAgentHelius,
|
|
|
|
|
MevAgentNode1,
|
|
|
|
|
MevAgentFlashBlock,
|
|
|
|
|
MevAgentUnknown,
|
|
|
|
|
MevAgentBlockRazor,
|
|
|
|
|
MevAgentFast,
|
|
|
|
|
MevAgentSoyas,
|
|
|
|
|
MevAgentStellium,
|
|
|
|
|
MevAgentAstralane,
|
|
|
|
|
MevagentFa1con,
|
|
|
|
|
MevagentBlocksprint,
|
|
|
|
|
MevAgentMoon,
|
|
|
|
|
MevAgentSpeedlanding,
|
|
|
|
|
MevAgentAllenhark,
|
|
|
|
|
MevAgentRaiden,
|
2026-05-13 17:07:47 +08:00
|
|
|
MevAgentZan,
|
2026-05-13 17:30:06 +08:00
|
|
|
MevAgentTunneling,
|
2026-04-16 16:40:21 +08:00
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func txBinaryEnumTableByVersion(version uint16) (*txBinaryEnumTable, error) {
|
|
|
|
|
table, ok := txBinaryEnumTables[version]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, fmt.Errorf("unsupported tx binary enum version: %d", version)
|
|
|
|
|
}
|
|
|
|
|
return table, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newTxBinaryEnumTable(
|
|
|
|
|
version uint16,
|
|
|
|
|
programName string,
|
|
|
|
|
programs []string,
|
|
|
|
|
eventName string,
|
|
|
|
|
events []string,
|
|
|
|
|
platformName string,
|
|
|
|
|
platforms []string,
|
|
|
|
|
mevAgentName string,
|
|
|
|
|
mevAgents []string,
|
|
|
|
|
) *txBinaryEnumTable {
|
|
|
|
|
return &txBinaryEnumTable{
|
|
|
|
|
version: version,
|
|
|
|
|
programs: newTxBinaryEnumSet(programName, programs),
|
|
|
|
|
events: newTxBinaryEnumSet(eventName, events),
|
|
|
|
|
platforms: newTxBinaryEnumSet(platformName, platforms),
|
|
|
|
|
mevAgents: newTxBinaryEnumSet(mevAgentName, mevAgents),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newTxBinaryEnumSet(name string, values []string) txBinaryEnumSet {
|
|
|
|
|
ids := make(map[string]uint16, len(values))
|
|
|
|
|
for i, value := range values {
|
|
|
|
|
if _, exists := ids[value]; exists {
|
|
|
|
|
panic(fmt.Sprintf("duplicate %s enum value: %q", name, value))
|
|
|
|
|
}
|
|
|
|
|
ids[value] = uint16(i)
|
|
|
|
|
}
|
|
|
|
|
return txBinaryEnumSet{
|
|
|
|
|
name: name,
|
|
|
|
|
values: values,
|
|
|
|
|
ids: ids,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (set txBinaryEnumSet) id(value string) (uint16, error) {
|
|
|
|
|
id, ok := set.ids[value]
|
|
|
|
|
if !ok {
|
|
|
|
|
return 0, fmt.Errorf("unsupported %s enum value %q for versioned tx binary", set.name, value)
|
|
|
|
|
}
|
|
|
|
|
return id, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 17:30:06 +08:00
|
|
|
func (set txBinaryEnumSet) idOrFallback(value string, fallback string) (uint16, error) {
|
|
|
|
|
if id, ok := set.ids[value]; ok {
|
|
|
|
|
return id, nil
|
|
|
|
|
}
|
|
|
|
|
id, ok := set.ids[fallback]
|
|
|
|
|
if !ok {
|
|
|
|
|
return 0, fmt.Errorf("unsupported %s fallback enum value %q for versioned tx binary", set.name, fallback)
|
|
|
|
|
}
|
|
|
|
|
return id, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-16 16:40:21 +08:00
|
|
|
func (set txBinaryEnumSet) value(id uint16) (string, error) {
|
|
|
|
|
if int(id) >= len(set.values) {
|
|
|
|
|
return "", fmt.Errorf("unknown %s enum id %d", set.name, id)
|
|
|
|
|
}
|
|
|
|
|
return set.values[id], nil
|
|
|
|
|
}
|
2026-05-13 17:30:06 +08:00
|
|
|
|
|
|
|
|
func (set txBinaryEnumSet) valueOrFallback(id uint16, fallback string) (string, error) {
|
|
|
|
|
if int(id) < len(set.values) {
|
|
|
|
|
return set.values[id], nil
|
|
|
|
|
}
|
|
|
|
|
if _, ok := set.ids[fallback]; !ok {
|
|
|
|
|
return "", fmt.Errorf("unsupported %s fallback enum value %q for versioned tx binary", set.name, fallback)
|
|
|
|
|
}
|
|
|
|
|
return fallback, nil
|
|
|
|
|
}
|