package pump_parser import ( "bytes" "encoding/binary" "errors" "fmt" "math/big" "github.com/gagliardetto/solana-go" "github.com/shopspring/decimal" ) var ( chainlinkSOLUSDFeedAccount = solana.MustPublicKeyFromBase58("CH31Xns5z3M1cTAbKW34jcxPPciazARpijcHj9rxtemt") chainlinkSubmitDiscriminator = calculateDiscriminator("global:submit") ) func chainLinkParser(tx *Tx, instruction Instruction, inners InnerInstructions, offset [2]uint) ([2]uint, error) { if !tx.rawTx.accountList[instruction.ProgramIDIndex].Equals(chainLinkProgram) { return increaseOffset(offset), fmt.Errorf("system program instruction not found, block: %d, tx: %s, outerIndex: %d, innerIndex: %d", tx.rawTx.Slot, tx.rawTx.TxHash(), offset[0], offset[1]) } if tx.rawTx.Meta.Err != nil { return increaseOffset(offset), nil } decode := instruction.Data if len(decode) < 4 { return increaseOffset(offset), nil } discriminator := binary.LittleEndian.Uint32(decode[0:4]) switch discriminator { case transferDiscriminator: return chainLinkSubmitParser(instruction, inners, offset, tx, decode[4:]) default: return increaseOffset(offset), nil } } func chainLinkSubmitParser(instruction Instruction, inners InnerInstructions, offset [2]uint, tx *Tx, decodeData []byte) ([2]uint, error) { if len(instruction.Accounts) < 6 { return increaseOffset(offset), InstructionIgnoredError } inner, err := getInnerInstructions(inners, offset[1]) if err != nil { return increaseOffset(offset), err } if len(inner) < 1 { return increaseOffset(offset), InstructionIgnoredError } storeInstruction := inner[0] if len(storeInstruction.Accounts) < 2 { return increaseOffset(offset), InstructionIgnoredError } if storeInstruction.Accounts[0] >= len(tx.rawTx.accountList) || tx.rawTx.accountList[storeInstruction.Accounts[0]] != chainlinkSOLUSDFeedAccount { return increaseOffset(offset), InstructionIgnoredError } if len(storeInstruction.Data) < 8 { return increaseOffset(offset), InstructionIgnoredError } if !bytes.Equal(storeInstruction.Data[0:8], chainlinkSubmitDiscriminator[:]) { return increaseOffset(offset), InstructionIgnoredError } data, err := parseChainLinkSubmitData(storeInstruction.Data) if err != nil { return increaseOffset(offset), InstructionIgnoredError } tx.ChainLink.Timestamp = int64(data.Timestamp) tx.ChainLink.Price = decimal.NewFromBigInt(data.Price(), -8) return increaseOffset(offset), nil } type SubmitData struct { Discriminator [8]byte Timestamp uint64 Answer [16]byte } func parseChainLinkSubmitData(data []byte) (*SubmitData, error) { if len(data) != 32 { return nil, errors.New("invalid submit data length") } var submitData SubmitData copy(submitData.Discriminator[:], data[:8]) submitData.Timestamp = binary.LittleEndian.Uint64(data[8:16]) copy(submitData.Answer[:], data[16:32]) return &submitData, nil } func (s *SubmitData) Price() *big.Int { return int128LEBytesToBigInt(s.Answer) } func int128LEBytesToBigInt(bytes [16]byte) *big.Int { // Create new big.Int bigInt := new(big.Int) // Reverse bytes for little-endian to big-endian conversion reversed := make([]byte, 16) for i := 0; i < 16; i++ { reversed[15-i] = bytes[i] } // Check if negative (first byte in little-endian is highest byte) isNegative := bytes[15]&0x80 != 0 if isNegative { // If negative, flip all bits for i := range reversed { reversed[i] = ^reversed[i] } // Convert to big.Int bigInt.SetBytes(reversed) // Add 1 and negate bigInt.Add(bigInt, big.NewInt(1)) bigInt.Neg(bigInt) } else { // If positive, convert directly bigInt.SetBytes(reversed) } return bigInt }