chain link parser
This commit is contained in:
120
chainlink.go
Normal file
120
chainlink.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
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
|
||||||
|
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 !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
|
||||||
|
}
|
||||||
2
meta.go
2
meta.go
@@ -236,4 +236,6 @@ var createAccountWithSeedDiscriminator = uint32(3)
|
|||||||
var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111")
|
var systemProgram = solana.MustPublicKeyFromBase58("11111111111111111111111111111111")
|
||||||
var momoProgram = solana.MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")
|
var momoProgram = solana.MustPublicKeyFromBase58("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr")
|
||||||
|
|
||||||
|
var chainLinkProgram = solana.MustPublicKeyFromBase58("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ")
|
||||||
|
|
||||||
var eventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}
|
var eventDiscriminator = [8]byte{228, 69, 165, 46, 81, 203, 154, 29}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ func WithMeteoraDlmm() ParserOption {
|
|||||||
var actionPrograms = map[solana.PublicKey]actionParser{
|
var actionPrograms = map[solana.PublicKey]actionParser{
|
||||||
systemProgram: systemParser,
|
systemProgram: systemParser,
|
||||||
budgGetProgram: budgetParser,
|
budgGetProgram: budgetParser,
|
||||||
|
chainLinkProgram: chainLinkParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseRawTx(rawTx *RawTx) (*Tx, error) {
|
func ParseRawTx(rawTx *RawTx) (*Tx, error) {
|
||||||
|
|||||||
5
tx.go
5
tx.go
@@ -74,6 +74,10 @@ type SolTransfer struct {
|
|||||||
To solana.PublicKey
|
To solana.PublicKey
|
||||||
Amount decimal.Decimal
|
Amount decimal.Decimal
|
||||||
}
|
}
|
||||||
|
type ChainLink struct {
|
||||||
|
Timestamp int64
|
||||||
|
Price decimal.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
type Tx struct {
|
type Tx struct {
|
||||||
rawTx *RawTx
|
rawTx *RawTx
|
||||||
@@ -83,6 +87,7 @@ type Tx struct {
|
|||||||
Swaps []Swap `json:"swaps,omitempty"`
|
Swaps []Swap `json:"swaps,omitempty"`
|
||||||
SolTransfer []SolTransfer `json:"sol_transfer,omitempty"`
|
SolTransfer []SolTransfer `json:"sol_transfer,omitempty"`
|
||||||
Block uint64 `json:"block"`
|
Block uint64 `json:"block"`
|
||||||
|
ChainLink ChainLink `json:"chain_link"`
|
||||||
BlockIndex uint64 `json:"index"`
|
BlockIndex uint64 `json:"index"`
|
||||||
TxHash *[64]byte `json:"-"`
|
TxHash *[64]byte `json:"-"`
|
||||||
BlockAt int64 `json:"block_at"`
|
BlockAt int64 `json:"block_at"`
|
||||||
|
|||||||
Reference in New Issue
Block a user