Compare commits

...

1 Commits

Author SHA1 Message Date
thloyi
ab0e87a48a fix raydium v4 swap v2 2026-04-16 11:39:15 +08:00
2 changed files with 140 additions and 1 deletions

View File

@@ -402,11 +402,14 @@ func raydiumv4SwapParser(tx *Tx, instruction Instruction, innerInstructions Inne
func raydiumv4SwapV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { func raydiumv4SwapV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) {
accountsLen := len(instruction.Accounts) accountsLen := len(instruction.Accounts)
if accountsLen != 8 { if accountsLen < 8 {
return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 swapv2 instruction, offset %d, %d", offset[0], offset[1]) return nil, increaseOffset(offset), fmt.Errorf("invalid number of accounts for raydiumv4 swapv2 instruction, offset %d, %d", offset[0], offset[1])
} }
var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex]
// Raydium's documented V2 layout uses the first 8 accounts. Routed CPI calls
// may append extra readonly accounts (for example the Raydium program id) at
// the tail, so we only require the canonical prefix here.
ammAccount := tx.rawTx.accountList[instruction.Accounts[1]] ammAccount := tx.rawTx.accountList[instruction.Accounts[1]]
user := tx.rawTx.accountList[instruction.Accounts[7]] user := tx.rawTx.accountList[instruction.Accounts[7]]
userSourceTokenAccount := tx.rawTx.accountList[instruction.Accounts[5]] userSourceTokenAccount := tx.rawTx.accountList[instruction.Accounts[5]]

136
raydiumv4_test.go Normal file
View File

@@ -0,0 +1,136 @@
package pump_parser
import (
"encoding/binary"
"testing"
"github.com/gagliardetto/solana-go"
"github.com/shopspring/decimal"
)
func transferInstructionData(amount uint64) solana.Base58 {
data := make([]byte, 9)
data[0] = 3
binary.LittleEndian.PutUint64(data[1:], amount)
return solana.Base58(data)
}
func TestRaydiumV4SwapV2ParserAllowsTrailingReadonlyAccounts(t *testing.T) {
t.Parallel()
accountList := make([]solana.PublicKey, 32)
for i := range accountList {
accountList[i] = testPublicKey(byte(i + 1))
}
accountList[0] = solana.TokenProgramID
accountList[8] = raydiumV4Program
accountList[20] = testPublicKey(200)
accountList[21] = testPublicKey(201)
accountList[22] = testPublicKey(202)
outerInstruction := Instruction{ProgramIDIndex: 20}
swapInstruction := Instruction{
Accounts: []int{0, 1, 2, 3, 4, 5, 6, 7, 8},
ProgramIDIndex: 8,
Data: solana.Base58([]byte{raydiumV4SwapBaseInV2Discriminator}),
}
innerInstructions := InnerInstructions{
Index: 0,
Instructions: []Instruction{
swapInstruction,
{
Accounts: []int{5, 4, 7},
ProgramIDIndex: 0,
Data: transferInstructionData(55),
},
{
Accounts: []int{3, 6, 2},
ProgramIDIndex: 0,
Data: transferInstructionData(42),
},
},
}
rawTx := &RawTx{
accountList: accountList,
Meta: Meta{
PostTokenBalances: []TokenBalance{
{
AccountIndex: 3,
MintAccount: accountList[21],
ProgramIDAccount: solana.TokenProgramID,
UITokenAmount: UITokenAmount{
Amount: "1000",
Decimals: 6,
},
},
{
AccountIndex: 4,
MintAccount: accountList[22],
ProgramIDAccount: solana.TokenProgramID,
UITokenAmount: UITokenAmount{
Amount: "2000",
Decimals: 9,
},
},
{
AccountIndex: 5,
MintAccount: accountList[22],
ProgramIDAccount: solana.TokenProgramID,
UITokenAmount: UITokenAmount{
Amount: "300",
Decimals: 9,
},
},
{
AccountIndex: 6,
MintAccount: accountList[21],
ProgramIDAccount: solana.TokenProgramID,
UITokenAmount: UITokenAmount{
Amount: "400",
Decimals: 6,
},
},
},
},
Transaction: Transaction{
Message: Message{
Instructions: []Instruction{outerInstruction},
},
},
}
tx := &Tx{rawTx: rawTx}
swaps, nextOffset, err := raydiumv4SwapV2Parser(tx, swapInstruction, innerInstructions, [2]uint{0, 1})
if err != nil {
t.Fatalf("raydiumv4SwapV2Parser() error = %v", err)
}
if len(swaps) != 1 {
t.Fatalf("raydiumv4SwapV2Parser() swaps len = %d, want 1", len(swaps))
}
if nextOffset != [2]uint{0, 4} {
t.Fatalf("raydiumv4SwapV2Parser() nextOffset = %v, want [0 4]", nextOffset)
}
swap := swaps[0]
if swap.Event != "buy" {
t.Fatalf("swap.Event = %q, want %q", swap.Event, "buy")
}
if !swap.Pool.Equals(accountList[1]) {
t.Fatalf("swap.Pool = %s, want %s", swap.Pool, accountList[1])
}
if !swap.User.Equals(accountList[7]) {
t.Fatalf("swap.User = %s, want %s", swap.User, accountList[7])
}
if !swap.EntryContract.Equals(accountList[20]) {
t.Fatalf("swap.EntryContract = %s, want %s", swap.EntryContract, accountList[20])
}
if !swap.BaseAmount.Equal(decimal.NewFromInt(42)) {
t.Fatalf("swap.BaseAmount = %s, want 42", swap.BaseAmount)
}
if !swap.QuoteAmount.Equal(decimal.NewFromInt(55)) {
t.Fatalf("swap.QuoteAmount = %s, want 55", swap.QuoteAmount)
}
}