Compare commits

..

4 Commits

Author SHA1 Message Date
thloyi
78d323efd5 add pump amm buy_exact_quote_in 2025-12-23 14:37:12 +08:00
thloyi
d22347ce8d fix okx user 2025-12-22 18:00:35 +08:00
thloyi
9898554bf8 fix okx user 2025-12-22 17:56:40 +08:00
thloyi
b44c7372d5 mv example to internal 2025-12-15 15:14:14 +08:00
21 changed files with 642 additions and 5712 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/.idea

View File

@@ -4,6 +4,13 @@ import "github.com/gagliardetto/solana-go"
var platformFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("BB5dnY55FXS1e1NXqZDwCzgdYJdMCj3B92PU6Q5Fb6DT"): PlatformGMGN,
solana.MustPublicKeyFromBase58("7sHXjs1j7sDJGVSMSPjD1b4v3FD6uRSvRWfhRdfv5BiA"): PlatformGMGN,
solana.MustPublicKeyFromBase58("ByRRgnZenY6W2sddo1VJzX9o4sMU4gPDUkcmgrpGBxRy"): PlatformGMGN,
solana.MustPublicKeyFromBase58("DXfkEGoo6WFsdL7x6gLZ7r6Hw2S6HrtrAQVPWYx2A1s9"): PlatformGMGN,
solana.MustPublicKeyFromBase58("3t9EKmRiAUcQUYzTZpNojzeGP1KBAVEEbDNmy6wECQpK"): PlatformGMGN,
solana.MustPublicKeyFromBase58("DymeoWc5WLNiQBaoLuxrxDnDRvLgGZ1QGsEoCAM7Jsrx"): PlatformGMGN,
solana.MustPublicKeyFromBase58("dBhdrmwBkRa66XxBuAK4WZeZnsZ6bHeHCCLXa3a8bTJ"): PlatformGMGN,
solana.MustPublicKeyFromBase58("6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn"): PlatformGMGN,
solana.MustPublicKeyFromBase58("AVUCZyuT35YSuj4RH7fwiyPu82Djn2Hfg7y2ND2XcnZH"): PlatformPhoton,
solana.MustPublicKeyFromBase58("7LCZckF6XXGQ1hDY6HFXBKWAtiUgL9QY5vj1C4Bn1Qjj"): PlatformAxiom,
solana.MustPublicKeyFromBase58("4V65jvcDG9DSQioUVqVPiUcUY9v6sb6HKtMnsxSKEz5S"): PlatformAxiom,
@@ -40,6 +47,7 @@ var platformFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("5wkyL2FLEcyUUgc3UeGntHTAfWfzDrVuxMnaMm7792Gk"): PlatformMoonshotMoney,
solana.MustPublicKeyFromBase58("MaestroUL88UBnZr3wfoN7hqmNWFi3ZYCGqZoJJHE36"): PlatformMaestro,
solana.MustPublicKeyFromBase58("ZG98FUCjb8mJ824Gbs6RsgVmr1FhXb2oNiJHa2dwmPd"): PlatformBonkBot,
solana.MustPublicKeyFromBase58("J5XGHmzrRmnYWbmw45DbYkdZAU2bwERFZ11qCDXPvFB5"): PlatformPadre,
}
var mevAgentFeeAddresses = map[solana.PublicKey]string{
@@ -72,6 +80,15 @@ var mevAgentFeeAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("ENxTEjSQ1YabmUpXAdCgevnHQ9MHdLv8tzFiuiYJqa13"): MevAgent0slot,
solana.MustPublicKeyFromBase58("6rYLG55Q9RpsPGvqdPNJs4z5WTxJVatMB8zV3WJhs5EK"): MevAgent0slot,
solana.MustPublicKeyFromBase58("Cix2bHfqPcKcM233mzxbLk14kSggUUiz2A87fJtGivXr"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axm2JQY1FKEktAwgXWqjGYkkWsWPfwKzgbnGVt5kiP4"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axm3PjbgwVrF6rnY2xLRMmWmLdDQGKfUYTEDtZ1haz7"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axmD4LFJopAcbRKCKsrrmovCZZzmKQCMEfs5qEXj8dG"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axmFmfqQwZGEUZeF3i3MqbRCDiGPfshtbdoBjk41k88"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axmMdWvgEnN3NFrxMfTqUURzj9NLhZL2DkHkWCdgiFV"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axmQTWU68qZ4fuG7zzkCXCBmxxeHVZrNrLkgxEFCbRv"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axmWxBPqgRmcBN2cV12quqaQzsk16SazVXq8397KFKu"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axmYVq9b1ABYqtyizMtyfJppPTPxZGXPLctB3hV6W5b"): MevAgent0slot,
solana.MustPublicKeyFromBase58("axmhpocX3hU7nT7KtsLBzNBR1Ur3HtU22Q5P313FREY"): MevAgent0slot,
solana.MustPublicKeyFromBase58("HWEoBxYs7ssKuudEjzjmpfJVX7Dvi7wescFsVx2L5yoY"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("7ks326H4LbMVaUC8nW5FpC5EoAf5eK5pf4Dsx4HDQLpq"): MevAgentBlocxRoute,
solana.MustPublicKeyFromBase58("95cfoy472fcQHaw4tPGBTKpn6ZQnfEPfBgDQx6gcRmRg"): MevAgentBlocxRoute,
@@ -177,4 +194,8 @@ var entryContractAddresses = map[solana.PublicKey]string{
solana.MustPublicKeyFromBase58("NoVA1TmDUqksaj2hB1nayFkPysjJbFiU76dT4qPw2wm"): EntryContractNovaBotsProgram,
solana.MustPublicKeyFromBase58("E6YoRP3adE5XYneSseLee15wJshDxCsmyD2WtLvAmfLi"): EntryContractTaggedSearcher,
solana.MustPublicKeyFromBase58("MAyhSmzXzV1pTf7LsNkrNwkWKTo4ougAJ1PPg47MD4e"): EntryContractMayhem,
solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u"): EntryContractOKXDexRouterV2,
}
var okxDexRoutersV2 = solana.MustPublicKeyFromBase58("proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u")
var okxAggregatorV2 = solana.MustPublicKeyFromBase58("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma")

View File

@@ -38,6 +38,7 @@ const (
EntryContractNumeraire = "numeraire"
EntryContractBloomRouter = "bloomRouter"
EntryContractOKXAggregatorV2 = "oKXAggregatorV2"
EntryContractOKXDexRouterV2 = "oKXDExRouterV2"
EntryContractFluxbeamDEX = "fluxbeamDEX"
EntryContractNovaBotsProgram = "novaBotsProgram"
EntryContractTaggedSearcher = "taggedSearcher"
@@ -61,6 +62,7 @@ const (
PlatformMoonshotMoney = "moonshot.money"
PlatformMaestro = "maestro"
PlatformBonkBot = "bonkbot"
PlatformPadre = "padre"
// used to flag transactions impersonating platform users
PlatformFake = "fake"

View File

@@ -1,4 +1,4 @@
package geyser
package pump_parser
import (
"encoding/binary"

View File

@@ -1,7 +0,0 @@
protoc:
protoc \
--go_out=./proto \
--go_opt=paths=source_relative \
--go-grpc_out=./proto \
--go-grpc_opt=paths=source_relative \
--proto_path ./proto/ ./proto/*.proto

File diff suppressed because it is too large Load Diff

View File

@@ -1,272 +0,0 @@
syntax = "proto3";
import "google/protobuf/timestamp.proto";
import public "solana-storage.proto";
option go_package = "github.com/rpcpool/yellowstone-grpc/examples/golang/proto";
package geyser;
service Geyser {
rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeUpdate) {}
rpc Ping(PingRequest) returns (PongResponse) {}
rpc GetLatestBlockhash(GetLatestBlockhashRequest) returns (GetLatestBlockhashResponse) {}
rpc GetBlockHeight(GetBlockHeightRequest) returns (GetBlockHeightResponse) {}
rpc GetSlot(GetSlotRequest) returns (GetSlotResponse) {}
rpc IsBlockhashValid(IsBlockhashValidRequest) returns (IsBlockhashValidResponse) {}
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
}
enum CommitmentLevel {
PROCESSED = 0;
CONFIRMED = 1;
FINALIZED = 2;
}
enum SlotStatus {
SLOT_PROCESSED = 0;
SLOT_CONFIRMED = 1;
SLOT_FINALIZED = 2;
SLOT_FIRST_SHRED_RECEIVED = 3;
SLOT_COMPLETED = 4;
SLOT_CREATED_BANK = 5;
SLOT_DEAD = 6;
}
message SubscribeRequest {
map<string, SubscribeRequestFilterAccounts> accounts = 1;
map<string, SubscribeRequestFilterSlots> slots = 2;
map<string, SubscribeRequestFilterTransactions> transactions = 3;
map<string, SubscribeRequestFilterTransactions> transactions_status = 10;
map<string, SubscribeRequestFilterBlocks> blocks = 4;
map<string, SubscribeRequestFilterBlocksMeta> blocks_meta = 5;
map<string, SubscribeRequestFilterEntry> entry = 8;
optional CommitmentLevel commitment = 6;
repeated SubscribeRequestAccountsDataSlice accounts_data_slice = 7;
optional SubscribeRequestPing ping = 9;
optional uint64 from_slot = 11;
}
message SubscribeRequestFilterAccounts {
repeated string account = 2;
repeated string owner = 3;
repeated SubscribeRequestFilterAccountsFilter filters = 4;
optional bool nonempty_txn_signature = 5;
}
message SubscribeRequestFilterAccountsFilter {
oneof filter {
SubscribeRequestFilterAccountsFilterMemcmp memcmp = 1;
uint64 datasize = 2;
bool token_account_state = 3;
SubscribeRequestFilterAccountsFilterLamports lamports = 4;
}
}
message SubscribeRequestFilterAccountsFilterMemcmp {
uint64 offset = 1;
oneof data {
bytes bytes = 2;
string base58 = 3;
string base64 = 4;
}
}
message SubscribeRequestFilterAccountsFilterLamports {
oneof cmp {
uint64 eq = 1;
uint64 ne = 2;
uint64 lt = 3;
uint64 gt = 4;
}
}
message SubscribeRequestFilterSlots {
optional bool filter_by_commitment = 1;
optional bool interslot_updates = 2;
}
message SubscribeRequestFilterTransactions {
optional bool vote = 1;
optional bool failed = 2;
optional string signature = 5;
repeated string account_include = 3;
repeated string account_exclude = 4;
repeated string account_required = 6;
}
message SubscribeRequestFilterBlocks {
repeated string account_include = 1;
optional bool include_transactions = 2;
optional bool include_accounts = 3;
optional bool include_entries = 4;
}
message SubscribeRequestFilterBlocksMeta {}
message SubscribeRequestFilterEntry {}
message SubscribeRequestAccountsDataSlice {
uint64 offset = 1;
uint64 length = 2;
}
message SubscribeRequestPing {
int32 id = 1;
}
message SubscribeUpdate {
repeated string filters = 1;
oneof update_oneof {
SubscribeUpdateAccount account = 2;
SubscribeUpdateSlot slot = 3;
SubscribeUpdateTransaction transaction = 4;
SubscribeUpdateTransactionStatus transaction_status = 10;
SubscribeUpdateBlock block = 5;
SubscribeUpdatePing ping = 6;
SubscribeUpdatePong pong = 9;
SubscribeUpdateBlockMeta block_meta = 7;
SubscribeUpdateEntry entry = 8;
}
google.protobuf.Timestamp created_at = 11;
}
message SubscribeUpdateAccount {
SubscribeUpdateAccountInfo account = 1;
uint64 slot = 2;
bool is_startup = 3;
}
message SubscribeUpdateAccountInfo {
bytes pubkey = 1;
uint64 lamports = 2;
bytes owner = 3;
bool executable = 4;
uint64 rent_epoch = 5;
bytes data = 6;
uint64 write_version = 7;
optional bytes txn_signature = 8;
}
message SubscribeUpdateSlot {
uint64 slot = 1;
optional uint64 parent = 2;
SlotStatus status = 3;
optional string dead_error = 4;
}
message SubscribeUpdateTransaction {
SubscribeUpdateTransactionInfo transaction = 1;
uint64 slot = 2;
}
message SubscribeUpdateTransactionInfo {
bytes signature = 1;
bool is_vote = 2;
solana.storage.ConfirmedBlock.Transaction transaction = 3;
solana.storage.ConfirmedBlock.TransactionStatusMeta meta = 4;
uint64 index = 5;
}
message SubscribeUpdateTransactionStatus {
uint64 slot = 1;
bytes signature = 2;
bool is_vote = 3;
uint64 index = 4;
solana.storage.ConfirmedBlock.TransactionError err = 5;
}
message SubscribeUpdateBlock {
uint64 slot = 1;
string blockhash = 2;
solana.storage.ConfirmedBlock.Rewards rewards = 3;
solana.storage.ConfirmedBlock.UnixTimestamp block_time = 4;
solana.storage.ConfirmedBlock.BlockHeight block_height = 5;
uint64 parent_slot = 7;
string parent_blockhash = 8;
uint64 executed_transaction_count = 9;
repeated SubscribeUpdateTransactionInfo transactions = 6;
uint64 updated_account_count = 10;
repeated SubscribeUpdateAccountInfo accounts = 11;
uint64 entries_count = 12;
repeated SubscribeUpdateEntry entries = 13;
}
message SubscribeUpdateBlockMeta {
uint64 slot = 1;
string blockhash = 2;
solana.storage.ConfirmedBlock.Rewards rewards = 3;
solana.storage.ConfirmedBlock.UnixTimestamp block_time = 4;
solana.storage.ConfirmedBlock.BlockHeight block_height = 5;
uint64 parent_slot = 6;
string parent_blockhash = 7;
uint64 executed_transaction_count = 8;
uint64 entries_count = 9;
}
message SubscribeUpdateEntry {
uint64 slot = 1;
uint64 index = 2;
uint64 num_hashes = 3;
bytes hash = 4;
uint64 executed_transaction_count = 5;
uint64 starting_transaction_index = 6; // added in v1.18, for solana 1.17 value is always 0
}
message SubscribeUpdatePing {}
message SubscribeUpdatePong {
int32 id = 1;
}
// non-streaming methods
message PingRequest {
int32 count = 1;
}
message PongResponse {
int32 count = 1;
}
message GetLatestBlockhashRequest {
optional CommitmentLevel commitment = 1;
}
message GetLatestBlockhashResponse {
uint64 slot = 1;
string blockhash = 2;
uint64 last_valid_block_height = 3;
}
message GetBlockHeightRequest {
optional CommitmentLevel commitment = 1;
}
message GetBlockHeightResponse {
uint64 block_height = 1;
}
message GetSlotRequest {
optional CommitmentLevel commitment = 1;
}
message GetSlotResponse {
uint64 slot = 1;
}
message GetVersionRequest {}
message GetVersionResponse {
string version = 1;
}
message IsBlockhashValidRequest {
string blockhash = 1;
optional CommitmentLevel commitment = 2;
}
message IsBlockhashValidResponse {
uint64 slot = 1;
bool valid = 2;
}

View File

@@ -1,344 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// source: geyser.proto
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Geyser_Subscribe_FullMethodName = "/geyser.Geyser/Subscribe"
Geyser_Ping_FullMethodName = "/geyser.Geyser/Ping"
Geyser_GetLatestBlockhash_FullMethodName = "/geyser.Geyser/GetLatestBlockhash"
Geyser_GetBlockHeight_FullMethodName = "/geyser.Geyser/GetBlockHeight"
Geyser_GetSlot_FullMethodName = "/geyser.Geyser/GetSlot"
Geyser_IsBlockhashValid_FullMethodName = "/geyser.Geyser/IsBlockhashValid"
Geyser_GetVersion_FullMethodName = "/geyser.Geyser/GetVersion"
)
// GeyserClient is the client API for Geyser service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GeyserClient interface {
Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error)
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error)
GetLatestBlockhash(ctx context.Context, in *GetLatestBlockhashRequest, opts ...grpc.CallOption) (*GetLatestBlockhashResponse, error)
GetBlockHeight(ctx context.Context, in *GetBlockHeightRequest, opts ...grpc.CallOption) (*GetBlockHeightResponse, error)
GetSlot(ctx context.Context, in *GetSlotRequest, opts ...grpc.CallOption) (*GetSlotResponse, error)
IsBlockhashValid(ctx context.Context, in *IsBlockhashValidRequest, opts ...grpc.CallOption) (*IsBlockhashValidResponse, error)
GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error)
}
type geyserClient struct {
cc grpc.ClientConnInterface
}
func NewGeyserClient(cc grpc.ClientConnInterface) GeyserClient {
return &geyserClient{cc}
}
func (c *geyserClient) Subscribe(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Geyser_ServiceDesc.Streams[0], Geyser_Subscribe_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SubscribeRequest, SubscribeUpdate]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Geyser_SubscribeClient = grpc.BidiStreamingClient[SubscribeRequest, SubscribeUpdate]
func (c *geyserClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(PongResponse)
err := c.cc.Invoke(ctx, Geyser_Ping_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) GetLatestBlockhash(ctx context.Context, in *GetLatestBlockhashRequest, opts ...grpc.CallOption) (*GetLatestBlockhashResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetLatestBlockhashResponse)
err := c.cc.Invoke(ctx, Geyser_GetLatestBlockhash_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) GetBlockHeight(ctx context.Context, in *GetBlockHeightRequest, opts ...grpc.CallOption) (*GetBlockHeightResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetBlockHeightResponse)
err := c.cc.Invoke(ctx, Geyser_GetBlockHeight_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) GetSlot(ctx context.Context, in *GetSlotRequest, opts ...grpc.CallOption) (*GetSlotResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetSlotResponse)
err := c.cc.Invoke(ctx, Geyser_GetSlot_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) IsBlockhashValid(ctx context.Context, in *IsBlockhashValidRequest, opts ...grpc.CallOption) (*IsBlockhashValidResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IsBlockhashValidResponse)
err := c.cc.Invoke(ctx, Geyser_IsBlockhashValid_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *geyserClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetVersionResponse)
err := c.cc.Invoke(ctx, Geyser_GetVersion_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// GeyserServer is the server API for Geyser service.
// All implementations must embed UnimplementedGeyserServer
// for forward compatibility.
type GeyserServer interface {
Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error
Ping(context.Context, *PingRequest) (*PongResponse, error)
GetLatestBlockhash(context.Context, *GetLatestBlockhashRequest) (*GetLatestBlockhashResponse, error)
GetBlockHeight(context.Context, *GetBlockHeightRequest) (*GetBlockHeightResponse, error)
GetSlot(context.Context, *GetSlotRequest) (*GetSlotResponse, error)
IsBlockhashValid(context.Context, *IsBlockhashValidRequest) (*IsBlockhashValidResponse, error)
GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error)
mustEmbedUnimplementedGeyserServer()
}
// UnimplementedGeyserServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedGeyserServer struct{}
func (UnimplementedGeyserServer) Subscribe(grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]) error {
return status.Errorf(codes.Unimplemented, "method Subscribe not implemented")
}
func (UnimplementedGeyserServer) Ping(context.Context, *PingRequest) (*PongResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
}
func (UnimplementedGeyserServer) GetLatestBlockhash(context.Context, *GetLatestBlockhashRequest) (*GetLatestBlockhashResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLatestBlockhash not implemented")
}
func (UnimplementedGeyserServer) GetBlockHeight(context.Context, *GetBlockHeightRequest) (*GetBlockHeightResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBlockHeight not implemented")
}
func (UnimplementedGeyserServer) GetSlot(context.Context, *GetSlotRequest) (*GetSlotResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSlot not implemented")
}
func (UnimplementedGeyserServer) IsBlockhashValid(context.Context, *IsBlockhashValidRequest) (*IsBlockhashValidResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsBlockhashValid not implemented")
}
func (UnimplementedGeyserServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
}
func (UnimplementedGeyserServer) mustEmbedUnimplementedGeyserServer() {}
func (UnimplementedGeyserServer) testEmbeddedByValue() {}
// UnsafeGeyserServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GeyserServer will
// result in compilation errors.
type UnsafeGeyserServer interface {
mustEmbedUnimplementedGeyserServer()
}
func RegisterGeyserServer(s grpc.ServiceRegistrar, srv GeyserServer) {
// If the following call pancis, it indicates UnimplementedGeyserServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Geyser_ServiceDesc, srv)
}
func _Geyser_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GeyserServer).Subscribe(&grpc.GenericServerStream[SubscribeRequest, SubscribeUpdate]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Geyser_SubscribeServer = grpc.BidiStreamingServer[SubscribeRequest, SubscribeUpdate]
func _Geyser_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).Ping(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_Ping_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).Ping(ctx, req.(*PingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_GetLatestBlockhash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetLatestBlockhashRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).GetLatestBlockhash(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_GetLatestBlockhash_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).GetLatestBlockhash(ctx, req.(*GetLatestBlockhashRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_GetBlockHeight_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetBlockHeightRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).GetBlockHeight(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_GetBlockHeight_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).GetBlockHeight(ctx, req.(*GetBlockHeightRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_GetSlot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetSlotRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).GetSlot(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_GetSlot_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).GetSlot(ctx, req.(*GetSlotRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_IsBlockhashValid_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(IsBlockhashValidRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).IsBlockhashValid(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_IsBlockhashValid_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).IsBlockhashValid(ctx, req.(*IsBlockhashValidRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Geyser_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetVersionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GeyserServer).GetVersion(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Geyser_GetVersion_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GeyserServer).GetVersion(ctx, req.(*GetVersionRequest))
}
return interceptor(ctx, in, info, handler)
}
// Geyser_ServiceDesc is the grpc.ServiceDesc for Geyser service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Geyser_ServiceDesc = grpc.ServiceDesc{
ServiceName: "geyser.Geyser",
HandlerType: (*GeyserServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Ping",
Handler: _Geyser_Ping_Handler,
},
{
MethodName: "GetLatestBlockhash",
Handler: _Geyser_GetLatestBlockhash_Handler,
},
{
MethodName: "GetBlockHeight",
Handler: _Geyser_GetBlockHeight_Handler,
},
{
MethodName: "GetSlot",
Handler: _Geyser_GetSlot_Handler,
},
{
MethodName: "IsBlockhashValid",
Handler: _Geyser_IsBlockhashValid_Handler,
},
{
MethodName: "GetVersion",
Handler: _Geyser_GetVersion_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Subscribe",
Handler: _Geyser_Subscribe_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "geyser.proto",
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,149 +0,0 @@
syntax = "proto3";
package solana.storage.ConfirmedBlock;
option go_package = "github.com/rpcpool/yellowstone-grpc/examples/golang/proto";
message ConfirmedBlock {
string previous_blockhash = 1;
string blockhash = 2;
uint64 parent_slot = 3;
repeated ConfirmedTransaction transactions = 4;
repeated Reward rewards = 5;
UnixTimestamp block_time = 6;
BlockHeight block_height = 7;
NumPartitions num_partitions = 8;
}
message ConfirmedTransaction {
Transaction transaction = 1;
TransactionStatusMeta meta = 2;
}
message Transaction {
repeated bytes signatures = 1;
Message message = 2;
}
message Message {
MessageHeader header = 1;
repeated bytes account_keys = 2;
bytes recent_blockhash = 3;
repeated CompiledInstruction instructions = 4;
bool versioned = 5;
repeated MessageAddressTableLookup address_table_lookups = 6;
}
message MessageHeader {
uint32 num_required_signatures = 1;
uint32 num_readonly_signed_accounts = 2;
uint32 num_readonly_unsigned_accounts = 3;
}
message MessageAddressTableLookup {
bytes account_key = 1;
bytes writable_indexes = 2;
bytes readonly_indexes = 3;
}
message TransactionStatusMeta {
TransactionError err = 1;
uint64 fee = 2;
repeated uint64 pre_balances = 3;
repeated uint64 post_balances = 4;
repeated InnerInstructions inner_instructions = 5;
bool inner_instructions_none = 10;
repeated string log_messages = 6;
bool log_messages_none = 11;
repeated TokenBalance pre_token_balances = 7;
repeated TokenBalance post_token_balances = 8;
repeated Reward rewards = 9;
repeated bytes loaded_writable_addresses = 12;
repeated bytes loaded_readonly_addresses = 13;
ReturnData return_data = 14;
bool return_data_none = 15;
// Sum of compute units consumed by all instructions.
// Available since Solana v1.10.35 / v1.11.6.
// Set to `None` for txs executed on earlier versions.
optional uint64 compute_units_consumed = 16;
}
message TransactionError {
bytes err = 1;
}
message InnerInstructions {
uint32 index = 1;
repeated InnerInstruction instructions = 2;
}
message InnerInstruction {
uint32 program_id_index = 1;
bytes accounts = 2;
bytes data = 3;
// Invocation stack height of an inner instruction.
// Available since Solana v1.14.6
// Set to `None` for txs executed on earlier versions.
optional uint32 stack_height = 4;
}
message CompiledInstruction {
uint32 program_id_index = 1;
bytes accounts = 2;
bytes data = 3;
}
message TokenBalance {
uint32 account_index = 1;
string mint = 2;
UiTokenAmount ui_token_amount = 3;
string owner = 4;
string program_id = 5;
}
message UiTokenAmount {
double ui_amount = 1;
uint32 decimals = 2;
string amount = 3;
string ui_amount_string = 4;
}
message ReturnData {
bytes program_id = 1;
bytes data = 2;
}
enum RewardType {
Unspecified = 0;
Fee = 1;
Rent = 2;
Staking = 3;
Voting = 4;
}
message Reward {
string pubkey = 1;
int64 lamports = 2;
uint64 post_balance = 3;
RewardType reward_type = 4;
string commission = 5;
}
message Rewards {
repeated Reward rewards = 1;
NumPartitions num_partitions = 2;
}
message UnixTimestamp {
int64 timestamp = 1;
}
message BlockHeight {
uint64 block_height = 1;
}
message NumPartitions {
uint64 num_partitions = 1;
}

3
go.mod
View File

@@ -8,8 +8,8 @@ require (
github.com/jackc/pgtype v1.14.4
github.com/mr-tron/base58 v1.2.0
github.com/shopspring/decimal v1.4.0
go.onsig.ai/onsig/yellowstone-proto v1.0.0
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
)
require (
@@ -43,4 +43,5 @@ require (
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
google.golang.org/protobuf v1.36.10 // indirect
)

2
go.sum
View File

@@ -185,6 +185,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.mongodb.org/mongo-driver v1.12.2 h1:gbWY1bJkkmUB9jjZzcdhOL8O85N9H+Vvsf2yFN0RDws=
go.mongodb.org/mongo-driver v1.12.2/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ=
go.onsig.ai/onsig/yellowstone-proto v1.0.0 h1:+XBNIoyl3HoQGBhgWCf8Ma3zNoUHKorFV8tR+HnE4Lw=
go.onsig.ai/onsig/yellowstone-proto v1.0.0/go.mod h1:e5dlYkNpgNHtiXFwPmPDZRf4PrCsgNaSoA8iG4rfiKA=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=

View File

@@ -5,10 +5,8 @@ import (
"fmt"
"time"
"github.com/shopspring/decimal"
parser "github.com/thloyi/pump-parser"
example "github.com/thloyi/pump-parser/example"
"github.com/thloyi/pump-parser/example/geyser"
example "github.com/thloyi/pump-parser/internal/example"
)
func main() {
@@ -19,8 +17,8 @@ func main() {
//xt := tracker.NewTwitterTracker(nil) // Initialize Twitter tracker if needed
// laserstream-mainnet-slc.helius-rpc.com:80
ch := make(chan geyser.SubscriptionMessage, 1)
go geyser.RunLoopWithReConnect(context.Background(), "127.0.0.1:10001", parser.SolProgramPump, ch)
ch := make(chan example.SubscriptionMessage, 1)
go example.RunLoopWithReConnect(context.Background(), "127.0.0.1:10001", parser.SolProgramPump, ch)
// var tokenTxs = make(map[string]*types.Tx)
// currentBlock := uint64(0)
for msg := range ch {
@@ -48,7 +46,7 @@ func main() {
//}
// 处理交易
txErr, ok := ptx.Err.(*geyser.TransactionError)
txErr, ok := ptx.Err.(*parser.TransactionError)
var customerErrCode uint32
var instructorErrIndex uint8
if ok {
@@ -62,16 +60,16 @@ func main() {
}
printed := false
for _, tx := range txs {
if tx.Program != parser.SolProgramPump {
continue
}
if tx.Token1Amount.GreaterThanOrEqual(decimal.NewFromFloat(0.1)) || tx.Event != "buy" {
continue
}
//if tx.Program != parser.SolProgramPump {
// continue
//}
//if tx.Token1Amount.GreaterThanOrEqual(decimal.NewFromFloat(0.1)) || tx.Event != "buy" {
// continue
//}
printed = true
fmt.Printf("t: %s, block: %d, hash: %s, signer: %s, program: %s, event: %s, token1: %s, cuPrice: %s, mevAgent: %s, mevFee: %s, platform: %s, platformFee: %s, entryContract: %s, mayhem: %t\n",
fmt.Printf("t: %s, block: %d, hash: %s, maker: %s, program: %s, event: %s, token0: %s, entryContract: %s, token balance: %s, \n",
time.Now().Format(time.RFC3339Nano),
tx.Block, tx.GetTxHash(), tx.Maker, tx.Program, tx.Event, tx.Token1Amount, tx.CUPrice, tx.MevAgent, tx.MevAgentFee, tx.Platform, tx.PlatformFee, tx.EntryContract, tx.Mayhem)
tx.Block, tx.GetTxHash(), tx.Maker, tx.Program, tx.Event, tx.Token0Amount, tx.EntryContract, tx.AfterSignerToken0Balance)
//break
}
if !printed {

View File

@@ -1,4 +1,4 @@
package geyser
package parser
import (
"github.com/thloyi/pump-parser"

View File

@@ -1,4 +1,4 @@
package geyser
package parser
import (
"fmt"

View File

@@ -70,9 +70,9 @@ func FromTx(tx *parser.Tx) []*Tx {
for i, s := range tx.Swaps {
var newTx *Tx
platform, platformFee := tx.CheckPlatform(s)
token0Program := s.BaseTokenProgram
token0Address := s.BaseMint
token0Decimals := s.BaseMintDecimals
//token0Program := s.BaseTokenProgram
//token0Address := s.BaseMint
//token0Decimals := s.BaseMintDecimals
if s.Program == "Pump" {
quoteMint := s.QuoteMint
// 有些数据里 quote 会给 SystemProgram统一转成 WSOL
@@ -130,9 +130,9 @@ func FromTx(tx *parser.Tx) []*Tx {
} else if s.Event == "sell" {
eventName = "buy"
}
token0Program = s.QuoteTokenProgram
token0Address = s.QuoteMint
token0Decimals = s.QuoteMintDecimals
//token0Program = s.QuoteTokenProgram
//token0Address = s.QuoteMint
//token0Decimals = s.QuoteMintDecimals
newTx = &Tx{
Err: nil,
//BondingCurve: s.Pool.String(),
@@ -225,10 +225,11 @@ func FromTx(tx *parser.Tx) []*Tx {
if newTx == nil {
continue
}
if newTx.Maker == "HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K" && newTx.EntryContract == "oKXAggregatorV2" {
newTx.Maker = tx.Signer.String()
newTx.AfterSignerToken0Balance = tx.GetSignerTokenBalanceAfterTx(token0Program, token0Address).Div(decimal.New(1, int32(token0Decimals)))
}
//if (newTx.Maker == "HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K" && newTx.EntryContract == "oKXAggregatorV2") || (newTx.Maker == "ARu4n5mFdZogZAravu7CcizaojWnS6oqka37gdLT5SZn" && newTx.EntryContract == "oKXDExRouterV2") {
// newTx.Maker = tx.Signer.String()
// newTx.AfterSignerToken0Balance = tx.GetSignerTokenBalanceAfterTx(token0Program, token0Address).Div(decimal.New(1, int32(token0Decimals)))
//}
txs = append(txs, newTx)
}

View File

@@ -1,4 +1,4 @@
package geyser
package parser
import (
"context"
@@ -9,7 +9,6 @@ import (
"log"
"time"
solana2 "github.com/gagliardetto/solana-go"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
@@ -17,7 +16,7 @@ import (
"google.golang.org/grpc/metadata"
types "github.com/thloyi/pump-parser"
pb "github.com/thloyi/pump-parser/example/geyser/proto"
pb "go.onsig.ai/onsig/yellowstone-proto"
)
type Handler interface {
@@ -63,6 +62,9 @@ func NewClientWithPumpSwap(endpoint string, ch chan SubscriptionMessage) *Client
"pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", //Pump AMM
"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", //Pump
}
subscription.Transactions["transactions_sub"].AccountRequired = []string{
"ARu4n5mFdZogZAravu7CcizaojWnS6oqka37gdLT5SZn",
}
subscription.BlocksMeta = make(map[string]*pb.SubscribeRequestFilterBlocksMeta)
subscription.BlocksMeta["block_meta"] = &pb.SubscribeRequestFilterBlocksMeta{}
@@ -242,7 +244,7 @@ func (c *Client) grpcSubscribe(ctx context.Context, conn *grpc.ClientConn) error
}
continue
}
rawTx, err := ConvertYellowstoneGrpcTransactionToSolanaTransaction(txn, resp.GetCreatedAt().Seconds)
rawTx, err := types.ConvertYellowstoneGrpcTransactionToSolanaTransaction(txn, resp.GetCreatedAt().Seconds)
if err != nil {
log.Printf("Failed to convert transaction: %v", err)
continue
@@ -290,236 +292,3 @@ func (c *Client) sendBlock(blockMeta *pb.SubscribeUpdateBlockMeta) {
}
c.firstMessage = false
}
func ConvertYellowstoneGrpcTransactionToSolanaTransaction(y *pb.SubscribeUpdateTransaction, created int64) (*types.RawTx, error) {
sTx := &types.RawTx{
BlockTime: created,
Slot: y.Slot,
IndexWithinBlock: int64(y.Transaction.Index),
Meta: types.Meta{
Err: nil,
Fee: 0,
InnerInstructions: nil,
LoadedAddresses: types.LoadedAddresses{},
LogMessages: nil,
PostBalances: nil,
PostTokenBalances: nil,
PreBalances: nil,
PreTokenBalances: nil,
Rewards: nil,
},
//Transaction: types.Transaction{
// Message: types.Message{
// AccountKeys: nil,
// AddressTableLookups: nil,
// Header: types.Header{},
// Instructions: nil,
// RecentBlockHash: "",
// },
// Signatures: nil,
//},
//Version: nil,
}
meta := y.Transaction.GetMeta()
yTx := y.Transaction.Transaction
if meta.Err != nil && len(meta.Err.GetErr()) > 0 {
// If the transaction has an error, we set the error in the Meta
transError, err := DecodeTransactionError(meta.Err.GetErr())
if err != nil {
sTx.Meta.Err = err
} else {
sTx.Meta.Err = transError
}
// sTx.Meta.Err = meta.Err.GetErr()
}
sTx.Meta.Fee = meta.Fee
//sTx.Meta.InnerInstructions = meta.InnerInstructions
for _, innerInstr := range meta.InnerInstructions {
var instrs []types.Instruction
for _, instr := range innerInstr.Instructions {
instrs = append(instrs, types.Instruction{
ProgramIDIndex: int(instr.ProgramIdIndex),
Accounts: func() []int {
var out []int
for i := range instr.Accounts {
out = append(out, int(instr.Accounts[i]))
}
return out
}(),
Data: instr.Data,
})
}
sTx.Meta.InnerInstructions = append(sTx.Meta.InnerInstructions, types.InnerInstructions{
Index: int(innerInstr.Index),
Instructions: instrs,
})
}
sTx.Meta.LogMessages = meta.LogMessages
sTx.Meta.PostBalances = meta.PostBalances
sTx.Meta.PostTokenBalances = grpcTokenBalance(meta.PostTokenBalances)
sTx.Meta.PreBalances = meta.PreBalances
sTx.Meta.PreTokenBalances = grpcTokenBalance(meta.PreTokenBalances)
sTx.Meta.Rewards = nil
sTx.Meta.LoadedAddresses.Readonly = byteSlicesToKeySlices(meta.LoadedReadonlyAddresses)
sTx.Meta.LoadedAddresses.Writable = byteSlicesToKeySlices(meta.LoadedWritableAddresses)
// copy signatures
for i := range yTx.Signatures {
sTx.Transaction.Signatures = append(sTx.Transaction.Signatures, solana2.SignatureFromBytes(yTx.Signatures[i]))
}
// copy message
sTx.Transaction.Message = types.Message{
RecentBlockHash: solana2.HashFromBytes(yTx.Message.RecentBlockhash).String(),
}
// copy message.AccountKeys
//stopAt := len(yTx.Message.AccountKeys) - sTx.Message.NumLookups()
stopAt := len(yTx.Message.AccountKeys)
for accIndex, acc := range yTx.Message.AccountKeys {
sTx.Transaction.Message.AccountKeys = append(sTx.Transaction.Message.AccountKeys, solana2.PublicKeyFromBytes(acc))
if accIndex == stopAt-1 {
break
}
}
// copy message.Header
sTx.Transaction.Message.Header = types.Header{
NumRequiredSignatures: int(yTx.Message.Header.NumRequiredSignatures),
NumReadonlySignedAccounts: int(yTx.Message.Header.NumReadonlySignedAccounts),
NumReadonlyUnsignedAccounts: int(yTx.Message.Header.NumReadonlyUnsignedAccounts),
}
// copy message.versioned
if yTx.Message.Versioned {
sTx.Version = solana2.MessageVersionV0
} else {
sTx.Version = solana2.MessageVersionLegacy
}
// copy address table lookups
{
tables := map[solana2.PublicKey]solana2.PublicKeySlice{}
writable := byteSlicesToKeySlices(meta.LoadedWritableAddresses)
readonly := byteSlicesToKeySlices(meta.LoadedReadonlyAddresses)
for _, addr := range yTx.Message.AddressTableLookups {
sTx.Transaction.Message.AddressTableLookups = append(sTx.Transaction.Message.AddressTableLookups, solana2.MessageAddressTableLookup{
AccountKey: solana2.PublicKeyFromBytes(addr.AccountKey),
WritableIndexes: addr.WritableIndexes,
ReadonlyIndexes: addr.ReadonlyIndexes,
})
numTakeWritable := len(addr.WritableIndexes)
numTakeReadonly := len(addr.ReadonlyIndexes)
tableKey := solana2.PublicKeyFromBytes(addr.AccountKey)
{
// now need to rebuild the address table taking into account the indexes, and put the keys into the tables
maxIndex := 0
for _, indexB := range addr.WritableIndexes {
index := int(indexB)
if index > maxIndex {
maxIndex = index
}
}
for _, indexB := range addr.ReadonlyIndexes {
index := int(indexB)
if index > maxIndex {
maxIndex = index
}
}
tables[tableKey] = make([]solana2.PublicKey, maxIndex+1)
}
if numTakeWritable > 0 {
writableForTable := writable[:numTakeWritable]
for i, indexB := range addr.WritableIndexes {
index := int(indexB)
tables[tableKey][index] = writableForTable[i]
}
writable = writable[numTakeWritable:]
}
if numTakeReadonly > 0 {
readableForTable := readonly[:numTakeReadonly]
for i, indexB := range addr.ReadonlyIndexes {
index := int(indexB)
tables[tableKey][index] = readableForTable[i]
}
readonly = readonly[numTakeReadonly:]
}
}
}
// copy instructions
for _, instr := range yTx.Message.Instructions {
sTx.Transaction.Message.Instructions = append(sTx.Transaction.Message.Instructions, types.Instruction{
ProgramIDIndex: int(instr.ProgramIdIndex),
Accounts: func() []int {
var out []int
for i := range instr.Accounts {
out = append(out, int(instr.Accounts[i]))
}
return out
}(),
Data: instr.Data,
})
}
// resolve the lookups
//{
// if sTx.Transaction.Message.IsVersioned() {
// // only versioned transactions have address table lookups
// err := sTx.Transaction.Message.ResolveLookups()
// if err != nil {
// return sTx, fmt.Errorf("failed to resolve lookups: %w", err)
// }
// }
//}
return sTx, nil
}
func byteSlicesToKeySlices(keys [][]byte) []solana2.PublicKey {
var out []solana2.PublicKey
for _, key := range keys {
var k solana2.PublicKey
copy(k[:], key)
out = append(out, k)
}
return out
}
func grpcTokenBalance(src []*pb.TokenBalance) []types.TokenBalance {
out := make([]types.TokenBalance, len(src))
for i, tb := range src {
var (
mintAccount solana2.PublicKey
ownerAccount solana2.PublicKey
programIDAccount solana2.PublicKey
)
if tb.Mint != "" {
mintAccount, _ = solana2.PublicKeyFromBase58(tb.Mint)
}
if tb.Owner != "" {
ownerAccount, _ = solana2.PublicKeyFromBase58(tb.Owner)
}
if tb.ProgramId != "" {
programIDAccount, _ = solana2.PublicKeyFromBase58(tb.ProgramId)
}
out[i] = types.TokenBalance{
AccountIndex: int(tb.AccountIndex),
MintAccount: mintAccount,
OwnerAccount: &ownerAccount,
ProgramIDAccount: programIDAccount,
Mint: tb.Mint,
Owner: tb.Owner,
ProgramID: tb.ProgramId,
UITokenAmount: types.UITokenAmount{
Amount: tb.UiTokenAmount.Amount,
Decimals: uint64(tb.UiTokenAmount.Decimals),
UIAmount: tb.UiTokenAmount.UiAmount,
UIAmountString: tb.UiTokenAmount.UiAmountString,
},
}
}
return out
}

View File

@@ -40,7 +40,9 @@ var (
)
var (
pumpAmmBuyDiscriminator = calculateDiscriminator("global:buy")
pumpAmmBuyDiscriminator = calculateDiscriminator("global:buy")
pumpAmmBuyV2Discriminator = calculateDiscriminator("global:buy_exact_quote_in")
pumpAmmSellDiscriminator = calculateDiscriminator("global:sell")
pumpAmmCreateDiscriminator = calculateDiscriminator("global:create_pool")
pumpAmmWithdrawDiscriminator = calculateDiscriminator("global:withdraw")

35
pump.go
View File

@@ -218,6 +218,13 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pump create get inner instructions error: %v,offset, %d, %d", err, offset[0], offset[1])
}
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
for _, innerInstr := range inners {
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
entryContract = result.accountList[innerInstr.ProgramIDIndex]
}
}
}
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == feeEventProgramIndex && bytes.Equal(innerInstr.Data[:8], pumpGetFeesDiscriminator[:]) {
@@ -262,11 +269,6 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
}
offset = [2]uint{newoffset[0], newoffset[1]}
ataUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[6]
userBase := getAccountBalanceAfterTx(result, ataUserIdx)
userQuote, _ := GetSolAfterTx(result, userIndex)
event := ""
baseTokenProgram := solana.TokenProgramID
@@ -284,6 +286,24 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
Decimals: 6,
}
}
var user = tradeEvent.User
ataUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[6]
if !tradeEvent.User.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, tradeEvent.Mint)
//&& userBaseAmount.BigInt().Uint64() == tradeEvent.TokenAmount
if !userBaseAmount.IsZero() {
user = result.accountList[0]
userIndex = 0
ataUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx(result, ataUserIdx)
userQuote, _ := GetSolAfterTx(result, userIndex)
solAmount := tradeEvent.SolAmount
if tradeEvent.IsBuy && bytes.Equal(instruction.Data[:8], pumpBuyV2Discriminator[:]) {
fee := tradeEvent.Fee + tradeEvent.CreatorFee
@@ -304,7 +324,7 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
Creator: tradeEvent.Creator,
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: tradeEvent.User,
User: user,
BaseAmount: decimal.NewFromUint64(tradeEvent.TokenAmount),
QuoteAmount: decimal.NewFromUint64(solAmount),
BaseReserve: decimal.NewFromUint64(tradeEvent.RealTokenReserves),
@@ -327,13 +347,14 @@ func BuyOrSellParser(tx *Tx, instruction Instruction, innerInstructions InnerIns
Creator: tradeEvent.Creator,
BaseMintDecimals: 6,
QuoteMintDecimals: 9,
User: tradeEvent.User,
User: user,
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[1]]),
UserBaseBalance: userBase,
UserQuoteBalance: decimal.NewFromUint64(userQuote),
EntryContract: entryContract,
})
}
return swaps, offset, nil
}

View File

@@ -149,7 +149,7 @@ func pumpAmmParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
switch discriminator {
case pumpAmmCreateDiscriminator:
return ammCreatePoolParser(tx, instruction, innerInstructions, offset)
case pumpAmmBuyDiscriminator:
case pumpAmmBuyDiscriminator, pumpAmmBuyV2Discriminator:
return ammBuyParser(tx, instruction, innerInstructions, offset)
case pumpAmmSellDiscriminator:
return ammSellParser(tx, instruction, innerInstructions, offset)
@@ -245,6 +245,14 @@ func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstru
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm create get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
for _, innerInstr := range inners {
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
entryContract = result.accountList[innerInstr.ProgramIDIndex]
}
}
}
var event ammBuyEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
@@ -298,6 +306,28 @@ func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstru
TokenProgram: quoteTokenProgram,
}
}
var eventUser = event.User
baseMintAtaUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[1]
if !event.User.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
// && userBaseAmount.BigInt().Uint64() == event.BaseAmountOut
if !userBaseAmount.IsZero() {
eventUser = result.accountList[0]
userIndex = 0
baseMintAtaUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx)
userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
if quoteMint.Equals(wSolMint) {
userBalance, _ := GetSolAfterTx(result, userIndex)
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
}
return []Swap{
{
Program: SolProgramPumpAMM,
@@ -310,14 +340,14 @@ func ammBuyParser(tx *Tx, instruction Instruction, innerInstructions InnerInstru
Creator: event.CoinCreator,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
User: eventUser,
BaseAmount: decimal.NewFromUint64(event.BaseAmountOut),
QuoteAmount: decimal.NewFromUint64(event.UserQuoteAmountIn),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserve - event.BaseAmountOut),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserve + event.QuoteAmountIn),
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserve + event.BaseAmountOut),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserve - event.UserQuoteAmountIn),
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
EntryContract: entryContract,
},
}, offset, nil
@@ -332,6 +362,15 @@ func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
if err != nil {
return nil, increaseOffset(offset), fmt.Errorf("pumpamm sell get inner instructions error: %v, offset: %d, %d", err, offset[0], prefixLen)
}
if instruction.StackHeight != nil && *instruction.StackHeight > 2 {
for _, innerInstr := range inners {
if innerInstr.StackHeight != nil && *innerInstr.StackHeight == *instruction.StackHeight-1 {
entryContract = result.accountList[innerInstr.ProgramIDIndex]
}
}
}
var event ammSellEvent
for innerIndex, innerInstr := range inners {
if innerInstr.ProgramIDIndex == instruction.ProgramIDIndex &&
@@ -385,6 +424,28 @@ func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
TokenProgram: quoteTokenProgram,
}
}
var eventUser = event.User
baseMintAtaUserIdx := instruction.Accounts[5]
userIndex := instruction.Accounts[1]
if !event.User.IsOnCurve() && (entryContract.Equals(okxDexRoutersV2) || entryContract.Equals(okxAggregatorV2)) {
userBaseAmount, ataIndex := tokenBalanceChange(result, 0, baseTokenProgram, baseMint)
// && userBaseAmount.BigInt().Uint64() == event.BaseAmountIn
if !userBaseAmount.IsZero() {
eventUser = result.accountList[0]
userIndex = 0
baseMintAtaUserIdx = ataIndex
}
}
userBase := getAccountBalanceAfterTx(result, baseMintAtaUserIdx)
userQuote := GetTokenBalanceAfterTx(result, userIndex, quoteTokenProgram, quoteMint)
if quoteMint.Equals(wSolMint) {
userBalance, _ := GetSolAfterTx(result, userIndex)
userQuote = userQuote.Add(decimal.NewFromUint64(userBalance))
}
return []Swap{
{
Program: SolProgramPumpAMM,
@@ -397,14 +458,14 @@ func ammSellParser(tx *Tx, instruction Instruction, innerInstructions InnerInstr
Creator: event.CoinCreator,
BaseMintDecimals: baseMintDecimals,
QuoteMintDecimals: quoteMintDecimals,
User: event.User,
User: eventUser,
BaseAmount: decimal.NewFromUint64(event.BaseAmountIn),
QuoteAmount: decimal.NewFromUint64(event.UserQuoteAmountOut),
BaseReserve: decimal.NewFromUint64(event.PoolBaseTokenReserves + event.BaseAmountIn),
QuoteReserve: decimal.NewFromUint64(event.PoolQuoteTokenReserves - event.QuoteAmountOut),
Mayhem: isMayhemPump(result.accountList[instruction.Accounts[9]]),
UserBaseBalance: decimal.NewFromUint64(event.UserBaseTokenReserves - event.BaseAmountIn),
UserQuoteBalance: decimal.NewFromUint64(event.UserQuoteTokenReserves + event.UserQuoteAmountOut),
UserBaseBalance: userBase,
UserQuoteBalance: userQuote,
EntryContract: entryContract,
},
}, offset, nil

483
rawtx.go
View File

@@ -10,6 +10,7 @@ import (
"github.com/gagliardetto/solana-go/rpc"
"github.com/jackc/pgtype"
"github.com/shopspring/decimal"
pb "go.onsig.ai/onsig/yellowstone-proto"
)
func (tx *RawTx) getAccountList() []solana.PublicKey {
@@ -292,6 +293,198 @@ func InstructionsFromRpc(instructions []solana.CompiledInstruction) []Instructio
return instrs
}
func FromRpcTransactionWithMeta(tx rpc.TransactionWithMeta, blockTime *uint64, slot uint64, index int64) (*RawTx, error) {
created := int64(0)
if blockTime != nil {
created = int64(*blockTime)
}
sTx := &RawTx{
BlockTime: created,
Slot: slot,
IndexWithinBlock: index,
Meta: Meta{
Err: nil,
Fee: 0,
InnerInstructions: nil,
LoadedAddresses: LoadedAddresses{},
LogMessages: nil,
PostBalances: nil,
PostTokenBalances: nil,
PreBalances: nil,
PreTokenBalances: nil,
Rewards: nil,
},
}
meta := tx.Meta
yTx, _ := tx.GetTransaction()
if meta.Err != nil {
e, _ := json.Marshal(meta.Err)
sTx.Meta.Err = string(e)
}
sTx.Meta.Fee = meta.Fee
//sTx.Meta.InnerInstructions = meta.InnerInstructions
for _, innerInstr := range meta.InnerInstructions {
var instrs []Instruction
for _, instr := range innerInstr.Instructions {
instrs = append(instrs, Instruction{
ProgramIDIndex: int(instr.ProgramIDIndex),
Accounts: func() []int {
var out []int
for i := range instr.Accounts {
out = append(out, int(instr.Accounts[i]))
}
return out
}(),
Data: instr.Data,
StackHeight: newInt16(instr.StackHeight),
})
}
sTx.Meta.InnerInstructions = append(sTx.Meta.InnerInstructions, InnerInstructions{
Index: int(innerInstr.Index),
Instructions: instrs,
})
}
sTx.Meta.LogMessages = meta.LogMessages
sTx.Meta.PostBalances = meta.PostBalances
sTx.Meta.PreBalances = meta.PreBalances
sTx.Meta.PostTokenBalances = convertTokenBalanceFromRpc(meta.PostTokenBalances)
sTx.Meta.PreTokenBalances = convertTokenBalanceFromRpc(meta.PreTokenBalances)
sTx.Meta.Rewards = nil
sTx.Meta.LoadedAddresses.Readonly = meta.LoadedAddresses.ReadOnly
sTx.Meta.LoadedAddresses.Writable = meta.LoadedAddresses.Writable
// copy signatures
for i := range yTx.Signatures {
sTx.Transaction.Signatures = append(sTx.Transaction.Signatures, yTx.Signatures[i])
}
// copy message
sTx.Transaction.Message = Message{
RecentBlockHash: yTx.Message.RecentBlockhash.String(),
}
// copy message.AccountKeys
//stopAt := len(yTx.Message.AccountKeys) - sTx.Message.NumLookups()
stopAt := len(yTx.Message.AccountKeys)
for accIndex, acc := range yTx.Message.AccountKeys {
sTx.Transaction.Message.AccountKeys = append(sTx.Transaction.Message.AccountKeys, acc)
if accIndex == stopAt-1 {
break
}
}
// copy message.Header
sTx.Transaction.Message.Header = Header{
NumRequiredSignatures: int(yTx.Message.Header.NumRequiredSignatures),
NumReadonlySignedAccounts: int(yTx.Message.Header.NumReadonlySignedAccounts),
NumReadonlyUnsignedAccounts: int(yTx.Message.Header.NumReadonlyUnsignedAccounts),
}
// copy message.versioned
if yTx.Message.IsVersioned() {
sTx.Version = solana.MessageVersionV0
} else {
sTx.Version = solana.MessageVersionLegacy
}
// copy address table lookups
{
tables := map[solana.PublicKey]solana.PublicKeySlice{}
writable := meta.LoadedAddresses.Writable
readonly := meta.LoadedAddresses.ReadOnly
for _, addr := range yTx.Message.AddressTableLookups {
sTx.Transaction.Message.AddressTableLookups = append(sTx.Transaction.Message.AddressTableLookups, solana.MessageAddressTableLookup{
AccountKey: addr.AccountKey,
WritableIndexes: addr.WritableIndexes,
ReadonlyIndexes: addr.ReadonlyIndexes,
})
numTakeWritable := len(addr.WritableIndexes)
numTakeReadonly := len(addr.ReadonlyIndexes)
tableKey := addr.AccountKey
{
// now need to rebuild the address table taking into account the indexes, and put the keys into the tables
maxIndex := 0
for _, indexB := range addr.WritableIndexes {
index := int(indexB)
if index > maxIndex {
maxIndex = index
}
}
for _, indexB := range addr.ReadonlyIndexes {
index := int(indexB)
if index > maxIndex {
maxIndex = index
}
}
tables[tableKey] = make([]solana.PublicKey, maxIndex+1)
}
if numTakeWritable > 0 {
writableForTable := writable[:numTakeWritable]
for i, indexB := range addr.WritableIndexes {
index := int(indexB)
tables[tableKey][index] = writableForTable[i]
}
writable = writable[numTakeWritable:]
}
if numTakeReadonly > 0 {
readableForTable := readonly[:numTakeReadonly]
for i, indexB := range addr.ReadonlyIndexes {
index := int(indexB)
tables[tableKey][index] = readableForTable[i]
}
readonly = readonly[numTakeReadonly:]
}
}
}
// copy instructions
for _, instr := range yTx.Message.Instructions {
sTx.Transaction.Message.Instructions = append(sTx.Transaction.Message.Instructions, Instruction{
ProgramIDIndex: int(instr.ProgramIDIndex),
Accounts: func() []int {
var out []int
for i := range instr.Accounts {
out = append(out, int(instr.Accounts[i]))
}
return out
}(),
Data: instr.Data,
})
}
return sTx, nil
}
func convertTokenBalanceFromRpc(tb []rpc.TokenBalance) []TokenBalance {
var tokenBalances []TokenBalance = make([]TokenBalance, len(tb))
for i, balance := range tb {
var uiAmount = float64(0)
if balance.UiTokenAmount.UiAmount != nil {
uiAmount = *balance.UiTokenAmount.UiAmount
}
tokenBalances[i] = TokenBalance{
AccountIndex: int(balance.AccountIndex),
MintAccount: balance.Mint,
OwnerAccount: balance.Owner,
ProgramIDAccount: func() solana.PublicKey {
if balance.ProgramId != nil {
return *balance.ProgramId
}
return solana.PublicKey{}
}(),
UITokenAmount: UITokenAmount{
Amount: balance.UiTokenAmount.Amount,
Decimals: uint64(balance.UiTokenAmount.Decimals),
UIAmount: uiAmount,
UIAmountString: balance.UiTokenAmount.UiAmountString,
},
}
}
return tokenBalances
}
func InnerInstructionsFromRpc(instructions []rpc.InnerInstruction) []InnerInstructions {
var innerInstructions []InnerInstructions = make([]InnerInstructions, len(instructions))
for i, instruction := range instructions {
@@ -374,6 +567,49 @@ func getAccountBalanceAfterTx(result *RawTx, accountIndex int) decimal.Decimal {
return amount
}
func tokenBalanceChange(result *RawTx, accountIndex int, tokenProgram, mint solana.PublicKey) (change decimal.Decimal, ataIndex int) {
ataAccount, _, _ := solana.FindProgramAddress([][]byte{
result.accountList[accountIndex][:],
tokenProgram[:],
mint[:],
},
solana.SPLAssociatedTokenAccountProgramID)
for i, account := range result.accountList {
if account.Equals(ataAccount) {
ataIndex = i
break
}
}
if ataIndex == 0 {
return decimal.Zero, ataIndex
}
before := decimal.Zero
after := decimal.Zero
for _, pre := range result.Meta.PreTokenBalances {
if pre.AccountIndex == ataIndex {
amount, err := decimal.NewFromString(pre.UITokenAmount.Amount)
if err != nil {
return decimal.Zero, ataIndex
}
before = amount
break
}
}
for _, post := range result.Meta.PostTokenBalances {
if post.AccountIndex == ataIndex {
amount, err := decimal.NewFromString(post.UITokenAmount.Amount)
if err != nil {
return decimal.Zero, ataIndex
}
after = amount
break
}
}
return after.Sub(before).Abs(), ataIndex
}
func GetTokenBalanceAfterTx(result *RawTx, accountIndex int, tokenProgram, mint solana.PublicKey) decimal.Decimal {
ataAccount, _, _ := solana.FindProgramAddress([][]byte{
result.accountList[accountIndex][:],
@@ -445,3 +681,250 @@ func isAccountOwner(account, owner, mint solana.PublicKey) (bool, error) {
}
return account == ata, nil
}
func ConvertYellowstoneGrpcTransactionToSolanaTransaction(y *pb.SubscribeUpdateTransaction, created int64) (*RawTx, error) {
sTx := &RawTx{
BlockTime: created,
Slot: y.Slot,
IndexWithinBlock: int64(y.Transaction.Index),
Meta: Meta{
Err: nil,
Fee: 0,
InnerInstructions: nil,
LoadedAddresses: LoadedAddresses{},
LogMessages: nil,
PostBalances: nil,
PostTokenBalances: nil,
PreBalances: nil,
PreTokenBalances: nil,
Rewards: nil,
},
//Transaction: types.Transaction{
// Message: types.Message{
// AccountKeys: nil,
// AddressTableLookups: nil,
// Header: types.Header{},
// Instructions: nil,
// RecentBlockHash: "",
// },
// Signatures: nil,
//},
//Version: nil,
}
meta := y.Transaction.GetMeta()
yTx := y.Transaction.Transaction
if meta.Err != nil && len(meta.Err.GetErr()) > 0 {
// If the transaction has an error, we set the error in the Meta
transError, err := DecodeTransactionError(meta.Err.GetErr())
if err != nil {
sTx.Meta.Err = err
} else {
sTx.Meta.Err = transError
}
// sTx.Meta.Err = meta.Err.GetErr()
}
sTx.Meta.Fee = meta.Fee
//sTx.Meta.InnerInstructions = meta.InnerInstructions
for _, innerInstr := range meta.InnerInstructions {
var instrs []Instruction
for _, instr := range innerInstr.Instructions {
instrs = append(instrs, Instruction{
ProgramIDIndex: int(instr.ProgramIdIndex),
Accounts: func() []int {
var out []int
for i := range instr.Accounts {
out = append(out, int(instr.Accounts[i]))
}
return out
}(),
Data: instr.Data,
StackHeight: newInt(instr.StackHeight),
})
}
sTx.Meta.InnerInstructions = append(sTx.Meta.InnerInstructions, InnerInstructions{
Index: int(innerInstr.Index),
Instructions: instrs,
})
}
sTx.Meta.LogMessages = meta.LogMessages
sTx.Meta.PostBalances = meta.PostBalances
sTx.Meta.PostTokenBalances = grpcTokenBalance(meta.PostTokenBalances)
sTx.Meta.PreBalances = meta.PreBalances
sTx.Meta.PreTokenBalances = grpcTokenBalance(meta.PreTokenBalances)
sTx.Meta.Rewards = nil
sTx.Meta.LoadedAddresses.Readonly = byteSlicesToKeySlices(meta.LoadedReadonlyAddresses)
sTx.Meta.LoadedAddresses.Writable = byteSlicesToKeySlices(meta.LoadedWritableAddresses)
// copy signatures
for i := range yTx.Signatures {
sTx.Transaction.Signatures = append(sTx.Transaction.Signatures, solana.SignatureFromBytes(yTx.Signatures[i]))
}
// copy message
sTx.Transaction.Message = Message{
RecentBlockHash: solana.HashFromBytes(yTx.Message.RecentBlockhash).String(),
}
// copy message.AccountKeys
//stopAt := len(yTx.Message.AccountKeys) - sTx.Message.NumLookups()
stopAt := len(yTx.Message.AccountKeys)
for accIndex, acc := range yTx.Message.AccountKeys {
sTx.Transaction.Message.AccountKeys = append(sTx.Transaction.Message.AccountKeys, solana.PublicKeyFromBytes(acc))
if accIndex == stopAt-1 {
break
}
}
// copy message.Header
sTx.Transaction.Message.Header = Header{
NumRequiredSignatures: int(yTx.Message.Header.NumRequiredSignatures),
NumReadonlySignedAccounts: int(yTx.Message.Header.NumReadonlySignedAccounts),
NumReadonlyUnsignedAccounts: int(yTx.Message.Header.NumReadonlyUnsignedAccounts),
}
// copy message.versioned
if yTx.Message.Versioned {
sTx.Version = solana.MessageVersionV0
} else {
sTx.Version = solana.MessageVersionLegacy
}
// copy address table lookups
{
tables := map[solana.PublicKey]solana.PublicKeySlice{}
writable := byteSlicesToKeySlices(meta.LoadedWritableAddresses)
readonly := byteSlicesToKeySlices(meta.LoadedReadonlyAddresses)
for _, addr := range yTx.Message.AddressTableLookups {
sTx.Transaction.Message.AddressTableLookups = append(sTx.Transaction.Message.AddressTableLookups, solana.MessageAddressTableLookup{
AccountKey: solana.PublicKeyFromBytes(addr.AccountKey),
WritableIndexes: addr.WritableIndexes,
ReadonlyIndexes: addr.ReadonlyIndexes,
})
numTakeWritable := len(addr.WritableIndexes)
numTakeReadonly := len(addr.ReadonlyIndexes)
tableKey := solana.PublicKeyFromBytes(addr.AccountKey)
{
// now need to rebuild the address table taking into account the indexes, and put the keys into the tables
maxIndex := 0
for _, indexB := range addr.WritableIndexes {
index := int(indexB)
if index > maxIndex {
maxIndex = index
}
}
for _, indexB := range addr.ReadonlyIndexes {
index := int(indexB)
if index > maxIndex {
maxIndex = index
}
}
tables[tableKey] = make([]solana.PublicKey, maxIndex+1)
}
if numTakeWritable > 0 {
writableForTable := writable[:numTakeWritable]
for i, indexB := range addr.WritableIndexes {
index := int(indexB)
tables[tableKey][index] = writableForTable[i]
}
writable = writable[numTakeWritable:]
}
if numTakeReadonly > 0 {
readableForTable := readonly[:numTakeReadonly]
for i, indexB := range addr.ReadonlyIndexes {
index := int(indexB)
tables[tableKey][index] = readableForTable[i]
}
readonly = readonly[numTakeReadonly:]
}
}
}
// copy instructions
for _, instr := range yTx.Message.Instructions {
sTx.Transaction.Message.Instructions = append(sTx.Transaction.Message.Instructions, Instruction{
ProgramIDIndex: int(instr.ProgramIdIndex),
Accounts: func() []int {
var out []int
for i := range instr.Accounts {
out = append(out, int(instr.Accounts[i]))
}
return out
}(),
Data: instr.Data,
})
}
// resolve the lookups
//{
// if sTx.Transaction.Message.IsVersioned() {
// // only versioned transactions have address table lookups
// err := sTx.Transaction.Message.ResolveLookups()
// if err != nil {
// return sTx, fmt.Errorf("failed to resolve lookups: %w", err)
// }
// }
//}
return sTx, nil
}
func newInt16(x uint16) *int {
y := int(x)
return &y
}
func newInt(x *uint32) *int {
if x == nil {
return nil
}
y := int(*x)
return &y
}
func byteSlicesToKeySlices(keys [][]byte) []solana.PublicKey {
var out []solana.PublicKey
for _, key := range keys {
var k solana.PublicKey
copy(k[:], key)
out = append(out, k)
}
return out
}
func grpcTokenBalance(src []*pb.TokenBalance) []TokenBalance {
out := make([]TokenBalance, len(src))
for i, tb := range src {
var (
mintAccount solana.PublicKey
ownerAccount solana.PublicKey
programIDAccount solana.PublicKey
)
if tb.Mint != "" {
mintAccount, _ = solana.PublicKeyFromBase58(tb.Mint)
}
if tb.Owner != "" {
ownerAccount, _ = solana.PublicKeyFromBase58(tb.Owner)
}
if tb.ProgramId != "" {
programIDAccount, _ = solana.PublicKeyFromBase58(tb.ProgramId)
}
out[i] = TokenBalance{
AccountIndex: int(tb.AccountIndex),
MintAccount: mintAccount,
OwnerAccount: &ownerAccount,
ProgramIDAccount: programIDAccount,
Mint: tb.Mint,
Owner: tb.Owner,
ProgramID: tb.ProgramId,
UITokenAmount: UITokenAmount{
Amount: tb.UiTokenAmount.Amount,
Decimals: uint64(tb.UiTokenAmount.Decimals),
UIAmount: tb.UiTokenAmount.UiAmount,
UIAmountString: tb.UiTokenAmount.UiAmountString,
},
}
}
return out
}