package pump_parser import ( "encoding/binary" "fmt" "github.com/gagliardetto/solana-go" "github.com/shopspring/decimal" ) func decodeOrcaWhirlpoolSwapArgs(data []byte) (amount uint64, otherAmountThreshold uint64, amountSpecifiedIsInput bool, err error) { if len(data) < 42 { return 0, 0, false, fmt.Errorf("orca whirlpool swap instruction data too short") } amount = binary.LittleEndian.Uint64(data[8:16]) otherAmountThreshold = binary.LittleEndian.Uint64(data[16:24]) amountSpecifiedIsInput = data[40] != 0 return amount, otherAmountThreshold, amountSpecifiedIsInput, nil } func decodeOrcaWhirlpoolTwoHopSwapArgs(data []byte) (amount uint64, otherAmountThreshold uint64, amountSpecifiedIsInput bool, err error) { if len(data) < 27 { return 0, 0, false, fmt.Errorf("orca whirlpool two-hop swap instruction data too short") } amount = binary.LittleEndian.Uint64(data[8:16]) otherAmountThreshold = binary.LittleEndian.Uint64(data[16:24]) amountSpecifiedIsInput = data[24] != 0 return amount, otherAmountThreshold, amountSpecifiedIsInput, nil } func orcaWhirPoolParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(orcaProgramID) { return nil, increaseOffset(offset), fmt.Errorf("orcawhirpoolprogram instruction not found, offset, %d, %d", offset[0], offset[1]) } decode := instruction.Data discriminator := *(*[8]byte)(decode[:8]) switch discriminator { case orcaInitializePoolDiscriminator: return orcaWhirPoolInitialParser(tx, instruction, innerInstructions, offset) case orcaInitializePoolWithAdaptiveFeeDiscriminator: return orcaWhirPoolInitialWithAdaptiveFeeParser(tx, instruction, innerInstructions, offset) case orcaInitializePoolV2Discriminator: return orcaWhirPoolInitialV2Parser(tx, instruction, innerInstructions, offset) case orcaIncreaseLiquidityDiscriminator, orcaDecreaseLiquidityDiscriminator: return orcaWhirPoolLiquidityParser(tx, instruction, innerInstructions, offset) case orcaIncreaseLiquidityV2Discriminator, orcaDecreaseLiquidityV2Discriminator: return orcaWhirPoolLiquidityV2Parser(tx, instruction, innerInstructions, offset) case orcaCollectFeesDiscriminator: return orcaWhirPoolCollectFeeParser(tx, instruction, innerInstructions, offset) case orcaCollectFeesV2Discriminator: return orcaWhirPoolCollectFeeV2Parser(tx, instruction, innerInstructions, offset) case orcaCollectProtocolFeesV2Discriminator: return orcaWhirPoolCollectProtocolFeeV2Parser(tx, instruction, innerInstructions, offset) case orcaSwapDiscriminator: return orcaWhirPoolSwapParser(tx, instruction, innerInstructions, offset) case orcaSwapV2Discriminator: return orcaWhirPoolSwapV2Parser(tx, instruction, innerInstructions, offset) case orcaTwoHopSwapDiscriminator: return orcaWhirPoolTwoHopSwapParser(tx, instruction, innerInstructions, offset) case orcaTwoHopSwapV2Discriminator: return orcaWhirPoolTwoHopSwapV2Parser(tx, instruction, innerInstructions, offset) default: return nil, increaseOffset(offset), InstructionIgnoredError } } func orcaWhirPoolInitialParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 11 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize pool instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] // Get accounts from instruction pool := tx.rawTx.accountList[instruction.Accounts[4]] signer := tx.rawTx.accountList[0] vault0 := instruction.Accounts[5] vault1 := instruction.Accounts[6] baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err) } baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) offset[1] += 5 return []Swap{ { Program: SolProgramOrcaWhirPool, Event: "create", Pool: pool, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, Creator: signer, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseReserve: baseReserve, QuoteReserve: quoteReserve, User: tx.rawTx.accountList[instruction.Accounts[0]], EntryContract: entryContract, }, }, offset, nil } func orcaWhirPoolInitialWithAdaptiveFeeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 16 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize pool instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] pool := tx.rawTx.accountList[instruction.Accounts[7]] signer := tx.rawTx.accountList[0] vault0 := instruction.Accounts[9] vault1 := instruction.Accounts[10] baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err) } baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) offset[1] += 9 return []Swap{ { Program: SolProgramOrcaWhirPool, Event: "create", Pool: pool, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, Creator: signer, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseReserve: baseReserve, QuoteReserve: quoteReserve, User: tx.rawTx.accountList[instruction.Accounts[0]], EntryContract: entryContract, }, }, offset, nil } func orcaWhirPoolInitialV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 14 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize pool instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] pool := tx.rawTx.accountList[instruction.Accounts[6]] signer := tx.rawTx.accountList[0] vault0 := instruction.Accounts[7] vault1 := instruction.Accounts[8] baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err) } baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) offset[1] += 7 return []Swap{ { Program: SolProgramOrcaWhirPool, Event: "create", Pool: pool, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, Creator: signer, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseReserve: baseReserve, QuoteReserve: quoteReserve, User: tx.rawTx.accountList[instruction.Accounts[0]], EntryContract: entryContract, }, }, offset, nil } func orcaWhirPoolLiquidityParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 11 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] signer := tx.rawTx.accountList[0] pool := tx.rawTx.accountList[instruction.Accounts[0]] vault0 := instruction.Accounts[7] vault1 := instruction.Accounts[8] baseMint := tx.rawTx.accountList[instruction.Accounts[1]] quoteMint := tx.rawTx.accountList[instruction.Accounts[2]] baseTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault0) quoteTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault1) if baseTokenBalance == nil && quoteTokenBalance == nil { return nil, increaseOffset(offset), InstructionIgnoredError } var ( baseReserve decimal.Decimal quoteReserve decimal.Decimal baseMintDecimals uint8 quoteMintDecimals uint8 baseProgram solana.PublicKey quoteProgram solana.PublicKey ) if baseTokenBalance != nil { baseReserve, _ = decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) baseMintDecimals = uint8(baseTokenBalance.UITokenAmount.Decimals) baseProgram = baseTokenBalance.ProgramIDAccount } if quoteTokenBalance != nil { quoteReserve, _ = decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) quoteMintDecimals = uint8(quoteTokenBalance.UITokenAmount.Decimals) quoteProgram = quoteTokenBalance.ProgramIDAccount } discriminator := *(*[8]byte)(instruction.Data[:8]) var instructionName string if discriminator == orcaDecreaseLiquidityDiscriminator { instructionName = "remove_liquidity" } else if discriminator == orcaIncreaseLiquidityDiscriminator { instructionName = "add_liquidity" } var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } if len(inners) < 2 { return nil, increaseOffset(offset), fmt.Errorf("not enough inner instructions for protocol fee collection") } baseAmount := decimal.Zero quoteAmount := decimal.Zero var baseFound, quoteFound bool for i := 0; i < 2; i++ { from, to, amount, err := parseTokenTransfer(tx.rawTx, inners[i]) if err != nil { // maybe momo? continue //return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if !baseFound && (from.Equals(tx.rawTx.accountList[vault0]) || to.Equals(tx.rawTx.accountList[vault0])) { baseAmount = decimal.NewFromInt(int64(amount)) baseFound = true } else if !quoteFound && (from.Equals(tx.rawTx.accountList[vault1]) || to.Equals(tx.rawTx.accountList[vault1])) { quoteAmount = decimal.NewFromInt(int64(amount)) quoteFound = true } if baseFound && quoteFound { break } } if !baseFound && !quoteFound { return nil, increaseOffset(offset), fmt.Errorf("liquidity change failed to find token transfer for both vaults in inner instructions") } offset[1] += 2 if baseAmount.Equal(decimal.Zero) && quoteAmount.Equal(decimal.Zero) { return nil, increaseOffset(offset), InstructionIgnoredError } if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) { 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") } return []Swap{ { Program: SolProgramOrcaWhirPool, Event: instructionName, Pool: pool, BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseProgram, QuoteTokenProgram: quoteProgram, Creator: signer, BaseMintDecimals: baseMintDecimals, QuoteMintDecimals: quoteMintDecimals, BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, User: tx.rawTx.accountList[instruction.Accounts[0]], EntryContract: entryContract, }, }, offset, nil } func orcaWhirPoolLiquidityV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 15 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] signer := tx.rawTx.accountList[0] pool := tx.rawTx.accountList[instruction.Accounts[0]] baseMint := tx.rawTx.accountList[instruction.Accounts[1]] quoteMint := tx.rawTx.accountList[instruction.Accounts[2]] vault0 := instruction.Accounts[11] vault1 := instruction.Accounts[12] baseTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault0) quoteTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault1) if baseTokenBalance == nil && quoteTokenBalance == nil { return nil, increaseOffset(offset), InstructionIgnoredError } var ( baseReserve decimal.Decimal quoteReserve decimal.Decimal baseMintDecimals uint8 quoteMintDecimals uint8 baseProgram solana.PublicKey quoteProgram solana.PublicKey ) if baseTokenBalance != nil { baseReserve, _ = decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) baseMintDecimals = uint8(baseTokenBalance.UITokenAmount.Decimals) baseProgram = baseTokenBalance.ProgramIDAccount } if quoteTokenBalance != nil { quoteReserve, _ = decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) quoteMintDecimals = uint8(quoteTokenBalance.UITokenAmount.Decimals) quoteProgram = quoteTokenBalance.ProgramIDAccount } discriminator := *(*[8]byte)(instruction.Data[:8]) var instructionName string if discriminator == orcaDecreaseLiquidityV2Discriminator { instructionName = "remove_liquidity" } else if discriminator == orcaIncreaseLiquidityV2Discriminator { instructionName = "add_liquidity" } var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } if len(inners) < 2 { return nil, increaseOffset(offset), fmt.Errorf("not enough inner instructions for protocol fee collection") } baseAmount := decimal.Zero quoteAmount := decimal.Zero var baseFound, quoteFound bool for i := 0; i < 3; i++ { from, to, amount, err := parseTokenTransfer(tx.rawTx, inners[i]) if err != nil { // maybe momo? continue } if !baseFound && (from.Equals(tx.rawTx.accountList[vault0]) || to.Equals(tx.rawTx.accountList[vault0])) { baseAmount = decimal.NewFromInt(int64(amount)) baseFound = true } else if !quoteFound && (from.Equals(tx.rawTx.accountList[vault1]) || to.Equals(tx.rawTx.accountList[vault1])) { quoteAmount = decimal.NewFromInt(int64(amount)) quoteFound = true } if baseFound && quoteFound { break } } if !baseFound && !quoteFound { return nil, increaseOffset(offset), fmt.Errorf("liquidity change failed to find token transfer for both vaults in inner instructions") } offset[1] += 2 if baseAmount.Equal(decimal.Zero) && quoteAmount.Equal(decimal.Zero) { return nil, offset, InstructionIgnoredError } if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) { 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") } return []Swap{ { Program: SolProgramOrcaWhirPool, Event: instructionName, Pool: pool, BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseProgram, QuoteTokenProgram: quoteProgram, Creator: signer, BaseMintDecimals: baseMintDecimals, QuoteMintDecimals: quoteMintDecimals, BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, User: tx.rawTx.accountList[instruction.Accounts[0]], EntryContract: entryContract, }, }, offset, nil } func orcaWhirPoolCollectFeeParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 9 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize pool instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] signer := tx.rawTx.accountList[0] pool := tx.rawTx.accountList[instruction.Accounts[0]] vault0 := instruction.Accounts[5] vault1 := instruction.Accounts[7] baseTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault0) quoteTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault1) if baseTokenBalance == nil && quoteTokenBalance == nil { return nil, increaseOffset(offset), InstructionIgnoredError } var ( baseReserve decimal.Decimal quoteReserve decimal.Decimal baseMintDecimals uint8 quoteMintDecimals uint8 baseMint solana.PublicKey quoteMint solana.PublicKey baseProgram solana.PublicKey quoteProgram solana.PublicKey ) if baseTokenBalance != nil { baseReserve, _ = decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) baseMintDecimals = uint8(baseTokenBalance.UITokenAmount.Decimals) baseProgram = baseTokenBalance.ProgramIDAccount baseMint = baseTokenBalance.MintAccount } if quoteTokenBalance != nil { quoteReserve, _ = decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) quoteMintDecimals = uint8(quoteTokenBalance.UITokenAmount.Decimals) quoteProgram = quoteTokenBalance.ProgramIDAccount quoteMint = quoteTokenBalance.MintAccount } var instructionName = "remove_liquidity" var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } if len(inners) < 2 { return nil, increaseOffset(offset), fmt.Errorf("not enough inner instructions for protocol fee collection") } baseAmount := decimal.Zero quoteAmount := decimal.Zero var baseFound, quoteFound bool for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { continue //return nil, increaseOffset(offset), fmt.Errorf("orca whirpool parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if !baseFound && (from.Equals(tx.rawTx.accountList[vault0]) || to.Equals(tx.rawTx.accountList[vault0])) { baseAmount = decimal.NewFromInt(int64(amount)) baseFound = true } else if !quoteFound && (from.Equals(tx.rawTx.accountList[vault1]) || to.Equals(tx.rawTx.accountList[vault1])) { quoteAmount = decimal.NewFromInt(int64(amount)) quoteFound = true } if (baseFound && quoteFound) || i >= 6 { break } } if !baseFound && !quoteFound { return nil, increaseOffset(offset), fmt.Errorf("collect fee failed to find token transfer for both vaults in inner instructions") } offset[1] += 2 if baseAmount.Equal(decimal.Zero) && quoteAmount.Equal(decimal.Zero) { return nil, offset, InstructionIgnoredError } if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) { 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") } return []Swap{ { Program: SolProgramOrcaWhirPool, Event: instructionName, Pool: pool, BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseProgram, QuoteTokenProgram: quoteProgram, Creator: signer, BaseMintDecimals: baseMintDecimals, QuoteMintDecimals: quoteMintDecimals, BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, User: tx.rawTx.accountList[instruction.Accounts[0]], EntryContract: entryContract, }, }, offset, nil } func orcaWhirPoolCollectFeeV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 12 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize pool instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] signer := tx.rawTx.accountList[0] pool := tx.rawTx.accountList[instruction.Accounts[0]] baseMint := tx.rawTx.accountList[instruction.Accounts[4]] quoteMint := tx.rawTx.accountList[instruction.Accounts[5]] vault0 := instruction.Accounts[7] vault1 := instruction.Accounts[9] baseTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault0) quoteTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault1) if baseTokenBalance == nil && quoteTokenBalance == nil { return nil, increaseOffset(offset), InstructionIgnoredError } var ( baseReserve decimal.Decimal quoteReserve decimal.Decimal baseMintDecimals uint8 quoteMintDecimals uint8 baseProgram solana.PublicKey quoteProgram solana.PublicKey ) if baseTokenBalance != nil { baseReserve, _ = decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) baseMintDecimals = uint8(baseTokenBalance.UITokenAmount.Decimals) baseProgram = baseTokenBalance.ProgramIDAccount } if quoteTokenBalance != nil { quoteReserve, _ = decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) quoteMintDecimals = uint8(quoteTokenBalance.UITokenAmount.Decimals) quoteProgram = quoteTokenBalance.ProgramIDAccount } var instructionName = "remove_liquidity" var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } if len(inners) < 2 { return nil, increaseOffset(offset), fmt.Errorf("not enough inner instructions for protocol fee collection") } baseAmount := decimal.Zero quoteAmount := decimal.Zero var baseFound, quoteFound bool for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { continue //return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if !baseFound && (from.Equals(tx.rawTx.accountList[vault0]) || to.Equals(tx.rawTx.accountList[vault0])) { baseAmount = decimal.NewFromInt(int64(amount)) baseFound = true } else if !quoteFound && (from.Equals(tx.rawTx.accountList[vault1]) || to.Equals(tx.rawTx.accountList[vault1])) { quoteAmount = decimal.NewFromInt(int64(amount)) quoteFound = true } if (baseFound && quoteFound) || i >= 6 { break } } if !baseFound && !quoteFound { return nil, increaseOffset(offset), fmt.Errorf(" collect fee v2 failed to find token transfer for both vaults in inner instructions") } offset[1] += 2 if baseAmount.Equal(decimal.Zero) && quoteAmount.Equal(decimal.Zero) { return nil, offset, InstructionIgnoredError } if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) { 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") } return []Swap{ { Program: SolProgramOrcaWhirPool, Event: instructionName, Pool: pool, BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseProgram, QuoteTokenProgram: quoteProgram, Creator: signer, BaseMintDecimals: baseMintDecimals, QuoteMintDecimals: quoteMintDecimals, BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, User: tx.rawTx.accountList[instruction.Accounts[0]], EntryContract: entryContract, }, }, offset, nil } func orcaWhirPoolCollectProtocolFeeV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 12 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for initialize pool instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] signer := tx.rawTx.accountList[0] pool := tx.rawTx.accountList[instruction.Accounts[1]] baseMint := tx.rawTx.accountList[instruction.Accounts[3]] quoteMint := tx.rawTx.accountList[instruction.Accounts[4]] vault0 := instruction.Accounts[5] vault1 := instruction.Accounts[6] baseTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault0) quoteTokenBalance, _ := getTokenBalanceAfterTx(tx.rawTx, vault1) if baseTokenBalance == nil && quoteTokenBalance == nil { return nil, increaseOffset(offset), InstructionIgnoredError } var ( baseReserve decimal.Decimal quoteReserve decimal.Decimal baseMintDecimals uint8 quoteMintDecimals uint8 baseProgram solana.PublicKey quoteProgram solana.PublicKey ) if baseTokenBalance != nil { baseReserve, _ = decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) baseMintDecimals = uint8(baseTokenBalance.UITokenAmount.Decimals) baseProgram = baseTokenBalance.ProgramIDAccount } if quoteTokenBalance != nil { quoteReserve, _ = decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) quoteMintDecimals = uint8(quoteTokenBalance.UITokenAmount.Decimals) quoteProgram = quoteTokenBalance.ProgramIDAccount } var instructionName = "remove_liquidity" var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } if len(inners) < 2 { return nil, increaseOffset(offset), fmt.Errorf("not enough inner instructions for protocol fee collection") } baseAmount := decimal.Zero quoteAmount := decimal.Zero var baseFound, quoteFound bool for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { continue // return nil, increaseOffset(offset), fmt.Errorf("meta Bonding Curve initial parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if !baseFound && (from.Equals(tx.rawTx.accountList[vault0]) || to.Equals(tx.rawTx.accountList[vault0])) { baseAmount = decimal.NewFromInt(int64(amount)) baseFound = true } else if !quoteFound && (from.Equals(tx.rawTx.accountList[vault1]) || to.Equals(tx.rawTx.accountList[vault1])) { quoteAmount = decimal.NewFromInt(int64(amount)) quoteFound = true } if (baseFound && quoteFound) || i >= 6 { break } } if !baseFound && !quoteFound { return nil, increaseOffset(offset), fmt.Errorf("collect protocol fee failed to find token transfer for both vaults in inner instructions") } offset[1] += 2 if baseAmount.Equal(decimal.Zero) && quoteAmount.Equal(decimal.Zero) { return nil, offset, InstructionIgnoredError } if baseAmount.Equal(decimal.Zero) || quoteAmount.Equal(decimal.Zero) { 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") } return []Swap{ { Program: SolProgramOrcaWhirPool, Event: instructionName, Pool: pool, BaseMint: baseMint, QuoteMint: quoteMint, BaseTokenProgram: baseProgram, QuoteTokenProgram: quoteProgram, Creator: signer, BaseMintDecimals: baseMintDecimals, QuoteMintDecimals: quoteMintDecimals, BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, User: tx.rawTx.accountList[instruction.Accounts[0]], EntryContract: entryContract, }, }, offset, nil } func orcaWhirPoolSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 11 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for swap instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] amount, otherAmountThreshold, amountSpecifiedIsInput, err := decodeOrcaWhirlpoolSwapArgs(instruction.Data) if err != nil { return nil, increaseOffset(offset), err } swapMode := SwapModeExactOut if amountSpecifiedIsInput { swapMode = SwapModeExactIn } user := tx.rawTx.accountList[instruction.Accounts[1]] pool := tx.rawTx.accountList[instruction.Accounts[2]] token0Account := tx.rawTx.accountList[instruction.Accounts[3]] token1Account := tx.rawTx.accountList[instruction.Accounts[5]] user0 := instruction.Accounts[3] user1 := instruction.Accounts[5] vault0 := instruction.Accounts[4] vault1 := instruction.Accounts[6] token0Account = tx.rawTx.accountList[user0] token1Account = tx.rawTx.accountList[user1] vault0Account := tx.rawTx.accountList[vault0] vault1Account := tx.rawTx.accountList[vault1] baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err) } baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) userBase := getAccountBalanceAfterTx(tx.rawTx, user0) userQuote := getAccountBalanceAfterTx(tx.rawTx, user1) var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool swap get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } var baseAmount = decimal.Zero var quoteAmount = decimal.Zero var event string var baseFound, quoteFound bool for i, inner := range inners { from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool swap parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if !baseFound && (from.Equals(vault0Account) || to.Equals(vault0Account)) { baseAmount = decimal.NewFromInt(int64(amount)) if from.Equals(vault0Account) && to.Equals(token0Account) { event = "buy" } else if from.Equals(token0Account) && to.Equals(vault0Account) { event = "sell" } baseFound = true } else if !quoteFound && (from.Equals(vault1Account) || to.Equals(vault1Account)) { quoteAmount = decimal.NewFromInt(int64(amount)) if from.Equals(vault1Account) && to.Equals(token1Account) { event = "sell" } else if from.Equals(token1Account) && to.Equals(vault1Account) { event = "buy" } quoteFound = true } if i >= 1 || (baseFound && quoteFound) { break } } offset[1] += 2 if !baseFound || !quoteFound { return nil, offset, fmt.Errorf("orca whirpool swap failed to find both base and quote token transfer in inner instructions") } swap := Swap{ Program: SolProgramOrcaWhirPool, Event: event, Pool: pool, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, User: user, EntryContract: entryContract, } swap.SetSwapAmountInfo(swapMode, decimal.NewFromUint64(amount), decimal.NewFromUint64(otherAmountThreshold)) return []Swap{swap}, offset, nil } func orcaWhirPoolSwapV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 15 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for swap instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] amount, otherAmountThreshold, amountSpecifiedIsInput, err := decodeOrcaWhirlpoolSwapArgs(instruction.Data) if err != nil { return nil, increaseOffset(offset), err } swapMode := SwapModeExactOut if amountSpecifiedIsInput { swapMode = SwapModeExactIn } user := tx.rawTx.accountList[instruction.Accounts[3]] pool := tx.rawTx.accountList[instruction.Accounts[4]] user0 := instruction.Accounts[7] user1 := instruction.Accounts[9] vault0 := instruction.Accounts[8] vault1 := instruction.Accounts[10] token0Account := tx.rawTx.accountList[user0] token1Account := tx.rawTx.accountList[user1] vault0Account := tx.rawTx.accountList[vault0] vault1Account := tx.rawTx.accountList[vault1] baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault0) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, vault1) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get token1 vault balance after tx: %v", err) } baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) userBase := getAccountBalanceAfterTx(tx.rawTx, user0) userQuote := getAccountBalanceAfterTx(tx.rawTx, user1) var prefixLen = offset[1] inners, err := getInnerInstructions(innerInstructions, prefixLen) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool swap get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } var baseAmount = decimal.Zero var quoteAmount = decimal.Zero var event string var baseFound, quoteFound bool var skipOffset = 0 for i, inner := range inners { if !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.Token2022ProgramID) && !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.TokenProgramID) { continue } from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool swapv2 parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if !baseFound && (from.Equals(vault0Account) || to.Equals(vault0Account)) { baseAmount = decimal.NewFromInt(int64(amount)) if from.Equals(vault0Account) && to.Equals(token0Account) { event = "buy" } else if from.Equals(token0Account) && to.Equals(vault0Account) { event = "sell" } baseFound = true } else if !quoteFound && (from.Equals(vault1Account) || to.Equals(vault1Account)) { quoteAmount = decimal.NewFromInt(int64(amount)) if from.Equals(vault1Account) && to.Equals(token1Account) { event = "sell" } else if from.Equals(token1Account) && to.Equals(vault1Account) { event = "buy" } quoteFound = true } if baseFound && quoteFound { skipOffset = i break } } if !baseFound || !quoteFound { return nil, offset, fmt.Errorf("orca whirpool swapV2 failed to find both base and quote token transfer in inner instructions") } offset[1] += uint(skipOffset + 1) swap := Swap{ Program: SolProgramOrcaWhirPool, Event: event, Pool: pool, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, User: user, EntryContract: entryContract, } swap.SetSwapAmountInfo(swapMode, decimal.NewFromUint64(amount), decimal.NewFromUint64(otherAmountThreshold)) return []Swap{swap}, offset, nil } func orcaWhirPoolTwoHopSwapParser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 12 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for twoHopSwap instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] amountSpecified, otherAmountThreshold, amountSpecifiedIsInput, err := decodeOrcaWhirlpoolTwoHopSwapArgs(instruction.Data) if err != nil { return nil, increaseOffset(offset), err } swapMode := SwapModeExactOut if amountSpecifiedIsInput { swapMode = SwapModeExactIn } user := tx.rawTx.accountList[instruction.Accounts[1]] pool1 := tx.rawTx.accountList[instruction.Accounts[2]] pool2 := tx.rawTx.accountList[instruction.Accounts[3]] pool1UserBase := instruction.Accounts[4] pool1VaultBase := instruction.Accounts[5] pool1UserQuote := instruction.Accounts[6] pool1VaultQuote := instruction.Accounts[7] pool2UserBase := instruction.Accounts[8] pool2VaultBase := instruction.Accounts[9] pool2UserQuote := instruction.Accounts[10] pool2VaultQuote := instruction.Accounts[11] swaps := make([]Swap, 2) { baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, pool1VaultBase) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get pool1 token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, pool1VaultQuote) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get pool1 token1 vault balance after tx: %v", err) } userBase := getAccountBalanceAfterTx(tx.rawTx, pool1UserBase) userQuote := getAccountBalanceAfterTx(tx.rawTx, pool1UserQuote) baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) var baseAmount = decimal.Zero var quoteAmount = decimal.Zero var event string inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool two hop swap get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } var nextInstructionIndex = uint(0) var baseFound, quoteFound bool for i, inner := range inners { if !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.Token2022ProgramID) && !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.TokenProgramID) { continue } from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool two hop swap parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if from.Equals(tx.rawTx.accountList[pool1VaultBase]) || to.Equals(tx.rawTx.accountList[pool1VaultBase]) { baseAmount = decimal.NewFromInt(int64(amount)) if from.Equals(tx.rawTx.accountList[pool1VaultBase]) && to.Equals(tx.rawTx.accountList[pool1UserBase]) { event = "buy" } else if from.Equals(tx.rawTx.accountList[pool1UserBase]) && to.Equals(tx.rawTx.accountList[pool1VaultBase]) { event = "sell" } baseFound = true } else if from.Equals(tx.rawTx.accountList[pool1VaultQuote]) || to.Equals(tx.rawTx.accountList[pool1VaultQuote]) { quoteAmount = decimal.NewFromInt(int64(amount)) if from.Equals(tx.rawTx.accountList[pool1VaultQuote]) && to.Equals(tx.rawTx.accountList[pool1UserQuote]) { event = "sell" } else if from.Equals(tx.rawTx.accountList[pool1UserQuote]) && to.Equals(tx.rawTx.accountList[pool1VaultQuote]) { event = "buy" } quoteFound = true } nextInstructionIndex = uint(i + 1) if baseFound && quoteFound { break } } offset[1] += nextInstructionIndex swaps[0] = Swap{ Program: SolProgramOrcaWhirPool, Event: event, Pool: pool1, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, User: user, EntryContract: entryContract, } } { baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, pool2VaultBase) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get pool2 token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, pool2VaultQuote) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get pool2 token1 vault balance after tx: %v", err) } userBase := getAccountBalanceAfterTx(tx.rawTx, pool2UserBase) userQuote := getAccountBalanceAfterTx(tx.rawTx, pool2UserQuote) baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) var baseAmount = decimal.Zero var quoteAmount = decimal.Zero var event string inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool two hop swap get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } var baseFound, quoteFound bool var nextInstructionIndex = uint(0) for i, inner := range inners { if !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.Token2022ProgramID) && !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.TokenProgramID) { continue } from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool two hop swap parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if from.Equals(tx.rawTx.accountList[pool2VaultBase]) || to.Equals(tx.rawTx.accountList[pool2VaultBase]) { baseAmount = decimal.NewFromInt(int64(amount)) if from.Equals(tx.rawTx.accountList[pool2VaultBase]) && to.Equals(tx.rawTx.accountList[pool2UserBase]) { event = "buy" } else if from.Equals(tx.rawTx.accountList[pool2UserBase]) && to.Equals(tx.rawTx.accountList[pool2VaultBase]) { event = "sell" } baseFound = true } else if from.Equals(tx.rawTx.accountList[pool2VaultQuote]) || to.Equals(tx.rawTx.accountList[pool2VaultQuote]) { quoteAmount = decimal.NewFromInt(int64(amount)) if from.Equals(tx.rawTx.accountList[pool2VaultQuote]) && to.Equals(tx.rawTx.accountList[pool2UserQuote]) { event = "sell" } else if from.Equals(tx.rawTx.accountList[pool2UserQuote]) && to.Equals(tx.rawTx.accountList[pool2VaultQuote]) { event = "buy" } quoteFound = true } nextInstructionIndex = uint(i + 1) if baseFound && quoteFound { break } } offset[1] += nextInstructionIndex swaps[1] = Swap{ Program: SolProgramOrcaWhirPool, Event: event, Pool: pool2, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, User: user, EntryContract: entryContract, } } fixedSide := fixedSwapAmountSide(swaps[0].Event, swapMode) fixedMint := swapMintForSide(swaps[0].BaseMint, swaps[0].QuoteMint, fixedSide) limitSide := oppositeSwapAmountSide(fixedSide) limitMint := swapMintForSide(swaps[1].BaseMint, swaps[1].QuoteMint, limitSide) actualLimitAmount := swapAmountForSide(swaps[1].BaseAmount, swaps[1].QuoteAmount, limitSide) if swapMode == SwapModeExactOut { fixedSide = fixedSwapAmountSide(swaps[1].Event, swapMode) fixedMint = swapMintForSide(swaps[1].BaseMint, swaps[1].QuoteMint, fixedSide) limitSide = oppositeSwapAmountSide(fixedSwapAmountSide(swaps[0].Event, swapMode)) limitMint = swapMintForSide(swaps[0].BaseMint, swaps[0].QuoteMint, limitSide) actualLimitAmount = swapAmountForSide(swaps[0].BaseAmount, swaps[0].QuoteAmount, limitSide) } swaps[0].SetSwapAmountInfoDetailed( swapMode, decimal.NewFromUint64(amountSpecified), fixedSide, fixedMint, limitSwapAmountType(swapMode), decimal.NewFromUint64(otherAmountThreshold), limitSide, limitMint, actualLimitAmount, ) return swaps, offset, nil } func orcaWhirPoolTwoHopSwapV2Parser(tx *Tx, instruction Instruction, innerInstructions InnerInstructions, offset [2]uint) ([]Swap, [2]uint, error) { if len(instruction.Accounts) < 15 { return nil, increaseOffset(offset), fmt.Errorf("not enough accounts for twoHopSwapV2 instruction") } var entryContract = tx.rawTx.accountList[tx.rawTx.Transaction.Message.Instructions[offset[0]].ProgramIDIndex] amountSpecified, otherAmountThreshold, amountSpecifiedIsInput, err := decodeOrcaWhirlpoolTwoHopSwapArgs(instruction.Data) if err != nil { return nil, increaseOffset(offset), err } swapMode := SwapModeExactOut if amountSpecifiedIsInput { swapMode = SwapModeExactIn } user := tx.rawTx.accountList[instruction.Accounts[14]] pool1 := tx.rawTx.accountList[instruction.Accounts[0]] pool2 := tx.rawTx.accountList[instruction.Accounts[1]] pool1UserBase := instruction.Accounts[8] pool1VaultBase := instruction.Accounts[9] //pool1UserQuote := instruction.Accounts[6] pool1VaultQuote := instruction.Accounts[10] //pool2UserBase := instruction.Accounts[8] pool2VaultBase := instruction.Accounts[11] pool2VaultQuote := instruction.Accounts[12] pool2UserQuote := instruction.Accounts[13] swaps := make([]Swap, 2) { baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, pool1VaultBase) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get pool1 token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, pool1VaultQuote) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get pool1 token1 vault balance after tx: %v", err) } userBase := getAccountBalanceAfterTx(tx.rawTx, pool1UserBase) userQuote := decimal.Zero baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) var baseAmount = decimal.Zero var quoteAmount = decimal.Zero var event string inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool two hop swap get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } var baseFound, quoteFound bool for _, inner := range inners { if !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.Token2022ProgramID) && !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.TokenProgramID) { continue } from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool two hop swap parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if from.Equals(tx.rawTx.accountList[pool1VaultBase]) || to.Equals(tx.rawTx.accountList[pool1VaultBase]) { baseAmount = decimal.NewFromInt(int64(amount)) if from.Equals(tx.rawTx.accountList[pool1VaultBase]) && to.Equals(tx.rawTx.accountList[pool1UserBase]) { event = "buy" } else if from.Equals(tx.rawTx.accountList[pool1UserBase]) && to.Equals(tx.rawTx.accountList[pool1VaultBase]) { event = "sell" } baseFound = true } else if from.Equals(tx.rawTx.accountList[pool1VaultQuote]) || to.Equals(tx.rawTx.accountList[pool1VaultQuote]) { quoteAmount = decimal.NewFromInt(int64(amount)) if from.Equals(tx.rawTx.accountList[pool1VaultQuote]) && to.Equals(tx.rawTx.accountList[pool2VaultBase]) { event = "sell" } else if from.Equals(tx.rawTx.accountList[pool2VaultBase]) && to.Equals(tx.rawTx.accountList[pool1VaultQuote]) { event = "buy" } quoteFound = true } if baseFound && quoteFound { break } } swaps[0] = Swap{ Program: SolProgramOrcaWhirPool, Event: event, Pool: pool1, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, User: user, EntryContract: entryContract, } } { baseTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, pool2VaultBase) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get pool2 token0 vault balance after tx: %v", err) } quoteTokenBalance, err := getTokenBalanceAfterTx(tx.rawTx, pool2VaultQuote) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("failed to get pool2 token1 vault balance after tx: %v", err) } userBase := decimal.Zero userQuote := getAccountBalanceAfterTx(tx.rawTx, pool2UserQuote) baseReserve, _ := decimal.NewFromString(baseTokenBalance.UITokenAmount.Amount) quoteReserve, _ := decimal.NewFromString(quoteTokenBalance.UITokenAmount.Amount) var baseAmount = decimal.Zero var quoteAmount = decimal.Zero var event string inners, err := getInnerInstructions(innerInstructions, offset[1]) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool two hop swap get inner instructions error: %v, offset, %d, %d", err, offset[0], offset[1]) } var baseFound, quoteFound bool var nextInstructionIndex = uint(0) for i, inner := range inners { if !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.Token2022ProgramID) && !tx.rawTx.accountList[inner.ProgramIDIndex].Equals(solana.TokenProgramID) { continue } from, to, amount, err := parseTokenTransfer(tx.rawTx, inner) if err != nil { return nil, increaseOffset(offset), fmt.Errorf("orca whirpool two hop swap parse token transfer error: %v, offset, %d, %d", err, offset[0], offset[1]) } if from.Equals(tx.rawTx.accountList[pool2VaultBase]) || to.Equals(tx.rawTx.accountList[pool2VaultBase]) { baseAmount = decimal.NewFromInt(int64(amount)) if from.Equals(tx.rawTx.accountList[pool2VaultBase]) && to.Equals(tx.rawTx.accountList[pool1VaultBase]) { event = "buy" } else if from.Equals(tx.rawTx.accountList[pool1VaultBase]) && to.Equals(tx.rawTx.accountList[pool2VaultBase]) { event = "sell" } baseFound = true } else if from.Equals(tx.rawTx.accountList[pool2VaultQuote]) || to.Equals(tx.rawTx.accountList[pool2VaultQuote]) { quoteAmount = decimal.NewFromInt(int64(amount)) if from.Equals(tx.rawTx.accountList[pool2VaultQuote]) && to.Equals(tx.rawTx.accountList[pool2UserQuote]) { event = "sell" } else if from.Equals(tx.rawTx.accountList[pool2UserQuote]) && to.Equals(tx.rawTx.accountList[pool2VaultQuote]) { event = "buy" } quoteFound = true } nextInstructionIndex = uint(i + 1) if baseFound && quoteFound { break } } offset[1] += nextInstructionIndex swaps[1] = Swap{ Program: SolProgramOrcaWhirPool, Event: event, Pool: pool2, BaseMint: baseTokenBalance.MintAccount, QuoteMint: quoteTokenBalance.MintAccount, BaseTokenProgram: baseTokenBalance.ProgramIDAccount, QuoteTokenProgram: quoteTokenBalance.ProgramIDAccount, BaseMintDecimals: uint8(baseTokenBalance.UITokenAmount.Decimals), QuoteMintDecimals: uint8(quoteTokenBalance.UITokenAmount.Decimals), BaseAmount: baseAmount, QuoteAmount: quoteAmount, BaseReserve: baseReserve, QuoteReserve: quoteReserve, UserBaseBalance: userBase, UserQuoteBalance: userQuote, User: user, EntryContract: entryContract, } } fixedSide := fixedSwapAmountSide(swaps[0].Event, swapMode) fixedMint := swapMintForSide(swaps[0].BaseMint, swaps[0].QuoteMint, fixedSide) limitSide := oppositeSwapAmountSide(fixedSide) limitMint := swapMintForSide(swaps[1].BaseMint, swaps[1].QuoteMint, limitSide) actualLimitAmount := swapAmountForSide(swaps[1].BaseAmount, swaps[1].QuoteAmount, limitSide) if swapMode == SwapModeExactOut { fixedSide = fixedSwapAmountSide(swaps[1].Event, swapMode) fixedMint = swapMintForSide(swaps[1].BaseMint, swaps[1].QuoteMint, fixedSide) limitSide = oppositeSwapAmountSide(fixedSwapAmountSide(swaps[0].Event, swapMode)) limitMint = swapMintForSide(swaps[0].BaseMint, swaps[0].QuoteMint, limitSide) actualLimitAmount = swapAmountForSide(swaps[0].BaseAmount, swaps[0].QuoteAmount, limitSide) } swaps[0].SetSwapAmountInfoDetailed( swapMode, decimal.NewFromUint64(amountSpecified), fixedSide, fixedMint, limitSwapAmountType(swapMode), decimal.NewFromUint64(otherAmountThreshold), limitSide, limitMint, actualLimitAmount, ) return swaps, offset, nil }