Compare commits

..

4 Commits

Author SHA1 Message Date
thloyi
738e417167 fix EncodeTxBinary 2026-04-20 15:25:08 +08:00
thloyi
51f1511c8f fix EncodeTxBinary 2026-04-20 15:09:42 +08:00
thloyi
7dfe003e5b fix event enum 2026-04-20 14:16:20 +08:00
thloyi
fe94888b14 fix slippage 2026-04-20 12:31:30 +08:00
10 changed files with 410 additions and 86 deletions

View File

@@ -68,7 +68,7 @@ Interpretation:
- Positive: execution is better than the user limit
- Zero: execution lands exactly on the user limit
- `10000`: user limit is effectively unbounded on the constrained side (for example `min_out = 0`)
- Negative: this usually indicates an incorrect parser-side mapping or inconsistent source data
- Negative raw headroom is clamped to `0` because successful-swap storage uses a non-negative bounded metric
This definition makes `SlippageBps` a bounded "remaining headroom to the user's limit" metric for successful swaps:

13
enum.go
View File

@@ -126,4 +126,17 @@ const (
TxEventBuyFailed = "buy_failed"
TxEventSellFailed = "sell_failed"
TxEventBurn = "burn"
TxEventCreate = "create"
TxEventComplete = "complete"
TxEventMigrate = "migrate"
TxEventDeposit = "deposit"
TxEventWithdraw = "withdraw"
TxEventOpen = "open"
TxEventClose = "close"
TxEventClaimFee = "claim_fee"
TxEventAddLiquidity = "add_liquidity"
TxEventAddLiquidityOneSide = "add_liquidity_one_side"
TxEventRemoveLiquidity = "remove_liquidity"
TxEventRemoveLiquidityOneSide = "remove_liquidity_one_side"
)

View File

