diff --git a/cmd/debug_jupv6/main.go b/cmd/debug_jupv6/main.go new file mode 100644 index 0000000..b5544fa --- /dev/null +++ b/cmd/debug_jupv6/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "encoding/hex" + "fmt" + "os" +) + +func main() { + hexData := "bb64facc31c4af14be34e6edcc0000006f03a4df67000000b903320000000300000064342100024b00000000dc0500026310270203" + b, err := hex.DecodeString(hexData) + if err != nil { + panic(err) + } + payload := b[8:] + + off := 0 + read := func(n int) []byte { + if off+n > len(payload) { + fmt.Printf("OOB read: off=%d n=%d len=%d\n", off, n, len(payload)) + os.Exit(1) + } + out := payload[off : off+n] + off += n + return out + } + u8 := func() uint8 { return read(1)[0] } + leU16 := func() uint16 { + b := read(2) + return uint16(b[0]) | uint16(b[1])<<8 + } + leU32 := func() uint32 { + b := read(4) + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 + } + leU64 := func() uint64 { + b := read(8) + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 + } + + fmt.Printf("payload len=%d\n", len(payload)) + amountIn := leU64() + quotedOut := leU64() + slippage := leU16() + platform := leU16() + posSlip := leU16() + fmt.Printf("in=%d out=%d slip=%d plat=%d pos=%d\n", amountIn, quotedOut, slippage, platform, posSlip) + + planLen := leU32() + fmt.Printf("planLen=%d\n", planLen) + for i := uint32(0); i < planLen; i++ { + swapTag := u8() + fmt.Printf("step[%d] swapTag=%d (0x%02x) off=%d\n", i, swapTag, swapTag, off) + // payload depends on swapTag; we don't know, so just print next few bytes and stop + bps := leU16() + inIdx := u8() + outIdx := u8() + fmt.Printf(" bps=%d inIdx=%d outIdx=%d off=%d\n", bps, inIdx, outIdx, off) + } + fmt.Printf("done off=%d\n", off) +} diff --git a/cmd/shreder/main.go b/cmd/shreder/main.go index 14973e3..af45b3a 100644 --- a/cmd/shreder/main.go +++ b/cmd/shreder/main.go @@ -2,13 +2,15 @@ package main import ( "context" - "encoding/json" "errors" "fmt" + "log/slog" "os" "os/signal" "syscall" + "github.com/gagliardetto/solana-go/rpc" + "github.com/samlior/libsam/pkg/shreder" ) @@ -17,13 +19,25 @@ func main() { if url == "" { panic("URL is not set") } - + rpcUrl := os.Getenv("RPC_URL") + if rpcUrl == "" { + panic("RPC_URL is not set") + } + rpcClient := rpc.New(rpcUrl) + shreder.SetLogLevel(slog.LevelDebug) shrederClient, cleanup, err := shreder.NewShrederClient( url, + rpcClient, map[string]*shreder.SubscribeRequestFilterTransactions{ "pumpfunamm": { - AccountRequired: []string{ + //AccountRequired: []string{ + // "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + //}, + AccountInclude: []string{ "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + "GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority + "5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config + "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program }, }, "photon": { @@ -31,6 +45,11 @@ func main() { "BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW", }, }, + "jupiterV6": { + AccountRequired: []string{ + "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + }, + }, // TODO: axiom, gmgn, etc. }) if err != nil { @@ -63,8 +82,13 @@ func main() { case <-ctx.Done(): return case txBatch := <-txCh: - jsonData, _ := json.MarshalIndent(txBatch, "", " ") - fmt.Println(string(jsonData)) + //jsonData, _ := json.MarshalIndent(txBatch, "", " ") + for _, tx := range txBatch { + if tx.Label == "jupiterV6" { + fmt.Println("===============", tx.TxHash, tx.Token0Address, tx.Token0Amount) + } + } + //fmt.Println(txBatch[0].TxHash) } } } diff --git a/go.mod b/go.mod index 037ebc3..5257f9c 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gagliardetto/solana-go v1.12.0 github.com/mr-tron/base58 v1.2.0 github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454 + github.com/panjf2000/ants/v2 v2.11.4 github.com/shopspring/decimal v1.4.0 google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.10 @@ -21,6 +22,7 @@ require ( github.com/gagliardetto/binary v0.8.0 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect @@ -38,6 +40,7 @@ require ( go.uber.org/zap v1.21.0 // indirect golang.org/x/crypto v0.44.0 // indirect golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect diff --git a/go.sum b/go.sum index 0e80975..21e80a7 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= @@ -68,6 +70,8 @@ github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454 h1:lFN7TVecCMbCHVN github.com/near/borsh-go v0.3.2-0.20220516180422-1ff87d108454/go.mod h1:NeMochZp7jN/pYFuxLkrZtmLqbADmnp/y1+/dL+AsyQ= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/panjf2000/ants/v2 v2.11.4 h1:UJQbtN1jIcI5CYNocTj0fuAUYvsLjPoYi0YuhqV/Y48= +github.com/panjf2000/ants/v2 v2.11.4/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -81,8 +85,9 @@ github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:Vl github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -140,6 +145,8 @@ golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/shreder/OnChain_Labs_DexRouterV2-idl.json b/pkg/shreder/OnChain_Labs_DexRouterV2-idl.json new file mode 100644 index 0000000..58b0669 --- /dev/null +++ b/pkg/shreder/OnChain_Labs_DexRouterV2-idl.json @@ -0,0 +1 @@ +{"address":"proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u","metadata":{"name":"dex_solana_v3","version":"0.1.0","spec":"0.1.0","description":"Created with Anchor"},"instructions":[{"name":"claim","discriminator":[62,198,214,193,213,159,108,210],"accounts":[{"name":"signer","writable":true,"signer":true,"address":"CjoV5B96reuCfPh2rRK11G1QptG97jZdyZArTn3EN1Mj"},{"name":"receiver","writable":true},{"name":"source_token_account","writable":true,"optional":true},{"name":"destination_token_account","writable":true,"optional":true},{"name":"sa_authority","writable":true,"address":"ARu4n5mFdZogZAravu7CcizaojWnS6oqka37gdLT5SZn"},{"name":"token_mint","optional":true},{"name":"token_program","optional":true},{"name":"system_program","address":"11111111111111111111111111111111"},{"name":"associated_token_program","optional":true,"address":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"}],"args":[]},{"name":"create_token_account","discriminator":[147,241,123,100,244,132,174,118],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"owner"},{"name":"token_account","writable":true,"pda":{"seeds":[{"kind":"account","path":"token_mint"},{"kind":"account","path":"owner"}]}},{"name":"token_mint","address":"So11111111111111111111111111111111111111112"},{"name":"token_program","address":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"},{"name":"system_program","address":"11111111111111111111111111111111"}],"args":[{"name":"bump","type":"u8"}]},{"name":"create_token_account_with_seed","discriminator":[125,191,239,140,66,8,9,228],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"owner"},{"name":"token_account","writable":true,"pda":{"seeds":[{"kind":"account","path":"token_mint"},{"kind":"account","path":"owner"},{"kind":"arg","path":"seed"}]}},{"name":"token_mint","address":"So11111111111111111111111111111111111111112"},{"name":"token_program","address":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"},{"name":"system_program","address":"11111111111111111111111111111111"}],"args":[{"name":"bump","type":"u8"},{"name":"seed","type":"u32"}]},{"name":"proxy_swap","discriminator":[19,44,130,148,72,56,44,238],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"source_token_account","writable":true},{"name":"destination_token_account","writable":true},{"name":"source_mint"},{"name":"destination_mint"},{"name":"sa_authority","optional":true,"pda":{"seeds":[{"kind":"const","value":[111,107,120,95,115,97]}]}},{"name":"source_token_sa","writable":true,"optional":true},{"name":"destination_token_sa","writable":true,"optional":true},{"name":"source_token_program","optional":true},{"name":"destination_token_program","optional":true},{"name":"associated_token_program","optional":true,"address":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"},{"name":"system_program","optional":true,"address":"11111111111111111111111111111111"},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"SwapArgs"}}}]},{"name":"swap","discriminator":[248,198,158,145,225,117,135,200],"accounts":[{"name":"payer","signer":true},{"name":"source_token_account","writable":true},{"name":"destination_token_account","writable":true},{"name":"source_mint"},{"name":"destination_mint"},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"SwapArgs"}}}]},{"name":"swap_tob","discriminator":[170,41,85,177,132,80,31,53],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"source_token_account","writable":true},{"name":"destination_token_account","writable":true},{"name":"source_mint"},{"name":"destination_mint"},{"name":"commission_account","writable":true,"optional":true},{"name":"platform_fee_account","writable":true,"optional":true},{"name":"sa_authority","writable":true,"optional":true},{"name":"source_token_sa","writable":true,"optional":true},{"name":"destination_token_sa","writable":true,"optional":true},{"name":"source_token_program","optional":true},{"name":"destination_token_program","optional":true},{"name":"associated_token_program","optional":true,"address":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"},{"name":"system_program","optional":true,"address":"11111111111111111111111111111111"},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"SwapArgs"}}},{"name":"commission_info","type":"u32"},{"name":"platform_fee_rate","type":"u16"},{"name":"trim_rate","type":"u8"}]},{"name":"swap_tob_enhanced","discriminator":[190,156,169,176,149,154,161,108],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"source_token_account","writable":true},{"name":"destination_token_account","writable":true},{"name":"source_mint"},{"name":"destination_mint"},{"name":"commission_account","writable":true,"optional":true},{"name":"platform_fee_account","writable":true,"optional":true},{"name":"sa_authority","writable":true,"optional":true},{"name":"source_token_sa","writable":true,"optional":true},{"name":"destination_token_sa","writable":true,"optional":true},{"name":"source_token_program","optional":true},{"name":"destination_token_program","optional":true},{"name":"associated_token_program","optional":true,"address":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"},{"name":"system_program","optional":true,"address":"11111111111111111111111111111111"},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"SwapArgs"}}},{"name":"commission_info","type":"u32"},{"name":"platform_fee_rate","type":"u16"},{"name":"trim_rate","type":"u8"},{"name":"charge_rate","type":"u16"}]},{"name":"swap_tob_v2","docs":["Used to support commission distribution between parent and child nodes in TOB business"],"discriminator":[72,1,215,242,8,75,54,216],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"source_token_account","writable":true},{"name":"destination_token_account","writable":true},{"name":"source_mint"},{"name":"destination_mint"},{"name":"parent_commission_account","writable":true},{"name":"child_commission_account","writable":true},{"name":"platform_fee_account","writable":true,"optional":true},{"name":"sa_authority","writable":true,"optional":true},{"name":"source_token_sa","writable":true,"optional":true},{"name":"destination_token_sa","writable":true,"optional":true},{"name":"source_token_program","optional":true},{"name":"destination_token_program","optional":true},{"name":"associated_token_program","optional":true,"address":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"},{"name":"system_program","optional":true,"address":"11111111111111111111111111111111"},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"SwapArgs"}}},{"name":"total_commission_info","type":"u32"},{"name":"parent_commission_rate","type":"u32"},{"name":"platform_fee_rate","type":"u16"},{"name":"trim_rate","type":"u8"}]},{"name":"swap_tob_with_receiver","discriminator":[223,170,216,234,204,6,241,25],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"source_token_account","writable":true},{"name":"destination_token_account","writable":true},{"name":"source_mint"},{"name":"destination_mint"},{"name":"commission_account","writable":true,"optional":true},{"name":"platform_fee_account","writable":true,"optional":true},{"name":"sa_authority","writable":true,"optional":true},{"name":"source_token_sa","writable":true,"optional":true},{"name":"destination_token_sa","writable":true,"optional":true},{"name":"source_token_program","optional":true},{"name":"destination_token_program","optional":true},{"name":"associated_token_program","optional":true,"address":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"},{"name":"system_program","optional":true,"address":"11111111111111111111111111111111"},{"name":"sol_receiver","docs":["Optional SOL receiver account","- None: normal swap or SOL stays with payer","- Some: SOL receiver when converting wSOL -> SOL"],"writable":true,"optional":true},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"SwapArgs"}}},{"name":"commission_info","type":"u32"},{"name":"platform_fee_rate","type":"u16"},{"name":"trim_rate","type":"u8"}]},{"name":"swap_toc","discriminator":[187,201,212,51,16,155,236,60],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"source_token_account","writable":true},{"name":"destination_token_account","writable":true},{"name":"source_mint"},{"name":"destination_mint"},{"name":"commission_account","writable":true,"optional":true},{"name":"platform_fee_account","writable":true,"optional":true},{"name":"sa_authority","writable":true,"optional":true},{"name":"source_token_sa","writable":true,"optional":true},{"name":"destination_token_sa","writable":true,"optional":true},{"name":"source_token_program","optional":true},{"name":"destination_token_program","optional":true},{"name":"associated_token_program","optional":true,"address":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"},{"name":"system_program","optional":true,"address":"11111111111111111111111111111111"},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"SwapArgs"}}},{"name":"commission_info","type":"u32"},{"name":"platform_fee_rate","type":"u16"}]},{"name":"swap_toc_v2","docs":["Used to support commission distribution between parent and child nodes in TOC business"],"discriminator":[127,214,107,189,23,90,47,104],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"source_token_account","writable":true},{"name":"destination_token_account","writable":true},{"name":"source_mint"},{"name":"destination_mint"},{"name":"parent_commission_account","writable":true},{"name":"child_commission_account","writable":true},{"name":"platform_fee_account","writable":true,"optional":true},{"name":"sa_authority","writable":true,"optional":true},{"name":"source_token_sa","writable":true,"optional":true},{"name":"destination_token_sa","writable":true,"optional":true},{"name":"source_token_program","optional":true},{"name":"destination_token_program","optional":true},{"name":"associated_token_program","optional":true,"address":"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"},{"name":"system_program","optional":true,"address":"11111111111111111111111111111111"},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"SwapArgs"}}},{"name":"total_commission_info","type":"u32"},{"name":"parent_commission_rate","type":"u32"},{"name":"platform_fee_rate","type":"u16"}]},{"name":"wrap_unwrap","discriminator":[220,101,139,249,41,190,118,199],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"payer_wsol_account","writable":true},{"name":"wsol_mint","address":"So11111111111111111111111111111111111111112"},{"name":"temp_wsol_account","writable":true,"optional":true,"pda":{"seeds":[{"kind":"const","value":[116,101,109,112,95,119,115,111,108]},{"kind":"account","path":"payer"}]}},{"name":"commission_account","writable":true,"optional":true},{"name":"platform_fee_account","writable":true,"optional":true},{"name":"authority_pda","docs":["Used for signing fee transfers from authority_pda (SOL) or wsol_sa (WSOL)"],"writable":true,"optional":true},{"name":"wsol_sa","docs":["This is the authority_pda's associated token account for WSOL"],"writable":true,"optional":true},{"name":"token_program"},{"name":"system_program","address":"11111111111111111111111111111111"},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"PlatformFeeWrapUnwrapArgs"}}}]},{"name":"wrap_unwrap_with_receiver","docs":["Wrap/Unwrap with optional specified receiver","- Wrap (SOL -> WSOL): receiver is WSOL token account (ATA)","- Unwrap (WSOL -> SOL): receiver is system account (EOA)","Transfer amount:","- From fee: amount_in","- To fee: amount_in - fees"],"discriminator":[123,25,47,134,233,167,171,170],"accounts":[{"name":"payer","writable":true,"signer":true},{"name":"payer_wsol_account","writable":true},{"name":"wsol_mint","address":"So11111111111111111111111111111111111111112"},{"name":"temp_wsol_account","writable":true,"optional":true,"pda":{"seeds":[{"kind":"const","value":[116,101,109,112,95,119,115,111,108]},{"kind":"account","path":"payer"}]}},{"name":"commission_account","writable":true,"optional":true},{"name":"platform_fee_account","writable":true,"optional":true},{"name":"authority_pda","docs":["Used for signing fee transfers from authority_pda (SOL) or wsol_sa (WSOL)"],"writable":true,"optional":true},{"name":"wsol_sa","docs":["This is the authority_pda's associated token account for WSOL"],"writable":true,"optional":true},{"name":"token_program"},{"name":"system_program","address":"11111111111111111111111111111111"},{"name":"receiver","docs":["- Wrap: WSOL token account (ATA) to receive WSOL","- Unwrap: System account (EOA) to receive SOL"],"writable":true},{"name":"event_authority","pda":{"seeds":[{"kind":"const","value":[95,95,101,118,101,110,116,95,97,117,116,104,111,114,105,116,121]}]}},{"name":"program"}],"args":[{"name":"args","type":{"defined":{"name":"PlatformFeeWrapUnwrapArgs"}}}]}],"events":[{"name":"SwapCpiEvent","discriminator":[85,81,149,239,163,74,158,111]},{"name":"SwapEvent","discriminator":[64,198,205,232,38,8,113,226]},{"name":"SwapToBWithFeesCpiEventV2","discriminator":[65,237,163,109,189,222,187,45]},{"name":"SwapToCWithFeesCpiEventV2","discriminator":[71,137,74,60,189,117,182,65]},{"name":"SwapWithFeesCpiEvent","discriminator":[189,97,67,12,37,209,247,29]},{"name":"SwapWithFeesCpiEventEnhanced","discriminator":[37,72,219,67,50,244,1,213]}],"errors":[{"code":6000,"name":"RoutesCannotBeEmpty","msg":"Routes cannot be empty"},{"code":6001,"name":"TooManyRoutes","msg":"Too many routes"},{"code":6002,"name":"MultipleOrNoOutputNode","msg":"Multiple or no output node"},{"code":6003,"name":"MultipleOrNoInputNode","msg":"Multiple or no input node"},{"code":6004,"name":"NotAllNodesProcessed","msg":"Not all nodes are processed"},{"code":6005,"name":"InputIndexShouldStartFromZero","msg":"Input index should start from zero"},{"code":6006,"name":"CircularDependency","msg":"Circular dependency detected in DAG"},{"code":6007,"name":"InvalidWeight","msg":"Invalid weight"},{"code":6008,"name":"InvalidNodeOrder","msg":"Invalid node order"},{"code":6009,"name":"TargetNodeNotFound","msg":"Target node not found"},{"code":6010,"name":"MinReturnNotReached","msg":"Min return not reached"},{"code":6011,"name":"AmountInMustBeGreaterThanZero","msg":"amount_in must be greater than 0"},{"code":6012,"name":"SlippageTooHigh","msg":"slippage must be less than 100%"},{"code":6013,"name":"InvalidExpectAmountOut","msg":"invalid expect amount out"},{"code":6014,"name":"WeightsMustSumTo100","msg":"weights must sum to 100%"},{"code":6015,"name":"InvalidShareAmount","msg":"Invalid share amount"},{"code":6016,"name":"InvalidCommissionRate","msg":"Invalid commission rate"},{"code":6017,"name":"InvalidParentCommissionRate","msg":"Invalid parent commission rate"},{"code":6018,"name":"InvalidTrimRate","msg":"Invalid trim rate"},{"code":6019,"name":"InvalidChargeRate","msg":"Invalid charge rate"},{"code":6020,"name":"InvalidCommissionTemporaryTokenAccount","msg":"Invalid commission temporary token account"},{"code":6021,"name":"InvalidAccountsLength","msg":"Invalid accounts length"},{"code":6022,"name":"SwapAuthorityIsNotSigner","msg":"Swap authority is not signer"},{"code":6023,"name":"InvalidAuthorityPda","msg":"Invalid authority pda"},{"code":6024,"name":"InvalidSwapAuthority","msg":"Invalid swap authority"},{"code":6025,"name":"InvalidProgramId","msg":"Invalid program id"},{"code":6026,"name":"InvalidPool","msg":"Invalid pool"},{"code":6027,"name":"InvalidTokenMint","msg":"Invalid token mint"},{"code":6028,"name":"CalculationError","msg":"Calculation error"},{"code":6029,"name":"InvalidSanctumLstStateListData","msg":"Invalid sanctum lst state list data"},{"code":6030,"name":"InvalidSanctumLstStateListIndex","msg":"Invalid sanctum lst state list index"},{"code":6031,"name":"InvalidSanctumSwapAccounts","msg":"Invalid sanctum swap accounts"},{"code":6032,"name":"InvalidSwapAuthorityAccounts","msg":"Invalid swap authority account"},{"code":6033,"name":"InvalidBridgeSeed","msg":"Bridge Seed Error"},{"code":6034,"name":"InvalidBundleInput","msg":"Invalid accounts and instruction length"},{"code":6035,"name":"InvalidPlatformFeeRate","msg":"Invalid platform fee rate"},{"code":6036,"name":"AmountOutMustBeGreaterThanZero","msg":"Amount out must be greater than 0"},{"code":6037,"name":"InvalidDampingTerm","msg":"Invalid DampingTerm"},{"code":6038,"name":"InvalidMint","msg":"Invalid mint"},{"code":6039,"name":"InvalidPlatformFeeAccount","msg":"Invalid platform fee account"},{"code":6040,"name":"InvalidTrimAccount","msg":"Invalid trim account"},{"code":6041,"name":"InvalidChargeAccount","msg":"Invalid charge account"},{"code":6042,"name":"InvalidPlatformFeeAmount","msg":"Invalid platform fee amount"},{"code":6043,"name":"InvalidFeeTokenAccount","msg":"Invalid fee token account"},{"code":6044,"name":"InvalidSaAuthority","msg":"Invalid sa authority"},{"code":6045,"name":"InvalidNodeFromAccounts","msg":"Invalid node from accounts"},{"code":6046,"name":"InvalidNodeToAccounts","msg":"Invalid node to accounts"},{"code":6047,"name":"InvalidSourceTokenAccount","msg":"Invalid source token account"},{"code":6048,"name":"InvalidTokenAccount","msg":"Invalid token account"},{"code":6049,"name":"InvalidDestinationTokenAccount","msg":"Invalid destination token account"},{"code":6050,"name":"CommissionAccountIsNone","msg":"Commission account is none"},{"code":6051,"name":"PlatformFeeAccountIsNone","msg":"Platform fee account is none"},{"code":6052,"name":"TrimAccountIsNone","msg":"Trim account is none"},{"code":6053,"name":"ChargeAccountIsNone","msg":"Charge account is none"},{"code":6054,"name":"InvalidFeeAccount","msg":"Invalid fee account"},{"code":6055,"name":"InvalidTokenOwner","msg":"Invalid token owner"},{"code":6056,"name":"SaAuthorityIsNone","msg":"Sa authority is none"},{"code":6057,"name":"SourceTokenSaIsNone","msg":"Source token sa is none"},{"code":6058,"name":"SourceTokenProgramIsNone","msg":"Source token program is none"},{"code":6059,"name":"DestinationTokenSaIsNone","msg":"Destination token sa is none"},{"code":6060,"name":"DestinationTokenProgramIsNone","msg":"Destination token program is none"},{"code":6061,"name":"ResultMustBeGreaterThanZero","msg":"Calculation result must be greater than zero"},{"code":6062,"name":"InvalidAccountData","msg":"Invalid account data"},{"code":6063,"name":"InvalidRfqParameters","msg":"Invalid RFQ parameters"},{"code":6064,"name":"TobAuthorityPdaRequired","msg":"TOB mode requires authority PDA"},{"code":6065,"name":"TobWsolSaRequired","msg":"TOB mode with WSOL fees requires wsol_sa account"},{"code":6066,"name":"InvalidWsolSa","msg":"Invalid WSOL SA account"},{"code":6067,"name":"InvalidCommissionAccount","msg":"Invalid commission account"},{"code":6068,"name":"InvalidActualAmountIn","msg":"Invalid actual amount in"},{"code":6069,"name":"UnexpectedSaTokenAccount","msg":"Unexpected SA token account in CPI"},{"code":6070,"name":"InvalidSourceTokenSaMint","msg":"Invalid source token sa mint"},{"code":6071,"name":"InvalidDestinationTokenSaMint","msg":"Invalid destination token sa mint"},{"code":6072,"name":"InsufficientFunds","msg":"Insufficient funds"},{"code":6073,"name":"SaAuthorityLamportsDecreased","msg":"Sa authority lamports decreased"},{"code":6074,"name":"InvalidTokenProgram","msg":"Invalid token program"},{"code":6075,"name":"InvalidSigner","msg":"Invalid signer"},{"code":6076,"name":"InvalidAssociatedTokenProgram","msg":"Invalid associated token program"},{"code":6077,"name":"TokenProgramIsNone","msg":"Token program is none"},{"code":6078,"name":"AssociatedTokenProgramIsNone","msg":"Associated token program is none"},{"code":6079,"name":"SystemProgramIsNone","msg":"System program is none"},{"code":6080,"name":"InsufficientBalance","msg":"Insufficient balance for transfer"},{"code":6081,"name":"SolReceiverMustBeSystemAccount","msg":"SOL receiver must be a system account"},{"code":6082,"name":"SolReceiverRequiresAccCloseFlag","msg":"SOL receiver requires acc_close_flag to be true"},{"code":6083,"name":"DestinationMustBeWsolForSolReceiver","msg":"Destination must be wSOL when sol_receiver is specified"},{"code":6084,"name":"InvalidGoonfiParameters","msg":"Invalid goonfi parameters"},{"code":6085,"name":"AdapterAbort","msg":"Adapter abort"},{"code":6086,"name":"InconsistentFeeAccountTypes","msg":"Inconsistent fee account types"},{"code":6087,"name":"ReceiverMustBeTokenAccount","msg":"Receiver must be a token account"},{"code":6088,"name":"ReceiverMustBeWsolAccount","msg":"Receiver must be a WSOL token account"},{"code":6089,"name":"InconsistentCommissionAndTrimAccountTypes","msg":"The commission fee account and trim account are different types."}],"types":[{"name":"Dex","type":{"kind":"enum","variants":[{"name":"SplTokenSwap"},{"name":"StableSwap"},{"name":"Whirlpool"},{"name":"MeteoraDynamicpool"},{"name":"RaydiumSwap"},{"name":"RaydiumStableSwap"},{"name":"RaydiumClmmSwap"},{"name":"AldrinExchangeV1"},{"name":"AldrinExchangeV2"},{"name":"LifinityV1"},{"name":"LifinityV2"},{"name":"RaydiumClmmSwapV2"},{"name":"FluxBeam"},{"name":"MeteoraDlmm"},{"name":"RaydiumCpmmSwap"},{"name":"OpenBookV2"},{"name":"WhirlpoolV2"},{"name":"Phoenix"},{"name":"ObricV2"},{"name":"SanctumAddLiq"},{"name":"SanctumRemoveLiq"},{"name":"SanctumNonWsolSwap"},{"name":"SanctumWsolSwap"},{"name":"PumpfunBuy"},{"name":"PumpfunSell"},{"name":"StabbleSwap"},{"name":"SanctumRouter"},{"name":"MeteoraVaultDeposit"},{"name":"MeteoraVaultWithdraw"},{"name":"Saros"},{"name":"MeteoraLst"},{"name":"Solfi"},{"name":"QualiaSwap"},{"name":"Zerofi"},{"name":"PumpfunammBuy"},{"name":"PumpfunammSell"},{"name":"Virtuals"},{"name":"VertigoBuy"},{"name":"VertigoSell"},{"name":"PerpetualsAddLiq"},{"name":"PerpetualsRemoveLiq"},{"name":"PerpetualsSwap"},{"name":"RaydiumLaunchpad"},{"name":"LetsBonkFun"},{"name":"Woofi"},{"name":"MeteoraDbc"},{"name":"MeteoraDlmmSwap2"},{"name":"MeteoraDAMMV2"},{"name":"Gavel"},{"name":"BoopfunBuy"},{"name":"BoopfunSell"},{"name":"MeteoraDbc2"},{"name":"GooseFX"},{"name":"Dooar"},{"name":"Numeraire"},{"name":"SaberDecimalWrapperDeposit"},{"name":"SaberDecimalWrapperWithdraw"},{"name":"SarosDlmm"},{"name":"OneDexSwap"},{"name":"Manifest"},{"name":"ByrealClmm"},{"name":"PancakeSwapV3Swap"},{"name":"PancakeSwapV3SwapV2"},{"name":"Tessera"},{"name":"SolRfq","fields":[{"name":"rfq_id","type":"u64"},{"name":"expected_maker_amount","type":"u64"},{"name":"expected_taker_amount","type":"u64"},{"name":"maker_send_amount","type":"u64"},{"name":"taker_send_amount","type":"u64"},{"name":"expiry","type":"u64"},{"name":"maker_use_native_sol","type":"bool"},{"name":"taker_use_native_sol","type":"bool"}]},{"name":"Humidifi"},{"name":"HeavenBuy"},{"name":"HeavenSell"},{"name":"SolfiV2"},{"name":"Goonfi"},{"name":"MoonitBuy"},{"name":"MoonitSell"},{"name":"RaydiumSwapV2"},{"name":"Whalestreet"},{"name":"SugarMoneyBuy","fields":[{"name":"bonding_curve_bump","type":"u8"},{"name":"bonding_curve_sol_associated_account_bump","type":"u8"}]},{"name":"SugarMoneySell","fields":[{"name":"bonding_curve_bump","type":"u8"},{"name":"bonding_curve_sol_associated_account_bump","type":"u8"}]},{"name":"MeteoraDAMMV2Swap2"},{"name":"AlphaQ"},{"name":"FutarchyAmm"},{"name":"PumpfunBuy2"},{"name":"PumpfunSell2"},{"name":"HumidifiSwap2","fields":[{"name":"swap_id","type":"u64"}]},{"name":"Scorch","fields":[{"name":"id","type":"u128"}]},{"name":"JupiterLendDeposit"},{"name":"JupiterLendRedeem"},{"name":"TokkaAmm"}]}},{"name":"PlatformFeeWrapUnwrapArgs","type":{"kind":"struct","fields":[{"name":"order_id","type":"u64"},{"name":"amount_in","type":"u64"},{"name":"commission_info","type":"u32"},{"name":"platform_fee_rate","type":"u16"},{"name":"tob","type":"bool"}]}},{"name":"Route","type":{"kind":"struct","fields":[{"name":"dex","type":{"defined":{"name":"Dex"}}},{"name":"weight","type":"u16"},{"name":"index","type":"u8"}]}},{"name":"SwapArgs","type":{"kind":"struct","fields":[{"name":"order_id","type":"u64"},{"name":"amount_in","type":"u64"},{"name":"expect_amount_out","type":"u64"},{"name":"slippage","type":"u16"},{"name":"routes","type":{"vec":{"defined":{"name":"Route"}}}}]}},{"name":"SwapCpiEvent","type":{"kind":"struct","fields":[{"name":"order_id","type":"u64"},{"name":"source_mint","type":"pubkey"},{"name":"destination_mint","type":"pubkey"},{"name":"source_token_account_owner","type":"pubkey"},{"name":"destination_token_account_owner","type":"pubkey"},{"name":"source_token_change","type":"u64"},{"name":"destination_token_change","type":"u64"}]}},{"name":"SwapEvent","type":{"kind":"struct","fields":[{"name":"dex","type":{"defined":{"name":"Dex"}}},{"name":"amount_in","type":"u64"},{"name":"amount_out","type":"u64"}]}},{"name":"SwapToBWithFeesCpiEventV2","type":{"kind":"struct","fields":[{"name":"order_id","type":"u64"},{"name":"source_mint","type":"pubkey"},{"name":"destination_mint","type":"pubkey"},{"name":"source_token_account_owner","type":"pubkey"},{"name":"destination_token_account_owner","type":"pubkey"},{"name":"source_token_change","type":"u64"},{"name":"destination_token_change","type":"u64"},{"name":"commission_direction","type":"bool"},{"name":"total_commission_rate","type":"u32"},{"name":"parent_commission_rate","type":"u32"},{"name":"parent_commission_amount","type":"u64"},{"name":"parent_commission_account","type":"pubkey"},{"name":"child_commission_rate","type":"u32"},{"name":"child_commission_amount","type":"u64"},{"name":"child_commission_account","type":"pubkey"},{"name":"platform_fee_rate","type":"u16"},{"name":"platform_fee_amount","type":"u64"},{"name":"platform_fee_account","type":"pubkey"},{"name":"trim_rate","type":"u8"},{"name":"trim_amount","type":"u64"},{"name":"trim_account","type":"pubkey"}]}},{"name":"SwapToCWithFeesCpiEventV2","type":{"kind":"struct","fields":[{"name":"order_id","type":"u64"},{"name":"source_mint","type":"pubkey"},{"name":"destination_mint","type":"pubkey"},{"name":"source_token_account_owner","type":"pubkey"},{"name":"destination_token_account_owner","type":"pubkey"},{"name":"source_token_change","type":"u64"},{"name":"destination_token_change","type":"u64"},{"name":"commission_direction","type":"bool"},{"name":"total_commission_rate","type":"u32"},{"name":"parent_commission_rate","type":"u32"},{"name":"parent_commission_amount","type":"u64"},{"name":"parent_commission_account","type":"pubkey"},{"name":"child_commission_rate","type":"u32"},{"name":"child_commission_amount","type":"u64"},{"name":"child_commission_account","type":"pubkey"},{"name":"platform_fee_rate","type":"u16"},{"name":"platform_fee_amount","type":"u64"},{"name":"platform_fee_account","type":"pubkey"}]}},{"name":"SwapWithFeesCpiEvent","type":{"kind":"struct","fields":[{"name":"order_id","type":"u64"},{"name":"source_mint","type":"pubkey"},{"name":"destination_mint","type":"pubkey"},{"name":"source_token_account_owner","type":"pubkey"},{"name":"destination_token_account_owner","type":"pubkey"},{"name":"source_token_change","type":"u64"},{"name":"destination_token_change","type":"u64"},{"name":"commission_direction","type":"bool"},{"name":"commission_rate","type":"u32"},{"name":"commission_amount","type":"u64"},{"name":"commission_account","type":"pubkey"},{"name":"platform_fee_rate","type":"u16"},{"name":"platform_fee_amount","type":"u64"},{"name":"platform_fee_account","type":"pubkey"},{"name":"trim_rate","type":"u8"},{"name":"trim_amount","type":"u64"},{"name":"trim_account","type":"pubkey"}]}},{"name":"SwapWithFeesCpiEventEnhanced","type":{"kind":"struct","fields":[{"name":"order_id","type":"u64"},{"name":"source_mint","type":"pubkey"},{"name":"destination_mint","type":"pubkey"},{"name":"source_token_account_owner","type":"pubkey"},{"name":"destination_token_account_owner","type":"pubkey"},{"name":"source_token_change","type":"u64"},{"name":"destination_token_change","type":"u64"},{"name":"commission_direction","type":"bool"},{"name":"commission_rate","type":"u32"},{"name":"commission_amount","type":"u64"},{"name":"commission_account","type":"pubkey"},{"name":"platform_fee_rate","type":"u16"},{"name":"platform_fee_amount","type":"u64"},{"name":"platform_fee_account","type":"pubkey"},{"name":"trim_rate","type":"u8"},{"name":"trim_amount","type":"u64"},{"name":"trim_account","type":"pubkey"},{"name":"charge_rate","type":"u16"},{"name":"charge_amount","type":"u64"},{"name":"charge_account","type":"pubkey"}]}}],"constants":[{"name":"SEED_SA","type":"bytes","value":"[111, 107, 120, 95, 115, 97]"}]} \ No newline at end of file diff --git a/pkg/shreder/addresstables.go b/pkg/shreder/addresstables.go new file mode 100644 index 0000000..741f07b --- /dev/null +++ b/pkg/shreder/addresstables.go @@ -0,0 +1,106 @@ +package shreder + +import ( + "context" + "fmt" + "sync" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + lru "github.com/hashicorp/golang-lru/v2" + "github.com/panjf2000/ants/v2" +) + +type AddressTables struct { + rpcClient *rpc.Client + mux sync.RWMutex + loadMux sync.Mutex + tables *lru.Cache[solana.PublicKey, []solana.PublicKey] + loading map[solana.PublicKey]struct{} + + pool *ants.Pool +} + +func NewAddressTables(rpcClient *rpc.Client) *AddressTables { + pool, _ := ants.NewPool(5, ants.WithPreAlloc(true), ants.WithNonblocking(true)) + cache, _ := lru.New[solana.PublicKey, []solana.PublicKey](10000) + return &AddressTables{ + rpcClient: rpcClient, + tables: cache, + loading: make(map[solana.PublicKey]struct{}), + pool: pool, + } +} + +func (at *AddressTables) loadAddressTable(tablePubkey solana.PublicKey) ([]solana.PublicKey, error) { + // decode acc + acc, err := at.rpcClient.GetAccountInfoWithOpts(context.Background(), tablePubkey, &rpc.GetAccountInfoOpts{ + Encoding: solana.EncodingBase64, + }) + if err != nil { + return nil, err + } + data := acc.GetBinary() + if len(data) <= 56 { + return nil, fmt.Errorf("account data too short") + } + offset := 56 + var addresses solana.PublicKeySlice = make([]solana.PublicKey, 0, (len(data)-offset)/32) + for offset+32 <= len(data) { + addresses = append(addresses, solana.PublicKeyFromBytes(data[offset:offset+32])) + offset += 32 + } + // addresses = append(addresses, solana.PublicKeyFromBytes(data[start:start+32])) + return addresses, nil + +} + +func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uint8) []solana.PublicKey { + at.mux.RLock() + addresses, ok := at.tables.Get(tablePubkey) + if !ok { + at.mux.RUnlock() + _ = at.pool.Submit(func() { + at.loadMux.Lock() + _, loading := at.loading[tablePubkey] + if loading { + at.loadMux.Unlock() + return + } + at.loading[tablePubkey] = struct{}{} + at.loadMux.Unlock() + + table, err := at.loadAddressTable(tablePubkey) + if err != nil { + logger.Error("loadAddressTable failed", "err", err, "table", tablePubkey) + at.loadMux.Lock() + delete(at.loading, tablePubkey) + at.loadMux.Unlock() + return + } + at.loadMux.Lock() + delete(at.loading, tablePubkey) + at.loadMux.Unlock() + + at.mux.Lock() + at.tables.Add(tablePubkey, table) + total := at.tables.Len() + at.mux.Unlock() + logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total) + }) + + return nil + } + at.mux.RUnlock() + + var result solana.PublicKeySlice = make([]solana.PublicKey, 0, len(idx)) + for _, i := range idx { + if int(i) >= len(addresses) { + logger.Error("over loadAddressTable failed", "idx", i, "table", tablePubkey) + //todo... update table? + continue + } + result = append(result, addresses[i]) + } + return result +} diff --git a/pkg/shreder/client.go b/pkg/shreder/client.go index e0cbd8c..87a0a9a 100644 --- a/pkg/shreder/client.go +++ b/pkg/shreder/client.go @@ -2,35 +2,39 @@ package shreder import ( "context" - "log/slog" + "fmt" + "github.com/gagliardetto/solana-go/rpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) type Client struct { - log *slog.Logger - conn *grpc.ClientConn client ShrederServiceClient + tableLoader *AddressTables subscription map[string]*SubscribeRequestFilterTransactions } func NewShrederClient( url string, + rpcClient *rpc.Client, subscription map[string]*SubscribeRequestFilterTransactions, ) (*Client, func(), error) { + if rpcClient == nil { + return nil, func() {}, fmt.Errorf("rpc client is nil") + } + conn, err := grpc.NewClient(url, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { return nil, func() {}, err } - logger := slog.Default() s := &Client{ - log: logger, conn: conn, client: NewShrederServiceClient(conn), subscription: subscription, + tableLoader: NewAddressTables(rpcClient), } return s, func() { @@ -39,14 +43,14 @@ func NewShrederClient( } func (c *Client) Wait() { - c.log.Debug("waiting for shreder client to stop") + logger.Debug("waiting for shreder client to stop") err := c.conn.Close() if err != nil { - c.log.Error("failed to close connection: ", "err", err) + logger.Error("failed to close connection: ", "err", err) } - c.log.Debug("shreder client stopped") + logger.Debug("shreder client stopped") } func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error { @@ -68,7 +72,7 @@ func (c *Client) ReadSync(ctx context.Context, txCh chan<- TxSignalBatch) error return err } - txBatch := ParseTransaction(response.Transaction) + txBatch := ParseTransaction(response.Transaction, c.tableLoader) if len(txBatch) == 0 { continue } diff --git a/pkg/shreder/juptierv6-idl.json b/pkg/shreder/juptierv6-idl.json new file mode 100644 index 0000000..555ec30 --- /dev/null +++ b/pkg/shreder/juptierv6-idl.json @@ -0,0 +1,2570 @@ +{ + "address": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + "metadata": { + "name": "jupiter", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Jupiter aggregator program" + }, + "instructions": [ + { + "name": "claim", + "discriminator": [ + 62, + 198, + 214, + 193, + 213, + 159, + 108, + 210 + ], + "accounts": [ + { + "name": "wallet", + "writable": true, + "address": "7JQeyNK55fkUPUmEotupBFpiBGpgEQYLe8Ht1VdSfxcP" + }, + { + "name": "program_authority", + "writable": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "id", + "type": "u8" + } + ], + "returns": "u64" + }, + { + "name": "claim_token", + "discriminator": [ + 116, + 206, + 27, + 191, + 166, + 19, + 0, + 73 + ], + "accounts": [ + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "wallet", + "address": "7JQeyNK55fkUPUmEotupBFpiBGpgEQYLe8Ht1VdSfxcP" + }, + { + "name": "program_authority" + }, + { + "name": "program_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "wallet" + }, + { + "kind": "account", + "path": "token_program" + }, + { + "kind": "account", + "path": "mint" + } + ], + "program": { + "kind": "const", + "value": [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ] + } + } + }, + { + "name": "mint" + }, + { + "name": "token_program" + }, + { + "name": "associated_token_program", + "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "id", + "type": "u8" + } + ], + "returns": "u64" + }, + { + "name": "close_token", + "discriminator": [ + 26, + 74, + 236, + 151, + 104, + 64, + 183, + 249 + ], + "accounts": [ + { + "name": "operator", + "signer": true, + "address": "9RAufBfjGQjDfrwxeyKmZWPADHSb8HcoqCdrmpqvCr1g" + }, + { + "name": "wallet", + "writable": true, + "address": "7JQeyNK55fkUPUmEotupBFpiBGpgEQYLe8Ht1VdSfxcP" + }, + { + "name": "program_authority" + }, + { + "name": "program_token_account", + "writable": true + }, + { + "name": "mint", + "writable": true + }, + { + "name": "token_program" + } + ], + "args": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "burn_all", + "type": "bool" + } + ] + }, + { + "name": "create_token_ledger", + "discriminator": [ + 232, + 242, + 197, + 253, + 240, + 143, + 129, + 52 + ], + "accounts": [ + { + "name": "token_ledger", + "writable": true, + "signer": true + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "create_token_account", + "discriminator": [ + 147, + 241, + 123, + 100, + 244, + 132, + 174, + 118 + ], + "accounts": [ + { + "name": "token_account", + "writable": true + }, + { + "name": "user", + "writable": true, + "signer": true + }, + { + "name": "mint" + }, + { + "name": "token_program" + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [ + { + "name": "bump", + "type": "u8" + } + ] + }, + { + "name": "exact_out_route", + "discriminator": [ + 208, + 51, + 239, + 151, + 123, + 43, + 237, + 92 + ], + "accounts": [ + { + "name": "token_program" + }, + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "user_source_token_account", + "writable": true + }, + { + "name": "user_destination_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true, + "optional": true + }, + { + "name": "source_mint" + }, + { + "name": "destination_mint" + }, + { + "name": "platform_fee_account", + "writable": true, + "optional": true + }, + { + "name": "token_2022_program", + "optional": true + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStep" + } + } + } + }, + { + "name": "out_amount", + "type": "u64" + }, + { + "name": "quoted_in_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u8" + } + ], + "returns": "u64" + }, + { + "name": "route", + "discriminator": [ + 229, + 23, + 203, + 151, + 122, + 227, + 173, + 42 + ], + "accounts": [ + { + "name": "token_program" + }, + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "user_source_token_account", + "writable": true + }, + { + "name": "user_destination_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true, + "optional": true + }, + { + "name": "destination_mint" + }, + { + "name": "platform_fee_account", + "writable": true, + "optional": true + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStep" + } + } + } + }, + { + "name": "in_amount", + "type": "u64" + }, + { + "name": "quoted_out_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u8" + } + ], + "returns": "u64" + }, + { + "name": "route_with_token_ledger", + "discriminator": [ + 150, + 86, + 71, + 116, + 167, + 93, + 14, + 104 + ], + "accounts": [ + { + "name": "token_program" + }, + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "user_source_token_account", + "writable": true + }, + { + "name": "user_destination_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true, + "optional": true + }, + { + "name": "destination_mint" + }, + { + "name": "platform_fee_account", + "writable": true, + "optional": true + }, + { + "name": "token_ledger" + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStep" + } + } + } + }, + { + "name": "quoted_out_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u8" + } + ], + "returns": "u64" + }, + { + "name": "set_token_ledger", + "discriminator": [ + 228, + 85, + 185, + 112, + 78, + 79, + 77, + 2 + ], + "accounts": [ + { + "name": "token_ledger", + "writable": true + }, + { + "name": "token_account" + } + ], + "args": [] + }, + { + "name": "shared_accounts_exact_out_route", + "discriminator": [ + 176, + 209, + 105, + 168, + 154, + 125, + 69, + 62 + ], + "accounts": [ + { + "name": "token_program" + }, + { + "name": "program_authority" + }, + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "source_token_account", + "writable": true + }, + { + "name": "program_source_token_account", + "writable": true + }, + { + "name": "program_destination_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true + }, + { + "name": "source_mint" + }, + { + "name": "destination_mint" + }, + { + "name": "platform_fee_account", + "writable": true, + "optional": true + }, + { + "name": "token_2022_program", + "optional": true + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStep" + } + } + } + }, + { + "name": "out_amount", + "type": "u64" + }, + { + "name": "quoted_in_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u8" + } + ], + "returns": "u64" + }, + { + "name": "shared_accounts_route", + "discriminator": [ + 193, + 32, + 155, + 51, + 65, + 214, + 156, + 129 + ], + "accounts": [ + { + "name": "token_program" + }, + { + "name": "program_authority" + }, + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "source_token_account", + "writable": true + }, + { + "name": "program_source_token_account", + "writable": true + }, + { + "name": "program_destination_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true + }, + { + "name": "source_mint" + }, + { + "name": "destination_mint" + }, + { + "name": "platform_fee_account", + "writable": true, + "optional": true + }, + { + "name": "token_2022_program", + "optional": true + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStep" + } + } + } + }, + { + "name": "in_amount", + "type": "u64" + }, + { + "name": "quoted_out_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u8" + } + ], + "returns": "u64" + }, + { + "name": "shared_accounts_route_with_token_ledger", + "discriminator": [ + 230, + 121, + 143, + 80, + 119, + 159, + 106, + 170 + ], + "accounts": [ + { + "name": "token_program" + }, + { + "name": "program_authority" + }, + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "source_token_account", + "writable": true + }, + { + "name": "program_source_token_account", + "writable": true + }, + { + "name": "program_destination_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true + }, + { + "name": "source_mint" + }, + { + "name": "destination_mint" + }, + { + "name": "platform_fee_account", + "writable": true, + "optional": true + }, + { + "name": "token_2022_program", + "optional": true + }, + { + "name": "token_ledger" + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStep" + } + } + } + }, + { + "name": "quoted_out_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u8" + } + ], + "returns": "u64" + }, + { + "name": "exact_out_route_v2", + "discriminator": [ + 157, + 138, + 184, + 82, + 21, + 244, + 243, + 36 + ], + "accounts": [ + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "user_source_token_account", + "writable": true + }, + { + "name": "user_destination_token_account", + "writable": true + }, + { + "name": "source_mint" + }, + { + "name": "destination_mint" + }, + { + "name": "source_token_program" + }, + { + "name": "destination_token_program" + }, + { + "name": "destination_token_account", + "writable": true, + "optional": true + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "out_amount", + "type": "u64" + }, + { + "name": "quoted_in_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u16" + }, + { + "name": "positive_slippage_bps", + "type": "u16" + }, + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStepV2" + } + } + } + } + ], + "returns": "u64" + }, + { + "name": "route_v2", + "discriminator": [ + 187, + 100, + 250, + 204, + 49, + 196, + 175, + 20 + ], + "accounts": [ + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "user_source_token_account", + "writable": true + }, + { + "name": "user_destination_token_account", + "writable": true + }, + { + "name": "source_mint" + }, + { + "name": "destination_mint" + }, + { + "name": "source_token_program" + }, + { + "name": "destination_token_program" + }, + { + "name": "destination_token_account", + "writable": true, + "optional": true + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "in_amount", + "type": "u64" + }, + { + "name": "quoted_out_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u16" + }, + { + "name": "positive_slippage_bps", + "type": "u16" + }, + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStepV2" + } + } + } + } + ], + "returns": "u64" + }, + { + "name": "shared_accounts_exact_out_route_v2", + "discriminator": [ + 53, + 96, + 229, + 202, + 216, + 187, + 250, + 24 + ], + "accounts": [ + { + "name": "program_authority" + }, + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "source_token_account", + "writable": true + }, + { + "name": "program_source_token_account", + "writable": true + }, + { + "name": "program_destination_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true + }, + { + "name": "source_mint" + }, + { + "name": "destination_mint" + }, + { + "name": "source_token_program" + }, + { + "name": "destination_token_program" + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "out_amount", + "type": "u64" + }, + { + "name": "quoted_in_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u16" + }, + { + "name": "positive_slippage_bps", + "type": "u16" + }, + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStepV2" + } + } + } + } + ], + "returns": "u64" + }, + { + "name": "shared_accounts_route_v2", + "discriminator": [ + 209, + 152, + 83, + 147, + 124, + 254, + 216, + 233 + ], + "accounts": [ + { + "name": "program_authority" + }, + { + "name": "user_transfer_authority", + "signer": true + }, + { + "name": "source_token_account", + "writable": true + }, + { + "name": "program_source_token_account", + "writable": true + }, + { + "name": "program_destination_token_account", + "writable": true + }, + { + "name": "destination_token_account", + "writable": true + }, + { + "name": "source_mint" + }, + { + "name": "destination_mint" + }, + { + "name": "source_token_program" + }, + { + "name": "destination_token_program" + }, + { + "name": "event_authority", + "address": "D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf" + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "id", + "type": "u8" + }, + { + "name": "in_amount", + "type": "u64" + }, + { + "name": "quoted_out_amount", + "type": "u64" + }, + { + "name": "slippage_bps", + "type": "u16" + }, + { + "name": "platform_fee_bps", + "type": "u16" + }, + { + "name": "positive_slippage_bps", + "type": "u16" + }, + { + "name": "route_plan", + "type": { + "vec": { + "defined": { + "name": "RoutePlanStepV2" + } + } + } + } + ], + "returns": "u64" + } + ], + "accounts": [ + { + "name": "TokenLedger", + "discriminator": [ + 156, + 247, + 9, + 188, + 54, + 108, + 85, + 77 + ] + } + ], + "events": [ + { + "name": "FeeEvent", + "discriminator": [ + 73, + 79, + 78, + 127, + 184, + 213, + 13, + 220 + ] + }, + { + "name": "SwapEvent", + "discriminator": [ + 64, + 198, + 205, + 232, + 38, + 8, + 113, + 226 + ] + }, + { + "name": "SwapsEvent", + "discriminator": [ + 152, + 47, + 78, + 235, + 192, + 96, + 110, + 106 + ] + }, + { + "name": "CandidateSwapResults", + "discriminator": [ + 45, + 9, + 244, + 30, + 229, + 52, + 168, + 123 + ] + }, + { + "name": "BestSwapOutAmountViolation", + "discriminator": [ + 124, + 66, + 196, + 51, + 218, + 173, + 46, + 93 + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "EmptyRoute", + "msg": "Empty route" + }, + { + "code": 6001, + "name": "SlippageToleranceExceeded", + "msg": "Slippage tolerance exceeded" + }, + { + "code": 6002, + "name": "InvalidCalculation", + "msg": "Invalid calculation" + }, + { + "code": 6003, + "name": "MissingPlatformFeeAccount", + "msg": "Missing platform fee account" + }, + { + "code": 6004, + "name": "InvalidSlippage", + "msg": "Invalid slippage" + }, + { + "code": 6005, + "name": "NotEnoughPercent", + "msg": "Not enough percent to 100" + }, + { + "code": 6006, + "name": "InvalidInputIndex", + "msg": "Token input index is invalid" + }, + { + "code": 6007, + "name": "InvalidOutputIndex", + "msg": "Token output index is invalid" + }, + { + "code": 6008, + "name": "NotEnoughAccountKeys", + "msg": "Not Enough Account keys" + }, + { + "code": 6009, + "name": "NonZeroMinimumOutAmountNotSupported", + "msg": "Non zero minimum out amount not supported" + }, + { + "code": 6010, + "name": "InvalidRoutePlan", + "msg": "Invalid route plan" + }, + { + "code": 6011, + "name": "InvalidReferralAuthority", + "msg": "Invalid referral authority" + }, + { + "code": 6012, + "name": "LedgerTokenAccountDoesNotMatch", + "msg": "Token account doesn't match the ledger" + }, + { + "code": 6013, + "name": "InvalidTokenLedger", + "msg": "Invalid token ledger" + }, + { + "code": 6014, + "name": "IncorrectTokenProgramID", + "msg": "Token program ID is invalid" + }, + { + "code": 6015, + "name": "TokenProgramNotProvided", + "msg": "Token program not provided" + }, + { + "code": 6016, + "name": "SwapNotSupported", + "msg": "Swap not supported" + }, + { + "code": 6017, + "name": "ExactOutAmountNotMatched", + "msg": "Exact out amount doesn't match" + }, + { + "code": 6018, + "name": "SourceAndDestinationMintCannotBeTheSame", + "msg": "Source mint and destination mint cannot the same" + }, + { + "code": 6019, + "name": "InvalidMint", + "msg": "Invalid mint" + }, + { + "code": 6020, + "name": "InvalidProgramAuthority", + "msg": "Invalid program authority" + }, + { + "code": 6021, + "name": "InvalidOutputTokenAccount", + "msg": "Invalid output token account" + }, + { + "code": 6022, + "name": "InvalidFeeWallet", + "msg": "Invalid fee wallet" + }, + { + "code": 6023, + "name": "InvalidAuthority", + "msg": "Invalid authority" + }, + { + "code": 6024, + "name": "InsufficientFunds", + "msg": "Insufficient funds" + }, + { + "code": 6025, + "name": "InvalidTokenAccount", + "msg": "Invalid token account" + }, + { + "code": 6026, + "name": "BondingCurveAlreadyCompleted", + "msg": "Bonding curve already completed" + } + ], + "types": [ + { + "name": "FeeEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "account", + "type": "pubkey" + }, + { + "name": "mint", + "type": "pubkey" + }, + { + "name": "amount", + "type": "u64" + } + ] + } + }, + { + "name": "RemainingAccountsInfo", + "type": { + "kind": "struct", + "fields": [ + { + "name": "slices", + "type": { + "vec": { + "defined": { + "name": "RemainingAccountsSlice" + } + } + } + } + ] + } + }, + { + "name": "RemainingAccountsSlice", + "type": { + "kind": "struct", + "fields": [ + { + "name": "accounts_type", + "type": "u8" + }, + { + "name": "length", + "type": "u8" + } + ] + } + }, + { + "name": "AccountsType", + "type": { + "kind": "enum", + "variants": [ + { + "name": "TransferHookA" + }, + { + "name": "TransferHookB" + }, + { + "name": "TransferHookReward" + }, + { + "name": "TransferHookInput" + }, + { + "name": "TransferHookIntermediate" + }, + { + "name": "TransferHookOutput" + }, + { + "name": "SupplementalTickArrays" + }, + { + "name": "SupplementalTickArraysOne" + }, + { + "name": "SupplementalTickArraysTwo" + } + ] + } + }, + { + "name": "DefiTunaAccountsType", + "type": { + "kind": "enum", + "variants": [ + { + "name": "TransferHookA" + }, + { + "name": "TransferHookB" + }, + { + "name": "TransferHookInput" + }, + { + "name": "TransferHookIntermediate" + }, + { + "name": "TransferHookOutput" + }, + { + "name": "SupplementalTickArrays" + }, + { + "name": "SupplementalTickArraysOne" + }, + { + "name": "SupplementalTickArraysTwo" + } + ] + } + }, + { + "name": "RoutePlanStep", + "type": { + "kind": "struct", + "fields": [ + { + "name": "swap", + "type": { + "defined": { + "name": "Swap" + } + } + }, + { + "name": "percent", + "type": "u8" + }, + { + "name": "input_index", + "type": "u8" + }, + { + "name": "output_index", + "type": "u8" + } + ] + } + }, + { + "name": "RoutePlanStepV2", + "type": { + "kind": "struct", + "fields": [ + { + "name": "swap", + "type": { + "defined": { + "name": "Swap" + } + } + }, + { + "name": "bps", + "type": "u16" + }, + { + "name": "input_index", + "type": "u8" + }, + { + "name": "output_index", + "type": "u8" + } + ] + } + }, + { + "name": "Side", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Bid" + }, + { + "name": "Ask" + } + ] + } + }, + { + "name": "Swap", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Saber" + }, + { + "name": "SaberAddDecimalsDeposit" + }, + { + "name": "SaberAddDecimalsWithdraw" + }, + { + "name": "TokenSwap" + }, + { + "name": "Sencha" + }, + { + "name": "Step" + }, + { + "name": "Cropper" + }, + { + "name": "Raydium" + }, + { + "name": "Crema", + "fields": [ + { + "name": "a_to_b", + "type": "bool" + } + ] + }, + { + "name": "Lifinity" + }, + { + "name": "Mercurial" + }, + { + "name": "Cykura" + }, + { + "name": "Serum", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "MarinadeDeposit" + }, + { + "name": "MarinadeUnstake" + }, + { + "name": "Aldrin", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "AldrinV2", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "Whirlpool", + "fields": [ + { + "name": "a_to_b", + "type": "bool" + } + ] + }, + { + "name": "Invariant", + "fields": [ + { + "name": "x_to_y", + "type": "bool" + } + ] + }, + { + "name": "Meteora" + }, + { + "name": "GooseFX" + }, + { + "name": "DeltaFi", + "fields": [ + { + "name": "stable", + "type": "bool" + } + ] + }, + { + "name": "Balansol" + }, + { + "name": "MarcoPolo", + "fields": [ + { + "name": "x_to_y", + "type": "bool" + } + ] + }, + { + "name": "Dradex", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "LifinityV2" + }, + { + "name": "RaydiumClmm" + }, + { + "name": "Openbook", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "Phoenix", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "Symmetry", + "fields": [ + { + "name": "from_token_id", + "type": "u64" + }, + { + "name": "to_token_id", + "type": "u64" + } + ] + }, + { + "name": "TokenSwapV2" + }, + { + "name": "HeliumTreasuryManagementRedeemV0" + }, + { + "name": "StakeDexStakeWrappedSol" + }, + { + "name": "StakeDexSwapViaStake", + "fields": [ + { + "name": "bridge_stake_seed", + "type": "u32" + } + ] + }, + { + "name": "GooseFXV2" + }, + { + "name": "Perps" + }, + { + "name": "PerpsAddLiquidity" + }, + { + "name": "PerpsRemoveLiquidity" + }, + { + "name": "MeteoraDlmm" + }, + { + "name": "OpenBookV2", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "RaydiumClmmV2" + }, + { + "name": "StakeDexPrefundWithdrawStakeAndDepositStake", + "fields": [ + { + "name": "bridge_stake_seed", + "type": "u32" + } + ] + }, + { + "name": "Clone", + "fields": [ + { + "name": "pool_index", + "type": "u8" + }, + { + "name": "quantity_is_input", + "type": "bool" + }, + { + "name": "quantity_is_collateral", + "type": "bool" + } + ] + }, + { + "name": "SanctumS", + "fields": [ + { + "name": "src_lst_value_calc_accs", + "type": "u8" + }, + { + "name": "dst_lst_value_calc_accs", + "type": "u8" + }, + { + "name": "src_lst_index", + "type": "u32" + }, + { + "name": "dst_lst_index", + "type": "u32" + } + ] + }, + { + "name": "SanctumSAddLiquidity", + "fields": [ + { + "name": "lst_value_calc_accs", + "type": "u8" + }, + { + "name": "lst_index", + "type": "u32" + } + ] + }, + { + "name": "SanctumSRemoveLiquidity", + "fields": [ + { + "name": "lst_value_calc_accs", + "type": "u8" + }, + { + "name": "lst_index", + "type": "u32" + } + ] + }, + { + "name": "RaydiumCP" + }, + { + "name": "WhirlpoolSwapV2", + "fields": [ + { + "name": "a_to_b", + "type": "bool" + }, + { + "name": "remaining_accounts_info", + "type": { + "option": { + "defined": { + "name": "RemainingAccountsInfo" + } + } + } + } + ] + }, + { + "name": "OneIntro" + }, + { + "name": "PumpWrappedBuy" + }, + { + "name": "PumpWrappedSell" + }, + { + "name": "PerpsV2" + }, + { + "name": "PerpsV2AddLiquidity" + }, + { + "name": "PerpsV2RemoveLiquidity" + }, + { + "name": "MoonshotWrappedBuy" + }, + { + "name": "MoonshotWrappedSell" + }, + { + "name": "StabbleStableSwap" + }, + { + "name": "StabbleWeightedSwap" + }, + { + "name": "Obric", + "fields": [ + { + "name": "x_to_y", + "type": "bool" + } + ] + }, + { + "name": "FoxBuyFromEstimatedCost" + }, + { + "name": "FoxClaimPartial", + "fields": [ + { + "name": "is_y", + "type": "bool" + } + ] + }, + { + "name": "SolFi", + "fields": [ + { + "name": "is_quote_to_base", + "type": "bool" + } + ] + }, + { + "name": "SolayerDelegateNoInit" + }, + { + "name": "SolayerUndelegateNoInit" + }, + { + "name": "TokenMill", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "DaosFunBuy" + }, + { + "name": "DaosFunSell" + }, + { + "name": "ZeroFi" + }, + { + "name": "StakeDexWithdrawWrappedSol" + }, + { + "name": "VirtualsBuy" + }, + { + "name": "VirtualsSell" + }, + { + "name": "Perena", + "fields": [ + { + "name": "in_index", + "type": "u8" + }, + { + "name": "out_index", + "type": "u8" + } + ] + }, + { + "name": "PumpSwapBuy" + }, + { + "name": "PumpSwapSell" + }, + { + "name": "Gamma" + }, + { + "name": "MeteoraDlmmSwapV2", + "fields": [ + { + "name": "remaining_accounts_info", + "type": { + "defined": { + "name": "RemainingAccountsInfo" + } + } + } + ] + }, + { + "name": "Woofi" + }, + { + "name": "MeteoraDammV2" + }, + { + "name": "MeteoraDynamicBondingCurveSwap" + }, + { + "name": "StabbleStableSwapV2" + }, + { + "name": "StabbleWeightedSwapV2" + }, + { + "name": "RaydiumLaunchlabBuy", + "fields": [ + { + "name": "share_fee_rate", + "type": "u64" + } + ] + }, + { + "name": "RaydiumLaunchlabSell", + "fields": [ + { + "name": "share_fee_rate", + "type": "u64" + } + ] + }, + { + "name": "BoopdotfunWrappedBuy" + }, + { + "name": "BoopdotfunWrappedSell" + }, + { + "name": "Plasma", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "GoonFi", + "fields": [ + { + "name": "is_bid", + "type": "bool" + }, + { + "name": "blacklist_bump", + "type": "u8" + } + ] + }, + { + "name": "HumidiFi", + "fields": [ + { + "name": "swap_id", + "type": "u64" + }, + { + "name": "is_base_to_quote", + "type": "bool" + } + ] + }, + { + "name": "MeteoraDynamicBondingCurveSwapWithRemainingAccounts" + }, + { + "name": "TesseraV", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "PumpWrappedBuyV2" + }, + { + "name": "PumpWrappedSellV2" + }, + { + "name": "PumpSwapBuyV2" + }, + { + "name": "PumpSwapSellV2" + }, + { + "name": "Heaven", + "fields": [ + { + "name": "a_to_b", + "type": "bool" + } + ] + }, + { + "name": "SolFiV2", + "fields": [ + { + "name": "is_quote_to_base", + "type": "bool" + } + ] + }, + { + "name": "Aquifer" + }, + { + "name": "PumpWrappedBuyV3" + }, + { + "name": "PumpWrappedSellV3" + }, + { + "name": "PumpSwapBuyV3" + }, + { + "name": "PumpSwapSellV3" + }, + { + "name": "JupiterLendDeposit" + }, + { + "name": "JupiterLendRedeem" + }, + { + "name": "DefiTuna", + "fields": [ + { + "name": "a_to_b", + "type": "bool" + }, + { + "name": "remaining_accounts_info", + "type": { + "option": { + "defined": { + "name": "RemainingAccountsInfo" + } + } + } + } + ] + }, + { + "name": "AlphaQ", + "fields": [ + { + "name": "a_to_b", + "type": "bool" + } + ] + }, + { + "name": "RaydiumV2" + }, + { + "name": "SarosDlmm", + "fields": [ + { + "name": "swap_for_y", + "type": "bool" + } + ] + }, + { + "name": "Futarchy", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "MeteoraDammV2WithRemainingAccounts" + }, + { + "name": "Obsidian" + }, + { + "name": "WhaleStreet", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "DynamicV1", + "fields": [ + { + "name": "candidate_swaps", + "type": { + "vec": { + "defined": { + "name": "CandidateSwap" + } + } + } + } + ] + }, + { + "name": "PumpWrappedBuyV4" + }, + { + "name": "PumpWrappedSellV4" + }, + { + "name": "CarrotIssue" + }, + { + "name": "CarrotRedeem" + }, + { + "name": "Manifest", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "BisonFi", + "fields": [ + { + "name": "a_to_b", + "type": "bool" + } + ] + }, + { + "name": "HumidiFiV2", + "fields": [ + { + "name": "swap_id", + "type": "u64" + }, + { + "name": "is_base_to_quote", + "type": "bool" + } + ] + }, + { + "name": "PerenaStar", + "fields": [ + { + "name": "is_mint", + "type": "bool" + } + ] + }, + { + "name": "JupiterRfqV2", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + }, + { + "name": "fill_data", + "type": "bytes" + } + ] + }, + { + "name": "GoonFiV2", + "fields": [ + { + "name": "is_bid", + "type": "bool" + } + ] + } + ] + } + }, + { + "name": "CandidateSwap", + "type": { + "kind": "enum", + "variants": [ + { + "name": "HumidiFi", + "fields": [ + { + "name": "swap_id", + "type": "u64" + }, + { + "name": "is_base_to_quote", + "type": "bool" + } + ] + }, + { + "name": "TesseraV", + "fields": [ + { + "name": "side", + "type": { + "defined": { + "name": "Side" + } + } + } + ] + }, + { + "name": "HumidiFiV2", + "fields": [ + { + "name": "swap_id", + "type": "u64" + }, + { + "name": "is_base_to_quote", + "type": "bool" + } + ] + } + ] + } + }, + { + "name": "SwapEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amm", + "type": "pubkey" + }, + { + "name": "input_mint", + "type": "pubkey" + }, + { + "name": "input_amount", + "type": "u64" + }, + { + "name": "output_mint", + "type": "pubkey" + }, + { + "name": "output_amount", + "type": "u64" + } + ] + } + }, + { + "name": "SwapEventV2", + "type": { + "kind": "struct", + "fields": [ + { + "name": "input_mint", + "type": "pubkey" + }, + { + "name": "input_amount", + "type": "u64" + }, + { + "name": "output_mint", + "type": "pubkey" + }, + { + "name": "output_amount", + "type": "u64" + } + ] + } + }, + { + "name": "SwapsEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "swap_events", + "type": { + "vec": { + "defined": { + "name": "SwapEventV2" + } + } + } + } + ] + } + }, + { + "name": "TokenLedger", + "type": { + "kind": "struct", + "fields": [ + { + "name": "token_account", + "type": "pubkey" + }, + { + "name": "amount", + "type": "u64" + } + ] + } + }, + { + "name": "BestSwapOutAmountViolation", + "type": { + "kind": "struct", + "fields": [ + { + "name": "expected_out_amount", + "type": "u64" + }, + { + "name": "out_amount", + "type": "u64" + } + ] + } + }, + { + "name": "CandidateSwapResult", + "type": { + "kind": "enum", + "variants": [ + { + "name": "OutAmount", + "fields": [ + "u64" + ] + }, + { + "name": "ProgramError", + "fields": [ + "u64" + ] + } + ] + } + }, + { + "name": "CandidateSwapResults", + "type": { + "kind": "struct", + "fields": [ + { + "name": "results", + "type": { + "vec": { + "defined": { + "name": "CandidateSwapResult" + } + } + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/pkg/shreder/juptierv6.go b/pkg/shreder/juptierv6.go new file mode 100644 index 0000000..8ca669a --- /dev/null +++ b/pkg/shreder/juptierv6.go @@ -0,0 +1,1068 @@ +package shreder + +import ( + "bytes" + "encoding/binary" + "fmt" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/shopspring/decimal" +) + +var ( + jupiterRouteV2 = []byte{187, 100, 250, 204, 49, 196, 175, 20} + jupiterExactOutRouteV2 = []byte{157, 138, 184, 82, 21, 244, 243, 36} + + jupiterRoute = []byte{229, 23, 203, 151, 122, 227, 173, 42} + jupiterRouteWithTokenLedger = []byte{150, 86, 71, 116, 167, 93, 14, 104} + jupiterSharedAccountsExactOutRoute = []byte{176, 209, 105, 168, 154, 125, 69, 62} + jupiterSharedAccountsRoute = []byte{193, 32, 155, 51, 65, 214, 156, 129} + jupiterSharedAccountsRouteWithTokenLedger = []byte{230, 121, 143, 80, 119, 159, 106, 170} + + jupiterSharedAccountsExactOutRouteV2 = []byte{53, 96, 229, 202, 216, 187, 250, 24} + jupiterSharedAccountsRouteV2 = []byte{209, 152, 83, 147, 124, 254, 216, 233} +) + +type Side uint8 + +type SwapKind uint8 + +const ( + Saber SwapKind = iota + SaberAddDecimalsDeposit + SaberAddDecimalsWithdraw + TokenSwap + Sencha + Step + Cropper + Raydium + Crema + Lifinity + Mercurial + Cykura + Serum + MarinadeDeposit + MarinadeUnstake + Aldrin + AldrinV2 + Whirlpool + Invariant + Meteora + GooseFX + DeltaFi + Balansol + MarcoPolo + Dradex + LifinityV2 + RaydiumClmm + Openbook + Phoenix + Symmetry + TokenSwapV2 + HeliumTreasuryManagementRedeemV0 + StakeDexStakeWrappedSol + StakeDexSwapViaStake + GooseFXV2 + Perps + PerpsAddLiquidity + PerpsRemoveLiquidity + MeteoraDlmm + OpenBookV2 + RaydiumClmmV2 + StakeDexPrefundWithdrawStakeAndDepositStake + Clone + SanctumS + SanctumSAddLiquidity + SanctumSRemoveLiquidity + RaydiumCP + WhirlpoolSwapV2 + OneIntro + PumpWrappedBuy + PumpWrappedSell + PerpsV2 + PerpsV2AddLiquidity + PerpsV2RemoveLiquidity + MoonshotWrappedBuy + MoonshotWrappedSell + StabbleStableSwap + StabbleWeightedSwap + Obric + FoxBuyFromEstimatedCost + FoxClaimPartial + SolFi + SolayerDelegateNoInit + SolayerUndelegateNoInit + TokenMill + DaosFunBuy + DaosFunSell + ZeroFi + StakeDexWithdrawWrappedSol + VirtualsBuy + VirtualsSell + Perena + PumpSwapBuy + PumpSwapSell + Gamma + MeteoraDlmmSwapV2 + Woofi + MeteoraDammV2 + MeteoraDynamicBondingCurveSwap + StabbleStableSwapV2 + StabbleWeightedSwapV2 + RaydiumLaunchlabBuy + RaydiumLaunchlabSell + BoopdotfunWrappedBuy + BoopdotfunWrappedSell + Plasma + GoonFi + HumidiFi + MeteoraDynamicBondingCurveSwapWithRemainingAccounts + TesseraV + PumpWrappedBuyV2 + PumpWrappedSellV2 + PumpSwapBuyV2 + PumpSwapSellV2 + Heaven + SolFiV2 + Aquifer + PumpWrappedBuyV3 + PumpWrappedSellV3 + PumpSwapBuyV3 + PumpSwapSellV3 + JupiterLendDeposit + JupiterLendRedeem + DefiTuna + AlphaQ + RaydiumV2 + SarosDlmm + Futarchy + MeteoraDammV2WithRemainingAccounts + Obsidian + WhaleStreet + DynamicV1 + PumpWrappedBuyV4 + PumpWrappedSellV4 + CarrotIssue + CarrotRedeem + Manifest + BisonFi + HumidiFiV2 + PerenaStar + JupiterRfqV2 + GoonFiV2 +) + +var swapKindNames = [122]string{"Saber", "SaberAddDecimalsDeposit", "SaberAddDecimalsWithdraw", "TokenSwap", "Sencha", "Step", "Cropper", + "Raydium", "Crema", "Lifinity", "Mercurial", "Cykura", "Serum", "MarinadeDeposit", "MarinadeUnstake", "Aldrin", "AldrinV2", "Whirlpool", + "Invariant", "Meteora", "GooseFX", "DeltaFi", "Balansol", "MarcoPolo", "Dradex", "LifinityV2", "RaydiumClmm", "Openbook", "Phoenix", + "Symmetry", "TokenSwapV2", "HeliumTreasuryManagementRedeemV0", "StakeDexStakeWrappedSol", "StakeDexSwapViaStake", "GooseFXV2", "Perps", + "PerpsAddLiquidity", "PerpsRemoveLiquidity", "MeteoraDlmm", "OpenBookV2", "RaydiumClmmV2", "StakeDexPrefundWithdrawStakeAndDepositStake", + "Clone", "SanctumS", "SanctumSAddLiquidity", "SanctumSRemoveLiquidity", "RaydiumCP", "WhirlpoolSwapV2", "OneIntro", "PumpWrappedBuy", + "PumpWrappedSell", "PerpsV2", "PerpsV2AddLiquidity", "PerpsV2RemoveLiquidity", "MoonshotWrappedBuy", "MoonshotWrappedSell", "StabbleStableSwap", + "StabbleWeightedSwap", "Obric", "FoxBuyFromEstimatedCost", "FoxClaimPartial", "SolFi", "SolayerDelegateNoInit", "SolayerUndelegateNoInit", "TokenMill", + "DaosFunBuy", "DaosFunSell", "ZeroFi", "StakeDexWithdrawWrappedSol", "VirtualsBuy", "VirtualsSell", "Perena", "PumpSwapBuy", "PumpSwapSell", "Gamma", + "MeteoraDlmmSwapV2", "Woofi", "MeteoraDammV2", "MeteoraDynamicBondingCurveSwap", "StabbleStableSwapV2", "StabbleWeightedSwapV2", "RaydiumLaunchlabBuy", + "RaydiumLaunchlabSell", "BoopdotfunWrappedBuy", "BoopdotfunWrappedSell", "Plasma", "GoonFi", "HumidiFi", + "MeteoraDynamicBondingCurveSwapWithRemainingAccounts", "TesseraV", "PumpWrappedBuyV2", "PumpWrappedSellV2", "PumpSwapBuyV2", "PumpSwapSellV2", + "Heaven", "SolFiV2", "Aquifer", "PumpWrappedBuyV3", "PumpWrappedSellV3", "PumpSwapBuyV3", "PumpSwapSellV3", "JupiterLendDeposit", "JupiterLendRedeem", + "DefiTuna", "AlphaQ", "RaydiumV2", "SarosDlmm", "Futarchy", "MeteoraDammV2WithRemainingAccounts", "Obsidian", "WhaleStreet", "DynamicV1", + "PumpWrappedBuyV4", "PumpWrappedSellV4", "CarrotIssue", "CarrotRedeem", "Manifest", "BisonFi", "HumidiFiV2", "PerenaStar", "JupiterRfqV2", "GoonFiV2"} + +func (s SwapKind) String() string { + idx := int(s) + if idx < 0 || idx >= len(swapKindNames) { + return fmt.Sprintf("SwapKind(%d)", uint8(s)) + } + return swapKindNames[idx] +} + +type Swap struct { + Kind SwapKind +} + +type RoutePlanStepV2 struct { + Swap Swap + Bps uint16 + + InputIdx uint8 + OutputIdx uint8 +} + +type RoutePlanStep struct { + Swap Swap + Percent uint8 + + InputIdx uint8 + OutputIdx uint8 +} + +type JupiterV6RouteV2Arg struct { + In uint64 + Out uint64 + + SlippageBps uint16 + PlatformFeeBps uint16 + PositiveSlippageBps uint16 + + Plan []RoutePlanStepV2 +} + +func skipRemainingAccountsInfo(dec *bin.Decoder) error { + // RemainingAccountsInfo { slices: Vec } + // RemainingAccountsSlice { accounts_type: u8, length: u8 } + ln, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return err + } + // each slice is 2 bytes + return dec.SkipBytes(uint(ln) * 2) +} + +func skipOptionRemainingAccountsInfo(dec *bin.Decoder) error { + pos := dec.Position() + tag, err := dec.ReadUint8() + if err != nil { + return err + } + switch tag { + case 0: + return nil + case 1: + return skipRemainingAccountsInfo(dec) + default: + // Version drift: sometimes a swap variant we think has Option is actually no-payload. + _ = dec.SetPosition(pos) + return nil + } +} + +func skipCandidateSwap(dec *bin.Decoder) error { + // CandidateSwap enum (this IDL variant): + // 0 HumidiFi { u64, bool } + // 1 TesseraV { Side(u8) } + // NOTE: other IDL versions may include more variants (e.g. HumidiFiV2). + tag, err := dec.ReadUint8() + if err != nil { + return err + } + switch tag { + case 0: + // HumidiFi u64 + bool + if err := dec.SkipBytes(8); err != nil { + return err + } + return dec.SkipBytes(1) + case 1: + // TesseraV (Side: u8) + return dec.SkipBytes(1) + case 2: + // Seen in other IDLs: HumidiFiV2 { u64, bool } + if err := dec.SkipBytes(8); err != nil { + return err + } + return dec.SkipBytes(1) + default: + return fmt.Errorf("unknown CandidateSwap variant: %d", tag) + } +} + +func skipDynamicV1(dec *bin.Decoder) error { + // DynamicV1 { candidate_swaps: Vec } + ln, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return err + } + for i := uint32(0); i < ln; i++ { + if err := skipCandidateSwap(dec); err != nil { + return fmt.Errorf("CandidateSwap[%d]: %w", i, err) + } + } + return nil +} + +func decodeSwap(dec *bin.Decoder) (Swap, error) { + tag, err := dec.ReadUint8() + if err != nil { + return Swap{}, fmt.Errorf("read Swap variant: %w", err) + } + k := SwapKind(tag) + out := Swap{Kind: k} + + skipU8 := func() error { return dec.SkipBytes(1) } + skipBool := func() error { return dec.SkipBytes(1) } + skipU32 := func() error { return dec.SkipBytes(4) } + skipU64 := func() error { return dec.SkipBytes(8) } + skipTwoU64 := func() error { return dec.SkipBytes(16) } + skipRemaining := func() error { return skipRemainingAccountsInfo(dec) } + skipOptRemaining := func() error { return skipOptionRemainingAccountsInfo(dec) } + + switch k { + // -------- payload-less variants -------- + case Saber, SaberAddDecimalsDeposit, SaberAddDecimalsWithdraw, TokenSwap, Sencha, Step, Cropper, Raydium, Lifinity, + Mercurial, Cykura, MarinadeDeposit, MarinadeUnstake, Meteora, GooseFX, Balansol, LifinityV2, RaydiumClmm, + TokenSwapV2, HeliumTreasuryManagementRedeemV0, StakeDexStakeWrappedSol, GooseFXV2, Perps, PerpsAddLiquidity, + PerpsRemoveLiquidity, MeteoraDlmm, RaydiumClmmV2, RaydiumCP, OneIntro, PumpWrappedBuy, PumpWrappedSell, PerpsV2, + PerpsV2AddLiquidity, PerpsV2RemoveLiquidity, MoonshotWrappedBuy, MoonshotWrappedSell, StabbleStableSwap, + StabbleWeightedSwap, FoxBuyFromEstimatedCost, SolayerDelegateNoInit, SolayerUndelegateNoInit, DaosFunBuy, + DaosFunSell, ZeroFi, StakeDexWithdrawWrappedSol, VirtualsBuy, VirtualsSell, PumpSwapBuy, PumpSwapSell, + Gamma, Woofi, MeteoraDammV2, MeteoraDynamicBondingCurveSwap, StabbleStableSwapV2, StabbleWeightedSwapV2, + BoopdotfunWrappedBuy, BoopdotfunWrappedSell, MeteoraDynamicBondingCurveSwapWithRemainingAccounts, + PumpWrappedBuyV2, PumpWrappedSellV2, PumpSwapBuyV2, PumpSwapSellV2, Aquifer, PumpWrappedBuyV3, PumpWrappedSellV3, + PumpSwapBuyV3, PumpSwapSellV3, JupiterLendDeposit, JupiterLendRedeem, RaydiumV2, + MeteoraDammV2WithRemainingAccounts, Obsidian, PumpWrappedBuyV4, PumpWrappedSellV4, CarrotIssue, CarrotRedeem: + return out, nil + + // -------- bool payload -------- + case Crema, Whirlpool, Invariant, DeltaFi, MarcoPolo, Obric, FoxClaimPartial, SolFi, Heaven, SolFiV2, AlphaQ, + SarosDlmm, BisonFi, PerenaStar, GoonFiV2: + return out, skipBool() + // -------- u32 -------- + case StakeDexSwapViaStake, StakeDexPrefundWithdrawStakeAndDepositStake: + return out, skipU32() + // -------- u64 -------- + case RaydiumLaunchlabBuy, RaydiumLaunchlabSell: + return out, skipU64() + // -------- Side(u8) payload -------- + case Serum, Aldrin, AldrinV2, Dradex, Openbook, Phoenix, OpenBookV2, TokenMill, Plasma, TesseraV, Futarchy, WhaleStreet, Manifest: + return out, skipU8() + // -------- MeteoraDlmmSwapV2: RemainingAccountsInfo -------- + case MeteoraDlmmSwapV2: + return out, skipRemaining() + // -------- DynamicV1: Vec -------- + case DynamicV1: + return out, skipDynamicV1(dec) + // -------- u64 + u64 -------- + case Symmetry: + return out, skipTwoU64() + // -------- Clone: u8 + bool + bool -------- + case Clone: + if err := skipU8(); err != nil { + return Swap{}, err + } + if err := skipBool(); err != nil { + return Swap{}, err + } + return out, skipBool() + // -------- SanctumS: u8 + u8 + u32 + u32 -------- + case SanctumS: + if err := skipU8(); err != nil { + return Swap{}, err + } + if err := skipU8(); err != nil { + return Swap{}, err + } + if err := skipU32(); err != nil { + return Swap{}, err + } + return out, skipU32() + // -------- SanctumS(Add/Remove)Liquidity: u8 + u32 -------- + case SanctumSAddLiquidity, SanctumSRemoveLiquidity: + if err := skipU8(); err != nil { + return Swap{}, err + } + return out, skipU32() + // -------- WhirlpoolSwapV2 / DefiTuna: bool + Option -------- + case WhirlpoolSwapV2, DefiTuna: + if err := skipBool(); err != nil { + return Swap{}, err + } + return out, skipOptRemaining() + // -------- Perena: u8 + u8 -------- + case Perena: + if err := skipU8(); err != nil { + return Swap{}, err + } + return out, skipU8() + case GoonFi: + if err := skipBool(); err != nil { + return Swap{}, err + } + return out, skipU8() + // -------- HumidiFi/HumidiFiV2: u64 + bool -------- + case HumidiFi, HumidiFiV2: + if err := skipU64(); err != nil { + return Swap{}, err + } + return out, skipBool() + + // -------- JupiterRfqV2: Side(u8) + bytes -------- + case JupiterRfqV2: + // side + if err := skipU8(); err != nil { + return Swap{}, err + } + ln, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return Swap{}, err + } + if err := dec.SkipBytes(uint(ln)); err != nil { + return Swap{}, err + } + return out, nil + default: + // Unknown/new variant: assume no payload (keeps decoder aligned for RoutePlanStepV2 if it really is no-payload). + return out, nil + } +} + +func decodeRoutePlanStepV2(dec *bin.Decoder) (RoutePlanStepV2, error) { + sw, err := decodeSwap(dec) + if err != nil { + return RoutePlanStepV2{}, err + } + bps, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return RoutePlanStepV2{}, err + } + inIdx, err := dec.ReadUint8() + if err != nil { + return RoutePlanStepV2{}, err + } + outIdx, err := dec.ReadUint8() + if err != nil { + return RoutePlanStepV2{}, err + } + return RoutePlanStepV2{Swap: sw, Bps: bps, InputIdx: inIdx, OutputIdx: outIdx}, nil +} + +func decodeRoutePlanStep(dec *bin.Decoder) (RoutePlanStep, error) { + sw, err := decodeSwap(dec) + if err != nil { + return RoutePlanStep{}, err + } + percent, err := dec.ReadUint8() + if err != nil { + return RoutePlanStep{}, err + } + inIdx, err := dec.ReadUint8() + if err != nil { + return RoutePlanStep{}, err + } + outIdx, err := dec.ReadUint8() + if err != nil { + return RoutePlanStep{}, err + } + return RoutePlanStep{Swap: sw, Percent: percent, InputIdx: inIdx, OutputIdx: outIdx}, nil +} + +func decodeJupiterV6RouteV2Arg(data []byte) (*JupiterV6RouteV2Arg, error) { + dec := bin.NewBorshDecoder(data) + + var err error + out := &JupiterV6RouteV2Arg{} + if out.In, err = dec.ReadUint64(binary.LittleEndian); err != nil { + return nil, err + } + if out.Out, err = dec.ReadUint64(binary.LittleEndian); err != nil { + return nil, err + } + if out.SlippageBps, err = dec.ReadUint16(binary.LittleEndian); err != nil { + return nil, err + } + if out.PlatformFeeBps, err = dec.ReadUint16(binary.LittleEndian); err != nil { + return nil, err + } + if out.PositiveSlippageBps, err = dec.ReadUint16(binary.LittleEndian); err != nil { + return nil, err + } + + // vec: u32 length + elements + ln, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return nil, err + } + out.Plan = make([]RoutePlanStepV2, 0, ln) + for i := uint32(0); i < ln; i++ { + step, err := decodeRoutePlanStepV2(dec) + if err != nil { + return nil, fmt.Errorf("decode Plan[%d]: %w", i, err) + } + out.Plan = append(out.Plan, step) + } + return out, nil +} + +type JupiterV6RouteArg struct { + Plan []RoutePlanStep + + In uint64 + QuotedOut uint64 + SlippageBps uint16 + PlatformFeeBps uint8 +} + +type JupiterV6RouteWithTokenLedgerArg struct { + Plan []RoutePlanStep + + QuotedOut uint64 + SlippageBps uint16 + PlatformFeeBps uint8 +} + +type JupiterV6SharedAccountsExactOutRouteArg struct { + ID uint8 + + Plan []RoutePlanStep + + Out uint64 + QuotedIn uint64 + SlippageBps uint16 + PlatformFeeBps uint8 +} + +type JupiterV6SharedAccountsRouteArg struct { + ID uint8 + + Plan []RoutePlanStep + + In uint64 + QuotedOut uint64 + SlippageBps uint16 + PlatformFeeBps uint8 +} + +type JupiterV6SharedAccountsRouteWithTokenLedgerArg struct { + ID uint8 + + Plan []RoutePlanStep + + QuotedOut uint64 + SlippageBps uint16 + PlatformFeeBps uint8 +} + +type JupiterV6ExactOutRouteV2Arg struct { + Out uint64 + QuotedIn uint64 + Slippage uint16 + PlatFee uint16 + PosSlip uint16 + RoutePlan []RoutePlanStepV2 +} + +type JupiterV6SharedAccountsExactOutRouteV2Arg struct { + ID uint8 + + Out uint64 + QuotedIn uint64 + Slippage uint16 + PlatFee uint16 + PosSlip uint16 + RoutePlan []RoutePlanStepV2 +} + +type JupiterV6SharedAccountsRouteV2Arg struct { + ID uint8 + + In uint64 + QuotedOut uint64 + Slippage uint16 + PlatFee uint16 + PosSlip uint16 + RoutePlan []RoutePlanStepV2 +} + +func decodeVecRoutePlanStep(dec *bin.Decoder) ([]RoutePlanStep, error) { + ln, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return nil, err + } + out := make([]RoutePlanStep, 0, ln) + for i := uint32(0); i < ln; i++ { + step, err := decodeRoutePlanStep(dec) + if err != nil { + return nil, fmt.Errorf("decode RoutePlanStep[%d]: %w", i, err) + } + out = append(out, step) + } + return out, nil +} + +func decodeVecRoutePlanStepV2(dec *bin.Decoder) ([]RoutePlanStepV2, error) { + ln, err := dec.ReadUint32(binary.LittleEndian) + if err != nil { + return nil, err + } + out := make([]RoutePlanStepV2, 0, ln) + for i := uint32(0); i < ln; i++ { + step, err := decodeRoutePlanStepV2(dec) + if err != nil { + return nil, fmt.Errorf("decode RoutePlanStepV2[%d]: %w", i, err) + } + out = append(out, step) + } + return out, nil +} + +func decodeJupiterV6RouteArg(data []byte) (*JupiterV6RouteArg, error) { + dec := bin.NewBorshDecoder(data) + plan, err := decodeVecRoutePlanStep(dec) + if err != nil { + return nil, err + } + in, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + quotedOut, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + slippage, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pf, err := dec.ReadUint8() + if err != nil { + return nil, err + } + return &JupiterV6RouteArg{Plan: plan, In: in, QuotedOut: quotedOut, SlippageBps: slippage, PlatformFeeBps: pf}, nil +} + +func decodeJupiterV6RouteWithTokenLedgerArg(data []byte) (*JupiterV6RouteWithTokenLedgerArg, error) { + dec := bin.NewBorshDecoder(data) + plan, err := decodeVecRoutePlanStep(dec) + if err != nil { + return nil, err + } + quotedOut, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + slippage, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pf, err := dec.ReadUint8() + if err != nil { + return nil, err + } + return &JupiterV6RouteWithTokenLedgerArg{Plan: plan, QuotedOut: quotedOut, SlippageBps: slippage, PlatformFeeBps: pf}, nil +} + +func decodeJupiterV6SharedAccountsExactOutRouteArg(data []byte) (*JupiterV6SharedAccountsExactOutRouteArg, error) { + dec := bin.NewBorshDecoder(data) + id, err := dec.ReadUint8() + if err != nil { + return nil, err + } + plan, err := decodeVecRoutePlanStep(dec) + if err != nil { + return nil, err + } + outAmt, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + quotedIn, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + slippage, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pf, err := dec.ReadUint8() + if err != nil { + return nil, err + } + return &JupiterV6SharedAccountsExactOutRouteArg{ID: id, Plan: plan, Out: outAmt, QuotedIn: quotedIn, SlippageBps: slippage, PlatformFeeBps: pf}, nil +} + +func decodeJupiterV6SharedAccountsRouteArg(data []byte) (*JupiterV6SharedAccountsRouteArg, error) { + dec := bin.NewBorshDecoder(data) + id, err := dec.ReadUint8() + if err != nil { + return nil, err + } + plan, err := decodeVecRoutePlanStep(dec) + if err != nil { + return nil, err + } + inAmt, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + quotedOut, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + slippage, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pf, err := dec.ReadUint8() + if err != nil { + return nil, err + } + return &JupiterV6SharedAccountsRouteArg{ID: id, Plan: plan, In: inAmt, QuotedOut: quotedOut, SlippageBps: slippage, PlatformFeeBps: pf}, nil +} + +func decodeJupiterV6SharedAccountsRouteWithTokenLedgerArg(data []byte) (*JupiterV6SharedAccountsRouteWithTokenLedgerArg, error) { + dec := bin.NewBorshDecoder(data) + id, err := dec.ReadUint8() + if err != nil { + return nil, err + } + plan, err := decodeVecRoutePlanStep(dec) + if err != nil { + return nil, err + } + quotedOut, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + slippage, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pf, err := dec.ReadUint8() + if err != nil { + return nil, err + } + return &JupiterV6SharedAccountsRouteWithTokenLedgerArg{ID: id, Plan: plan, QuotedOut: quotedOut, SlippageBps: slippage, PlatformFeeBps: pf}, nil +} + +func decodeJupiterV6ExactOutRouteV2Arg(data []byte) (*JupiterV6ExactOutRouteV2Arg, error) { + dec := bin.NewBorshDecoder(data) + outAmt, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + quotedIn, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + slippage, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pf, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pos, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + plan, err := decodeVecRoutePlanStepV2(dec) + if err != nil { + return nil, err + } + return &JupiterV6ExactOutRouteV2Arg{Out: outAmt, QuotedIn: quotedIn, Slippage: slippage, PlatFee: pf, PosSlip: pos, RoutePlan: plan}, nil +} + +func decodeJupiterV6SharedAccountsExactOutRouteV2Arg(data []byte) (*JupiterV6SharedAccountsExactOutRouteV2Arg, error) { + dec := bin.NewBorshDecoder(data) + id, err := dec.ReadUint8() + if err != nil { + return nil, err + } + outAmt, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + quotedIn, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + slippage, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pf, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pos, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + plan, err := decodeVecRoutePlanStepV2(dec) + if err != nil { + return nil, err + } + return &JupiterV6SharedAccountsExactOutRouteV2Arg{ID: id, Out: outAmt, QuotedIn: quotedIn, Slippage: slippage, PlatFee: pf, PosSlip: pos, RoutePlan: plan}, nil +} + +func decodeJupiterV6SharedAccountsRouteV2Arg(data []byte) (*JupiterV6SharedAccountsRouteV2Arg, error) { + dec := bin.NewBorshDecoder(data) + id, err := dec.ReadUint8() + if err != nil { + return nil, err + } + inAmt, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + quotedOut, err := dec.ReadUint64(binary.LittleEndian) + if err != nil { + return nil, err + } + slippage, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pf, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + pos, err := dec.ReadUint16(binary.LittleEndian) + if err != nil { + return nil, err + } + plan, err := decodeVecRoutePlanStepV2(dec) + if err != nil { + return nil, err + } + return &JupiterV6SharedAccountsRouteV2Arg{ID: id, In: inAmt, QuotedOut: quotedOut, Slippage: slippage, PlatFee: pf, PosSlip: pos, RoutePlan: plan}, nil +} + +func pumpSwapSellAtIdx0(amount uint64, plan []RoutePlanStep) (uint64, int) { + var ( + ret uint64 + i int + ) + for _, step := range plan { + if step.InputIdx == 0 && + (step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) { + i++ + if ret > 0 { + // multiple pumpSwapSell at inputIdx=0? should not happen + return 0, i + } + ret += amount * uint64(step.Percent) / 100 + } + } + return ret, i +} + +func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) (uint64, int) { + var ( + ret uint64 + i int + ) + for _, step := range plan { + if step.InputIdx == 0 && + (step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) { + i++ + if ret > 0 { + // multiple pumpSwapSell at inputIdx=0? should not happen + + return 0, i + } + ret += amount * uint64(step.Bps) / 10000 + } + } + return ret, i +} + +// only decodes inputIdx = 0 container pumpSwap instructions for now +func parseJupiterV6Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { + msg := tx.Message + if instructionIndex >= len(msg.Instructions) { + return nil, fmt.Errorf("instruction index out of bounds") + } + + instruction := msg.Instructions[instructionIndex] + if len(instruction.Data) == 0 { + return nil, fmt.Errorf("data is empty") + } + if len(instruction.Data) < 8 { + return nil, nil + } + + disc := instruction.Data[:8] + + var ( + sourceMint solana.PublicKey + inputAmount uint64 + planCount int + err error + ) + + // route_v2 / exact_out_route_v2 / shared_accounts_*_v2 use accounts[3]/[4] as src/dst mints (per IDL) + // route/shared_accounts_* (v1) use different account layouts; we only decode args here. + switch { + case bytes.Equal(disc, jupiterRouteV2): + args, err := decodeJupiterV6RouteV2Arg(instruction.Data[8:]) + if err != nil { + return nil, err + } + inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.Plan) + case bytes.Equal(disc, jupiterSharedAccountsRouteV2): + args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:]) + if err != nil { + return nil, err + } + inputAmount, planCount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan) + case bytes.Equal(disc, jupiterRoute): + args, err := decodeJupiterV6RouteArg(instruction.Data[8:]) + if err != nil { + return nil, err + } + _ = args + inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) + case bytes.Equal(disc, jupiterSharedAccountsRoute): + args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:]) + if err != nil { + return nil, err + } + _ = args + inputAmount, planCount = pumpSwapSellAtIdx0(args.In, args.Plan) + default: + return nil, nil + } + if planCount > 1 { + // multiple pumpSwapSell at inputIdx=0? should not happen + logger.Warn("pumpSwapSell at inputIdx=0: multiple instances found", "tx", tx.Signatures[0].String(), "planCount", planCount) + } + if inputAmount == 0 { + return nil, nil + } + + // existing mint extraction logic only valid for route_v2/ exact_out_route_v2. Keep it but guard. + if bytes.Equal(disc, jupiterRouteV2) || bytes.Equal(disc, jupiterSharedAccountsRouteV2) { + if len(instruction.Accounts) < 6 { + return nil, fmt.Errorf("not enough accounts for jupiter v6 v2 instruction") + } + sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[3])) + if err != nil { + return nil, err + } + + var ( + srcIdx uint8 + ) + for i, acctIdx := range instruction.Accounts { + if i < 9 { + continue + } + key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + if err != nil { + return nil, err + } + if key.Equals(pumpAmmProgramID) { + srcIdx = uint8(i + 4) + break + } + } + if srcIdx == 0 { + return nil, nil + } + + sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx])) + if err != nil { + return nil, err + } + + quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1])) + if err != nil { + return nil, err + } + if !quoteMint.Equals(solana.WrappedSol) { + return nil, nil + } + + } else if bytes.Equal(disc, jupiterSharedAccountsRoute) { + if len(instruction.Accounts) < 12 { + return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterSharedAccountsRoute instruction") + } + sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[7])) + if err != nil { + return nil, err + } + var ( + srcIdx uint8 + ) + for i, acctIdx := range instruction.Accounts { + if i < 12 { + continue + } + key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + if err != nil { + return nil, err + } + if key.Equals(pumpAmmProgramID) { + srcIdx = uint8(i + 4) + break + } + } + if srcIdx == 0 { + return nil, nil + } + + sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx])) + if err != nil { + return nil, err + } + + quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1])) + if err != nil { + return nil, err + } + if !quoteMint.Equals(solana.WrappedSol) { + return nil, nil + } + } else { + if len(instruction.Accounts) < 10 { + return nil, fmt.Errorf("not enough accounts for jupiter v6 jupiterRoute instruction") + } + var ( + srcIdx uint8 + ) + + for i, acctIdx := range instruction.Accounts { + if i < 9 { + continue + } + key, err := getStaticKey(tx.Message.StaticAccountKeys, int(acctIdx)) + if err != nil { + return nil, err + } + if key.Equals(pumpAmmProgramID) { + srcIdx = uint8(i + 4) + break + } + } + if srcIdx == 0 { + return nil, nil + } + sourceMint, err = getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx])) + if err != nil { + return nil, err + } + + quoteMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1])) + if err != nil { + return nil, err + } + if !quoteMint.Equals(solana.WrappedSol) { + return nil, nil + } + } + + signal := &TxSignal{ + Label: "jupiterV6", + TxHash: tx.Signatures[0].String(), + Maker: tx.Message.StaticAccountKeys[0].String(), + Token0Address: sourceMint.String(), + Token1Address: wsolMint, + Token0Amount: formatTokenAmount(inputAmount), + Token1Amount: decimal.Zero, + Program: "PumpAMM", + Event: "sell", + IsToken2022: false, + IsMayhemMode: false, + ExactSOL: false, + Block: tx.Block, + Token0AmountUint64: inputAmount, + Token1AmountUint64: 0, + } + + return signal, nil +} + +// keep lints happy if solana-go isn't referenced elsewhere in this file for build tags +var _ = solana.PublicKey{} diff --git a/pkg/shreder/juptierv6_test.go b/pkg/shreder/juptierv6_test.go new file mode 100644 index 0000000..ac0b614 --- /dev/null +++ b/pkg/shreder/juptierv6_test.go @@ -0,0 +1,88 @@ +package shreder + +import ( + "encoding/hex" + "testing" +) + +func TestDecodeRouteV2Arg(t *testing.T) { + tests := []struct { + name string + hexData string + }{ + { + name: "Jupiter V6 RouteV2Arg Test 0", + hexData: "bb64facc31c4af14809fd500000000002222e8db1800000064000a000000020000005601fe102700016310270102", + }, + { + name: "Jupiter V6 RouteV2Arg Test 1", + hexData: "bb64facc31c4af144ff91634b90000004e6c4d05000000002c013200000003000000520000000000000000102700014f102701024310270203", + }, + { + name: "Jupiter V6 RouteV2Arg Test 2", + hexData: "bb64facc31c4af14ba2eafa02c1d0000777a9b2200000000f4010a0000000100000052000000000000000010270001", + }, + { + name: "Jupiter V6 RouteV2Arg Test 3", + hexData: "bb64facc31c4af144a3521186b07000030508d0e00000000c201320000000300000052000000000000000010270001740110270102590010270203", + }, + { + name: "Jupiter V6 RouteV2Arg Test 4", + hexData: "bb64facc31c4af14092d05050000000013701f198c0100008102380100000300000059011027000168001027010251000000000000000010270203", + }, + { + name: "Jupiter V6 RouteV2Arg Test 5", + hexData: "bb64facc31c4af1480969800000000006f44ad39bd0000001202320000000200000068001027000151000000000000000010270102", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + instrData, err := hex.DecodeString(tt.hexData) + if err != nil { + t.Fatalf("failed to decode hex string: %v", err) + return + } + t.Logf("raw bytes: %x", instrData[8:]) + args, err := decodeJupiterV6RouteV2Arg(instrData[8:]) + if err != nil { + t.Fatalf("failed to decode jupiter arguments: %v", err) + return + } + t.Logf("decoded args: %+v", args) + }) + } + +} + +func TestDecodeRouteArg(t *testing.T) { + tests := []struct { + name string + hexData string + }{ + { + name: "Jupiter V6 RouteArg Test 0", + hexData: "e517cb977ae3ad2a030000004f6400014f64010251000000000000000064020340420f00000000005c1c81900e000000640000", + }, + { + name: "Jupiter V6 RouteArg Test 1", + hexData: "e517cb977ae3ad2a0200000028640001510000000000000000640102c09ee605000000005e1bc48efa000000d00700", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + instrData, err := hex.DecodeString(tt.hexData) + if err != nil { + t.Fatalf("failed to decode hex string: %v", err) + return + } + t.Logf("raw bytes: %x", instrData[8:]) + args, err := decodeJupiterV6RouteArg(instrData[8:]) + if err != nil { + t.Fatalf("failed to decode jupiter arguments: %v", err) + return + } + t.Logf("decoded args: %+v", args) + }) + } + +} diff --git a/pkg/shreder/okxonchainlab.go b/pkg/shreder/okxonchainlab.go new file mode 100644 index 0000000..546de75 --- /dev/null +++ b/pkg/shreder/okxonchainlab.go @@ -0,0 +1,5 @@ +package shreder + +//func parseOkxDexRouteV2Instruction(tx *versionedTransaction, instructionIndex int) (*TxSignal, error) { +// +//} diff --git a/pkg/shreder/tx.go b/pkg/shreder/tx.go index a20c993..b263f72 100644 --- a/pkg/shreder/tx.go +++ b/pkg/shreder/tx.go @@ -1,6 +1,8 @@ package shreder import ( + "log/slog" + "os" "time" "github.com/shopspring/decimal" @@ -11,8 +13,23 @@ const ( SolDecimals = 9 ) +var ( + logger *slog.Logger +) + +func init() { + handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}) + logger = slog.New(handler) +} + +func SetLogLevel(level slog.Level) { + handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}) + logger = slog.New(handler) +} + type TxSignal struct { Source string `json:"source"` + Label string `json:"label"` TxHash string `json:"tx_hash"` Maker string `json:"maker"` Token0Address string `json:"token0_address"` diff --git a/pkg/shreder/txparser.go b/pkg/shreder/txparser.go index dcbd5c9..4722bbf 100644 --- a/pkg/shreder/txparser.go +++ b/pkg/shreder/txparser.go @@ -4,8 +4,8 @@ import ( "bytes" "encoding/binary" "fmt" - "log/slog" "math/big" + "strings" "github.com/gagliardetto/solana-go" "github.com/mr-tron/base58" @@ -43,8 +43,23 @@ var ( flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9") terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3") + + jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4") ) +type AccountNotFoundError struct { + Index int + Len int +} + +func NewAccountNotFoundError(i, l int) error { + return &AccountNotFoundError{i, l} +} + +func (e AccountNotFoundError) Error() string { + return fmt.Sprintf("account index %d out of range, len=%d", e.Index, e.Len) +} + // instruction discriminators var ( pumpCreateCoinIX = []byte{24, 30, 200, 40, 5, 28, 7, 119} @@ -81,11 +96,6 @@ var ( terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1} ) -// table lookups -const ( - photonTableLookup = "3r6paeFSLpeUVmWtShb5uZtXYpcBE3729kUxkUS7xKi1" -) - type compiledInstruction struct { ProgramIDIndex uint8 Accounts []uint8 @@ -183,7 +193,7 @@ type fjszBuyArgs struct { } // ParseTransaction mirrors the Rust parse_transaction entry point. -func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal { +func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) []*TxSignal { versioned, err := toVersionedTransaction(update) if err != nil || versioned == nil || len(versioned.Signatures) == 0 { return nil @@ -193,6 +203,35 @@ func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal { staticKeys := versioned.Message.StaticAccountKeys instructions := versioned.Message.Instructions + if loader != nil && len(versioned.Message.AddressTableLookups) > 0 { + lookupTableOk := true + for _, lookup := range versioned.Message.AddressTableLookups { + if len(lookup.WritableIndexes) == 0 { + continue + } + accounts := loader.GetAddressTable(lookup.AccountKey, lookup.WritableIndexes) + if len(accounts) != len(lookup.WritableIndexes) { + lookupTableOk = false + break + } + staticKeys = append(staticKeys, accounts...) + + } + if lookupTableOk { + for _, lookup := range versioned.Message.AddressTableLookups { + if len(lookup.ReadonlyIndexes) == 0 { + continue + } + accounts := loader.GetAddressTable(lookup.AccountKey, lookup.ReadonlyIndexes) + if len(accounts) != len(lookup.ReadonlyIndexes) { + break + } + staticKeys = append(staticKeys, accounts...) + } + } + versioned.Message.StaticAccountKeys = staticKeys + } + var parsed []*TxSignal for i := range instructions { @@ -233,6 +272,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal { case terminalProgramID: txRes, err := parseTermInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "terminal", terminalProgramID.String()) + case jupiterV6ProgramID: + txRes, err := parseJupiterV6Instruction(versioned, i) + parsed = appendParsed(parsed, txRes, err, txHash, "jupiterv6", jupiterV6ProgramID.String()) } } @@ -241,7 +283,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction) []*TxSignal { func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string, entryContract string) []*TxSignal { if err != nil { - slog.Error("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:])) + if !strings.HasPrefix(err.Error(), "account index") { + logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:])) + } return list } if parsed != nil { @@ -313,7 +357,7 @@ func formatSolAmount(lamports uint64) decimal.Decimal { func getStaticKey(static []solana.PublicKey, index int) (solana.PublicKey, error) { if index < 0 || index >= len(static) { - return solana.PublicKey{}, fmt.Errorf("account index %d out of bounds", index) + return solana.PublicKey{}, NewAccountNotFoundError(index, len(static)) } return static[index], nil } @@ -359,6 +403,7 @@ func parsePumpCreate(tx *versionedTransaction, instruction *compiledInstruction) return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pump", Maker: creator.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -379,7 +424,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 8 { - return nil, fmt.Errorf("data too short for create v2 args") + return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -399,6 +444,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pump", Maker: args.Creator.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -416,7 +462,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio func decodePumpBuyArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { - return 0, 0, fmt.Errorf("data too short for buy args") + return 0, 0, fmt.Errorf("data too short for pump buy buy args, len=%d", len(data)) } var args pumpBuyArgs @@ -462,6 +508,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (* return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pump", Maker: buyer.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -480,7 +527,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (* func decodePumpSellArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { - return 0, 0, fmt.Errorf("data too short for sell args") + return 0, 0, fmt.Errorf("data too short for pump sell sell args, len=%d", len(data)) } var args pumpExtendedSellArgs @@ -519,6 +566,7 @@ func parsePumpSell(tx *versionedTransaction, instruction *compiledInstruction) ( return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pump", Maker: seller.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -570,13 +618,14 @@ func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, } if len(instruction.Data) < 17 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(instruction.Data)) } solAmount := binary.LittleEndian.Uint64(instruction.Data[1:9]) return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "azcz", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -610,7 +659,7 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er } if len(instruction.Data) < 2 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(instruction.Data)) } var args azczBuyArgs @@ -620,6 +669,7 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "azcz", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -664,7 +714,7 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi } if len(instruction.Data) < 2 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(instruction.Data)) } var args f5tfBuyArgs @@ -674,6 +724,7 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "f5tf", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -699,8 +750,11 @@ func parseFlasInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi if len(instruction.Data) == 0 { return nil, fmt.Errorf("data is empty") } + if len(instruction.Data) == 10 && instruction.Data[0] == 1 { + return nil, nil + } if len(instruction.Data) < 20 { - return nil, fmt.Errorf("data too short for args") + return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data)) } methodData := instruction.Data[17:20] if !matchMethod(methodData, flasBuyTokensIX) { @@ -741,6 +795,7 @@ func parseFlasAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "flas", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -780,6 +835,7 @@ func parseFlasAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "flas", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -819,6 +875,7 @@ func parseFlasSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "flas", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -861,6 +918,7 @@ func parseFlasBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "flas", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -906,7 +964,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction) return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -927,6 +985,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction) solAmount := args.SolAmount * (100000000 - 1234568) / 100000000 return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "photon", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -947,7 +1006,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for swap args") + return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -956,12 +1015,11 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) return nil, err } - quoteIndex := int(instruction.Accounts[4]) - quote, err := resolveQuoteAccount(tx, quoteIndex, []string{photonTableLookup}, 0) + quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } - if quote != wsolMint { + if !quote.Equals(solana.WrappedSol) { return nil, nil } @@ -983,6 +1041,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) solAmount := args.FromAmount * (100000000 - 1234568) / 100000000 return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "photon", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, @@ -1064,6 +1123,7 @@ func parseTermAmmSell(tx *versionedTransaction, instruction *compiledInstruction return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "term", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1099,6 +1159,7 @@ func parseTermBuy(tx *versionedTransaction, instruction *compiledInstruction) (* return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "term", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1133,6 +1194,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) ( return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "term", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1150,7 +1212,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) ( func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { - return 0, 0, fmt.Errorf("data too short for buy args") + return 0, 0, fmt.Errorf("data too short for pump amm buy args, len=%d", len(data)) } var args pumpAmmBuyArgs @@ -1189,12 +1251,11 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction) if err != nil { return nil, err } - quoteIndex := int(instruction.Accounts[4]) - quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0) + quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } - if quote != wsolMint { + if !quote.Equals(solana.WrappedSol) { return nil, nil } @@ -1205,6 +1266,7 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction) return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pumpamm", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, @@ -1236,12 +1298,11 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction if err != nil { return nil, err } - quoteIndex := int(instruction.Accounts[4]) - quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0) + quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } - if quote != wsolMint { + if !quote.Equals(solana.WrappedSol) { return nil, nil } @@ -1252,6 +1313,7 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pumpamm", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, @@ -1285,7 +1347,7 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -1305,6 +1367,7 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "bobo", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1348,7 +1411,7 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 24 { - return nil, fmt.Errorf("data too short for sell args") + return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -1365,6 +1428,7 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e tokenAmount := binary.LittleEndian.Uint64(instruction.Data[19:25]) return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1386,7 +1450,7 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 24 { - return nil, fmt.Errorf("data too short for sell args") + return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -1403,6 +1467,7 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal tokenAmount := binary.LittleEndian.Uint64(instruction.Data[19:25]) return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1441,6 +1506,7 @@ func parseQtkvBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "qtkv", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1474,7 +1540,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -1494,6 +1560,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "fjsz", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1530,43 +1597,6 @@ func parseTerminalInstruction(tx *versionedTransaction, instructionIndex int) (* return nil, nil } -func resolveQuoteAccount(tx *versionedTransaction, quoteIndex int, expectedTableKeys []string, targetIndex uint8) (string, error) { - staticKeys := tx.Message.StaticAccountKeys - if quoteIndex < len(staticKeys) { - quoteKey := staticKeys[quoteIndex].String() - return quoteKey, nil - } - - // attempt to load from address table lookup - if len(expectedTableKeys) == 0 || len(tx.Message.AddressTableLookups) != 1 { - return "", fmt.Errorf("parse quote from table lookup failed") - } - - table := tx.Message.AddressTableLookups[0] - match := false - for _, key := range expectedTableKeys { - if table.AccountKey.String() == key { - match = true - break - } - } - if !match { - return "", fmt.Errorf("parse quote from table lookup failed") - } - - indexOfTarget := indexOf(table.ReadonlyIndexes, targetIndex) - if indexOfTarget < 0 { - return "", fmt.Errorf("parse quote from table lookup failed") - } - - expectedIndex := len(staticKeys) + len(table.WritableIndexes) + indexOfTarget - if quoteIndex != expectedIndex { - return "", fmt.Errorf("parse quote from table lookup failed") - } - - return wsolMint, nil -} - func indexOf(haystack []uint8, needle uint8) int { for i, v := range haystack { if v == needle {