@@ -25,7 +25,7 @@ func main() {
// laserstream-mainnet-slc.helius-rpc.com:80
ch := make(chan example.SubscriptionMessage, 1)
go example.RunLoopWithReConnect(context.Background(), "127.0.0.1:10001", parser.SolProgramPump, ch)
go example.RunLoopWithReConnect(context.Background(), "", "", parser.SolProgramPump, ch)
// var tokenTxs = make(map[string]*types.Tx)
// currentBlock := uint64(0)
for msg := range ch {
@@ -51,9 +51,24 @@ func main() {
//}
// 处理交易
if len(ptx.Swaps) > 0 && (ptx.Swaps[0].Program == parser.SolProgramPump || ptx.Swaps[0].Program == parser.SolProgramPumpAMM) {
fmt.Printf("success tx : %s, program: %s, event: %s, block: %d, tx: %s, base: %s, quote: %s \n", time.Now().Format("2006-01-02 15:04:05"), ptx.Swaps[0].Program, ptx.Swaps[0].Event, ptx.Block, ptx.GetTxHash(),
ptx.Swaps[0].BaseAmount.Div(decimal.NewFromInt(1e6)), ptx.Swaps[0].QuoteAmount.Div(decimal.NewFromInt(1e9)))
if len(ptx.Swaps) > 0 {
for _, swap := range ptx.Swaps {
if swap.SlippageBps.LessThan(decimal.Zero) || swap.SlippageBps.GreaterThan(decimal.NewFromInt(10000)) {
fmt.Printf("success tx : %s, program: %s, event: %s, block: %d, tx: %s, base: %s, quote: %s \n", time.Now().Format("2006-01-02 15:04:05"), swap.Program, swap.Event, ptx.Block, ptx.GetTxHash(),
swap.BaseAmount.Div(decimal.NewFromInt(1e6)), swap.QuoteAmount.Div(decimal.NewFromInt(1e9)))
}
if swap.SlippageBps.Equal(decimal.Zero) && (swap.Event == "buy" || swap.Event == "sell") {
fmt.Printf("zero success tx : %s, program: %s, event: %s, block: %d, tx: %s, base: %s, quote: %s, fix: %s, limit: %s, \n", time.Now().Format("2006-01-02 15:04:05"), swap.Program, swap.Event, ptx.Block, ptx.GetTxHash(),
swap.BaseAmount.Div(decimal.NewFromInt(1e6)), swap.QuoteAmount.Div(decimal.NewFromInt(1e9)), swap.FixedAmount.String(), swap.LimitAmount.String())
}
}
if len(ptx.Swaps) > 0 {
_, err := parser.EncodeTxBinary(ptx)
if err != nil {
fmt.Printf("success tx : %s, , block: %d, tx: %s, err: %s \n", time.Now().Format("2006-01-02 15:04:05"), ptx.Block, ptx.GetTxHash(), err.Error())
}
}
}
// currentBlock = ptx.Block
//

View File

@@ -45,9 +45,11 @@ type Client struct {
firstMessage bool
handler Handler
xToken string
}
func NewClientWithPumpSwap(endpoint string, ch chan SubscriptionMessage) *Client {
func NewClientWithPumpSwap(endpoint string, xtoken string, ch chan SubscriptionMessage) *Client {
var subscription pb.SubscribeRequest
//var failed = true
@@ -58,10 +60,10 @@ func NewClientWithPumpSwap(endpoint string, ch chan SubscriptionMessage) *Client
Vote: &vote,
}
subscription.Transactions["transactions_sub"].AccountInclude = []string{
"pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", //Pump AMM
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", //Pump
}
//subscription.Transactions["transactions_sub"].AccountInclude = []string{
// "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", //Pump AMM
// "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", //Pump
//}
subscription.BlocksMeta = make(map[string]*pb.SubscribeRequestFilterBlocksMeta)
subscription.BlocksMeta["block_meta"] = &pb.SubscribeRequestFilterBlocksMeta{}
@@ -72,6 +74,7 @@ func NewClientWithPumpSwap(endpoint string, ch chan SubscriptionMessage) *Client
lastReceiveTime: time.Now(),
subStatus: false,
subscription: &subscription,
xToken: xtoken,
}
c.handler = NewPumpHandler(func(tx *types.Tx) {
c.sendTx(tx)
@@ -112,12 +115,12 @@ func NewClientWithLaunchLab(endpoint string, ch chan SubscriptionMessage) *Clien
return c
}
func RunLoopWithReConnect(ctx context.Context, endpoint, program string, ch chan SubscriptionMessage) {
func RunLoopWithReConnect(ctx context.Context, endpoint, token, program string, ch chan SubscriptionMessage) {
var client *Client
if program == types.SolProgramRaydiumLaunchLab {
client = NewClientWithLaunchLab(endpoint, ch)
} else {
client = NewClientWithPumpSwap(endpoint, ch)
client = NewClientWithPumpSwap(endpoint, token, ch)
}
for {
select {
@@ -206,12 +209,13 @@ func (c *Client) grpcSubscribe(ctx context.Context, conn *grpc.ClientConn) error
log.Printf("Subscription request: %s", string(subscriptionJson))
// Set up the subscription request
//if *token != "" {
// md := metadata.New(map[string]string{"x-token": *token})
// ctx = metadata.NewOutgoingContext(ctx, md)
//}
md := metadata.New(map[string]string{"x-token": "5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"})
if c.xToken != "" {
fmt.Println("xtoken", c.xToken)
md := metadata.New(map[string]string{"x-token": c.xToken})
ctx = metadata.NewOutgoingContext(ctx, md)
}
//md := metadata.New(map[string]string{"x-token": "5adcf1f9-5719-43d1-bf3f-c2d4e1e5f94d"})
//ctx = metadata.NewOutgoingContext(ctx, md)
stream, err := client.Subscribe(ctx)
if err != nil {

View File

@@ -193,6 +193,7 @@ func meteoraDammSwapAmountInfo(event string, params *struct {
Amount1 uint64
SwapMode uint8
}) (swapMode SwapMode, fixedAmount decimal.Decimal, limitAmount decimal.Decimal, ok bool) {
_ = event
if params == nil {
return SwapModeUnknown, decimal.Zero, decimal.Zero, false
}
@@ -203,21 +204,14 @@ func meteoraDammSwapAmountInfo(event string, params *struct {
// - ExactIn / PartialFill: amount0=amount_in, amount1=minimum_amount_out
// - ExactOut: amount0=amount_out, amount1=maximum_amount_in
//
// The emitted event is normalized as token A <-> token B:
// - `sell` means A -> B, so A is the input side and B is the output side
// - `buy` means B -> A, so B is the input side and A is the output side
// `SetSwapAmountInfo` derives sides from the normalized buy/sell event, so
// the instruction parameters should stay in raw IDL order here.
switch params.SwapMode {
case 0, 1: // ExactIn / PartialFill
swapMode = SwapModeExactIn
if event == TxEventSell {
return swapMode, decimal.NewFromUint64(params.Amount0), decimal.NewFromUint64(params.Amount1), true
}
return swapMode, decimal.NewFromUint64(params.Amount1), decimal.NewFromUint64(params.Amount0), true
case 2: // ExactOut
swapMode = SwapModeExactOut
if event == TxEventSell {
return swapMode, decimal.NewFromUint64(params.Amount1), decimal.NewFromUint64(params.Amount0), true
}
return swapMode, decimal.NewFromUint64(params.Amount0), decimal.NewFromUint64(params.Amount1), true
default:
return SwapModeUnknown, decimal.Zero, decimal.Zero, false

View File

@@ -281,7 +281,7 @@ func orcaWhirPoolLiquidityParser(tx *Tx, instruction Instruction, innerInstructi
return nil, increaseOffset(offset), InstructionIgnoredError
}
if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) {
instructionName += "_on_side"
instructionName += "_one_side"
}
if (baseTokenBalance == nil && !baseAmount.Equal(decimal.Zero)) || (quoteTokenBalance == nil && !quoteAmount.Equal(decimal.Zero)) {
return nil, offset, fmt.Errorf("token balance is nil but amount is not zero")
@@ -388,7 +388,7 @@ func orcaWhirPoolLiquidityV2Parser(tx *Tx, instruction Instruction, innerInstruc
return nil, offset, InstructionIgnoredError
}
if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) {
instructionName += "_on_side"
instructionName += "_one_side"
}
if (baseTokenBalance == nil && !baseAmount.Equal(decimal.Zero)) || (quoteTokenBalance == nil && !quoteAmount.Equal(decimal.Zero)) {
return nil, offset, fmt.Errorf("token balance is nil but amount is not zero")
@@ -493,7 +493,7 @@ func orcaWhirPoolCollectFeeParser(tx *Tx, instruction Instruction, innerInstruct
return nil, offset, InstructionIgnoredError
}
if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) {
instructionName += "_on_side"
instructionName += "_one_side"
}
if (baseTokenBalance == nil && !baseAmount.Equal(decimal.Zero)) || (quoteTokenBalance == nil && !quoteAmount.Equal(decimal.Zero)) {
return nil, offset, fmt.Errorf("token balance is nil but amount is not zero")
@@ -595,7 +595,7 @@ func orcaWhirPoolCollectFeeV2Parser(tx *Tx, instruction Instruction, innerInstru
return nil, offset, InstructionIgnoredError
}
if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) {
instructionName += "_on_side"
instructionName += "_one_side"
}
if (baseTokenBalance == nil && !baseAmount.Equal(decimal.Zero)) || (quoteTokenBalance == nil && !quoteAmount.Equal(decimal.Zero)) {
return nil, offset, fmt.Errorf("token balance is nil but amount is not zero")
@@ -697,7 +697,7 @@ func orcaWhirPoolCollectProtocolFeeV2Parser(tx *Tx, instruction Instruction, inn
return nil, offset, InstructionIgnoredError
}
if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) {
instructionName += "_on_side"
instructionName += "_one_side"
}
if (baseTokenBalance == nil && !baseAmount.Equal(decimal.Zero)) || (quoteTokenBalance == nil && !quoteAmount.Equal(decimal.Zero)) {
return nil, offset, fmt.Errorf("token balance is nil but amount is not zero")

View File

@@ -9,6 +9,16 @@ import (
var maxSlippageBps = decimal.NewFromInt(10000)
func normalizeSlippageBps(value decimal.Decimal) decimal.Decimal {
//if value.IsNegative() {
// return decimal.Zero
//}
//if value.GreaterThan(maxSlippageBps) {
// return maxSlippageBps
//}
return value
}
type SwapMode uint8
type SwapAmountSide uint8
type SwapLimitType uint8
@@ -141,29 +151,36 @@ func limitSwapAmountType(swapMode SwapMode) SwapLimitType {
}
func calculateLimitSlippageBps(limitType SwapLimitType, limitAmount, actualAmount decimal.Decimal) decimal.Decimal {
var value decimal.Decimal
switch limitType {
case SwapLimitTypeMinOut:
if !actualAmount.IsPositive() {
if !limitAmount.IsPositive() {
return maxSlippageBps
value = maxSlippageBps
break
}
return maxSlippageBps.Neg()
value = maxSlippageBps.Neg()
break
}
if !limitAmount.IsPositive() {
return maxSlippageBps
value = maxSlippageBps
break
}
return actualAmount.Sub(limitAmount).Mul(maxSlippageBps).Div(actualAmount)
value = actualAmount.Sub(limitAmount).Mul(maxSlippageBps).Div(actualAmount)
case SwapLimitTypeMaxIn:
if !limitAmount.IsPositive() {
if !actualAmount.IsPositive() {
return maxSlippageBps
value = maxSlippageBps
break
}
return maxSlippageBps.Neg()
value = maxSlippageBps.Neg()
break
}
return limitAmount.Sub(actualAmount).Mul(maxSlippageBps).Div(limitAmount)
value = limitAmount.Sub(actualAmount).Mul(maxSlippageBps).Div(limitAmount)
default:
return decimal.Zero
value = decimal.Zero
}
return normalizeSlippageBps(value)
}
func (s *Swap) SetSwapAmountInfoDetailed(

View File

@@ -79,6 +79,38 @@ func TestSetSwapAmountInfoExactInZeroLimitUsesMaxSlippage(t *testing.T) {
}
}
func TestSetSwapAmountInfoExactInNegativeHeadroomClampsToZero(t *testing.T) {
swap := Swap{
Event: TxEventBuy,
BaseMint: solana.MustPublicKeyFromBase58("11111111111111111111111111111111"),
QuoteMint: solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112"),
BaseAmount: decimal.NewFromInt(90),
QuoteAmount: decimal.NewFromInt(100),
}
swap.SetSwapAmountInfo(SwapModeExactIn, decimal.NewFromInt(100), decimal.NewFromInt(110))
if got := swap.SlippageBps.String(); got != "0" {
t.Fatalf("slippage bps = %s, want 0", got)
}
}
func TestSetSwapAmountInfoExactOutNegativeHeadroomClampsToZero(t *testing.T) {
swap := Swap{
Event: TxEventSell,
BaseMint: solana.MustPublicKeyFromBase58("11111111111111111111111111111111"),
QuoteMint: solana.MustPublicKeyFromBase58("So11111111111111111111111111111111111111112"),
BaseAmount: decimal.NewFromInt(120),
QuoteAmount: decimal.NewFromInt(100),
}
swap.SetSwapAmountInfo(SwapModeExactOut, decimal.NewFromInt(100), decimal.NewFromInt(105))
if got := swap.SlippageBps.String(); got != "0" {
t.Fatalf("slippage bps = %s, want 0", got)
}
}
func TestMeteoraDammSwapAmountInfo(t *testing.T) {
tests := []struct {
name string
@@ -116,6 +148,18 @@ func TestMeteoraDammSwapAmountInfo(t *testing.T) {
wantFixed: 101,
wantLimit: 96,
},
{
name: "buy exact in keeps amount0 as input and amount1 as min out",
event: TxEventBuy,
params: &struct {
Amount0 uint64
Amount1 uint64
SwapMode uint8
}{Amount0: 130, Amount1: 120, SwapMode: 0},
wantMode: SwapModeExactIn,
wantFixed: 130,
wantLimit: 120,
},
{
name: "buy exact out uses amount0 as target output and amount1 as max input",
event: TxEventBuy,
@@ -128,6 +172,18 @@ func TestMeteoraDammSwapAmountInfo(t *testing.T) {
wantFixed: 120,
wantLimit: 130,
},
{
name: "sell exact out keeps amount0 as target output and amount1 as max input",
event: TxEventSell,
params: &struct {
Amount0 uint64
Amount1 uint64
SwapMode uint8
}{Amount0: 140, Amount1: 150, SwapMode: 2},
wantMode: SwapModeExactOut,
wantFixed: 140,
wantLimit: 150,
},
}
for _, tt := range tests {

View File

@@ -80,8 +80,8 @@ type SwapBinary struct {
ActualLimitAmountSide SwapAmountSide
SlippageBps uint64
BaseReserve uint64
QuoteReserve uint64
BaseReserve float64
QuoteReserve float64
Mayhem bool
Cashback bool
@@ -728,7 +728,7 @@ func newSwapBinary(swap Swap, index int, addressIndex *txBinaryAddressIndex) (Sw
out := SwapBinary{
Program: swap.Program,
Event: swap.Event,
Event: txBinaryCanonicalEvent(swap.Event),
TxIndex: int32(swap.TxIndex),
InstrIdx: swap.InstrIdx,
InnerIdx: swap.InnerIdx,
@@ -777,10 +777,10 @@ func newSwapBinary(swap Swap, index int, addressIndex *txBinaryAddressIndex) (Sw
if out.SlippageBps, err = txBinaryRoundedDecimalToUint64(swap.SlippageBps, fmt.Sprintf("swap[%d].slippage_bps", index)); err != nil {
return SwapBinary{}, err
}
if out.BaseReserve, err = txBinaryDecimalToUint64(swap.BaseReserve, fmt.Sprintf("swap[%d].base_reserve", index)); err != nil {
if out.BaseReserve, err = txBinaryDecimalToFloat64Raw(swap.BaseReserve, fmt.Sprintf("swap[%d].base_reserve", index)); err != nil {
return SwapBinary{}, err
}
if out.QuoteReserve, err = txBinaryDecimalToUint64(swap.QuoteReserve, fmt.Sprintf("swap[%d].quote_reserve", index)); err != nil {
if out.QuoteReserve, err = txBinaryDecimalToFloat64Raw(swap.QuoteReserve, fmt.Sprintf("swap[%d].quote_reserve", index)); err != nil {
return SwapBinary{}, err
}
if out.UserBaseBalance, err = txBinaryDecimalToUint64(swap.UserBaseBalance, fmt.Sprintf("swap[%d].user_base_balance", index)); err != nil {
@@ -878,8 +878,8 @@ func (swap SwapBinary) toSwap(addressTable []solana.PublicKey, index int) (Swap,
ActualLimitAmount: decimal.NewFromUint64(swap.ActualLimitAmount),
ActualLimitAmountSide: swap.ActualLimitAmountSide,
SlippageBps: decimal.NewFromUint64(swap.SlippageBps),
BaseReserve: decimal.NewFromUint64(swap.BaseReserve),
QuoteReserve: decimal.NewFromUint64(swap.QuoteReserve),
BaseReserve: txBinaryFloat64ToDecimalRaw(swap.BaseReserve),
QuoteReserve: txBinaryFloat64ToDecimalRaw(swap.QuoteReserve),
Mayhem: swap.Mayhem,
Cashback: swap.Cashback,
UserBaseBalance: decimal.NewFromUint64(swap.UserBaseBalance),
@@ -918,6 +918,17 @@ func txBinaryPlatformsFromTx(platforms map[string]platformInfo) ([]PlatformBinar
return out, nil
}
func txBinaryCanonicalEvent(event string) string {
switch event {
case "add_liquidity_on_side":
return TxEventAddLiquidityOneSide
case "remove_liquidity_on_side":
return TxEventRemoveLiquidityOneSide
default:
return event
}
}
func txBinaryMevAgentsFromTx(mevAgents map[string]mevInfo) ([]MevAgentBinary, error) {
if len(mevAgents) == 0 {
return nil, nil
@@ -1063,6 +1074,14 @@ func txBinaryDecimalToFloat64(value decimal.Decimal, scale int32, field string)
return f, nil
}
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
}
func txBinaryFloat64ToDecimal(value float64, scale int32) decimal.Decimal {
formatted := strconv.FormatFloat(value, 'f', int(scale), 64)
out, err := decimal.NewFromString(formatted)
@@ -1072,6 +1091,15 @@ func txBinaryFloat64ToDecimal(value float64, scale int32) decimal.Decimal {
return out
}
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
}
type txBinaryEncoder struct {
buf bytes.Buffer
}
@@ -1224,8 +1252,8 @@ func (enc *txBinaryEncoder) writeSwaps(swaps []SwapBinary, enumTable *txBinaryEn
enc.writeUint64(swap.ActualLimitAmount)
enc.writeUint8(uint8(swap.ActualLimitAmountSide))
enc.writeUint64(swap.SlippageBps)
enc.writeUint64(swap.BaseReserve)
enc.writeUint64(swap.QuoteReserve)
enc.writeFloat64(swap.BaseReserve)
enc.writeFloat64(swap.QuoteReserve)
enc.writeBool(swap.Mayhem)
enc.writeBool(swap.Cashback)
enc.writeUint64(swap.UserBaseBalance)
@@ -1720,10 +1748,10 @@ func txBinaryReadSwaps(dec txBinaryBodyReader, enumTable *txBinaryEnumTable) ([]
if swap.SlippageBps, err = dec.readUint64(); err != nil {
return nil, err
}
if swap.BaseReserve, err = dec.readUint64(); err != nil {
if swap.BaseReserve, err = dec.readFloat64(); err != nil {
return nil, err
}
if swap.QuoteReserve, err = dec.readUint64(); err != nil {
if swap.QuoteReserve, err = dec.readFloat64(); err != nil {
return nil, err
}
if swap.Mayhem, err = dec.readBool(); err != nil {
@@ -2055,6 +2083,18 @@ var txBinaryEnumTables = map[uint16]*txBinaryEnumTable{
TxEventBuyFailed,
TxEventSellFailed,
TxEventBurn,
TxEventCreate,
TxEventComplete,
TxEventMigrate,
TxEventDeposit,
TxEventWithdraw,
TxEventOpen,
TxEventClose,
TxEventClaimFee,
TxEventAddLiquidity,
TxEventAddLiquidityOneSide,
TxEventRemoveLiquidity,
TxEventRemoveLiquidityOneSide,
},
"platform",
[]string{

View File

@@ -225,6 +225,191 @@ func TestTxBinaryRejectsUnknownProgramEnum(t *testing.T) {
}
}
func TestTxBinaryAcceptsKnownEventEnums(t *testing.T) {
events := []string{
TxEventAddLP,
TxEventRemoveLP,
TxEventBuy,
TxEventSell,
TxEventBuyFailed,
TxEventSellFailed,
TxEventBurn,
TxEventCreate,
TxEventComplete,
TxEventMigrate,
TxEventDeposit,
TxEventWithdraw,
TxEventOpen,
TxEventClose,
TxEventClaimFee,
TxEventAddLiquidity,
TxEventAddLiquidityOneSide,
TxEventRemoveLiquidity,
TxEventRemoveLiquidityOneSide,
}
for _, event := range events {
t.Run(event, func(t *testing.T) {
txBinary := &TxBinary{
SchemaVersion: txBinarySchemaVersionCurrent,
EnumVersion: txBinaryEnumVersionV1,
AddressTable: []solana.PublicKey{
mustPubKey("11111111111111111111111111111111"),
mustPubKey("So11111111111111111111111111111111111111112"),
solana.TokenProgramID,
mustPubKey("BPFLoader1111111111111111111111111111111111"),
mustPubKey("SysvarRent111111111111111111111111111111111"),
},
Swaps: []SwapBinary{
{
Program: SolProgramPump,
Event: event,
Pool: 0,
BaseMint: 1,
QuoteMint: 1,
BaseTokenProgram: 2,
QuoteTokenProgram: 2,
Creator: 3,
User: 4,
FixedMint: 1,
LimitMint: 1,
},
},
}
encoded, err := txBinary.MarshalBinary()
if err != nil {
t.Fatalf("MarshalBinary() error = %v", err)
}
var decoded TxBinary
if err := decoded.UnmarshalBinary(encoded); err != nil {
t.Fatalf("UnmarshalBinary() error = %v", err)
}
if got := decoded.Swaps[0].Event; got != event {
t.Fatalf("decoded event = %q, want %q", got, event)
}
})
}
}
func TestTxBinaryPreservesFractionalReserves(t *testing.T) {
tx := &Tx{
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
Block: 1,
BlockIndex: 1,
CuFee: decimal.NewFromInt(1),
CUPrice: decimal.RequireFromString("0.000001"),
BeforeSolBalance: decimal.RequireFromString("1.000000000"),
AfterSOLBalance: decimal.RequireFromString("0.900000000"),
ComputeUnitsConsumed: 1,
CuLimit: 1,
Swaps: []Swap{
{
Program: SolProgramMeteoraPools,
Event: TxEventAddLiquidity,
Pool: mustPubKey("11111111111111111111111111111111"),
BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
QuoteMint: solana.WrappedSol,
BaseTokenProgram: solana.TokenProgramID,
QuoteTokenProgram: solana.TokenProgramID,
Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"),
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: mustPubKey("SysvarRent111111111111111111111111111111111"),
BaseAmount: decimal.NewFromInt(10),
QuoteAmount: decimal.NewFromInt(20),
SwapMode: SwapModeExactIn,
FixedAmount: decimal.NewFromInt(20),
FixedAmountSide: SwapAmountSideQuote,
FixedMint: solana.WrappedSol,
LimitAmountType: SwapLimitTypeMinOut,
LimitAmount: decimal.NewFromInt(9),
LimitAmountSide: SwapAmountSideBase,
LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
ActualLimitAmount: decimal.NewFromInt(10),
ActualLimitAmountSide: SwapAmountSideBase,
BaseReserve: decimal.RequireFromString("123.4"),
QuoteReserve: decimal.RequireFromString("710079483.625409498"),
AfterSOLBalance: decimal.RequireFromString("0.800000000"),
},
},
}
encoded, err := EncodeTxBinary(tx)
if err != nil {
t.Fatalf("EncodeTxBinary() error = %v", err)
}
decoded, err := DecodeTxBinary(encoded)
if err != nil {
t.Fatalf("DecodeTxBinary() error = %v", err)
}
if got := decoded.Swaps[0].BaseReserve.String(); got != "123.4" {
t.Fatalf("BaseReserve = %s, want 123.4", got)
}
diff := decoded.Swaps[0].QuoteReserve.Sub(decimal.RequireFromString("710079483.625409498")).Abs()
if diff.GreaterThan(decimal.RequireFromString("0.0000001")) {
t.Fatalf("QuoteReserve diff = %s, want <= 0.0000001", diff)
}
}
func TestTxBinaryCanonicalizesOnSideEventAlias(t *testing.T) {
tx := &Tx{
Signer: mustPubKey("So11111111111111111111111111111111111111112"),
Block: 1,
BlockIndex: 1,
CuFee: decimal.NewFromInt(1),
CUPrice: decimal.RequireFromString("0.000001"),
BeforeSolBalance: decimal.RequireFromString("1.000000000"),
AfterSOLBalance: decimal.RequireFromString("0.900000000"),
ComputeUnitsConsumed: 1,
CuLimit: 1,
Swaps: []Swap{
{
Program: SolProgramOrcaWhirPool,
Event: "remove_liquidity_on_side",
Pool: mustPubKey("11111111111111111111111111111111"),
BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
QuoteMint: solana.WrappedSol,
BaseTokenProgram: solana.TokenProgramID,
QuoteTokenProgram: solana.TokenProgramID,
Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"),
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: mustPubKey("SysvarRent111111111111111111111111111111111"),
BaseAmount: decimal.NewFromInt(10),
QuoteAmount: decimal.Zero,
SwapMode: SwapModeExactIn,
FixedAmount: decimal.NewFromInt(10),
FixedAmountSide: SwapAmountSideBase,
FixedMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"),
LimitAmountType: SwapLimitTypeMinOut,
LimitAmount: decimal.Zero,
LimitAmountSide: SwapAmountSideQuote,
ActualLimitAmount: decimal.Zero,
ActualLimitAmountSide: SwapAmountSideQuote,
BaseReserve: decimal.RequireFromString("123.4"),
QuoteReserve: decimal.RequireFromString("456.7"),
AfterSOLBalance: decimal.RequireFromString("0.800000000"),
},
},
}
encoded, err := EncodeTxBinary(tx)
if err != nil {
t.Fatalf("EncodeTxBinary() error = %v", err)
}
decoded, err := DecodeTxBinary(encoded)
if err != nil {
t.Fatalf("DecodeTxBinary() error = %v", err)
}
if got := decoded.Swaps[0].Event; got != TxEventRemoveLiquidityOneSide {
t.Fatalf("Event = %q, want %q", got, TxEventRemoveLiquidityOneSide)
}
}
func TestTxsBinaryRoundTripWithSharedAddressTable(t *testing.T) {
tx1 := Tx{
Signer: mustPubKey("So11111111111111111111111111111111111111112"),