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 8b54673..af45b3a 100644 --- a/cmd/shreder/main.go +++ b/cmd/shreder/main.go @@ -37,6 +37,7 @@ func main() { "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", "GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR", //Event Authority "5PHirr8joyTMp9JMm6nW7hNDVyEYdkzDqazxPD7RaTjx", // Fee Config + "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ", // pump fee program }, }, "photon": { @@ -44,6 +45,11 @@ func main() { "BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW", }, }, + "jupiterV6": { + AccountRequired: []string{ + "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", + }, + }, // TODO: axiom, gmgn, etc. }) if err != nil { @@ -77,7 +83,12 @@ func main() { return case txBatch := <-txCh: //jsonData, _ := json.MarshalIndent(txBatch, "", " ") - fmt.Println(txBatch[0].TxHash) + for _, tx := range txBatch { + if tx.Label == "jupiterV6" { + fmt.Println("===============", tx.TxHash, tx.Token0Address, tx.Token0Amount) + } + } + //fmt.Println(txBatch[0].TxHash) } } } diff --git a/pkg/shreder/addresstables.go b/pkg/shreder/addresstables.go index a411284..df8ff7b 100644 --- a/pkg/shreder/addresstables.go +++ b/pkg/shreder/addresstables.go @@ -21,7 +21,7 @@ type AddressTables struct { } func NewAddressTables(rpcClient *rpc.Client) *AddressTables { - pool, _ := ants.NewPool(10, ants.WithPreAlloc(true), ants.WithNonblocking(true)) + pool, _ := ants.NewPool(5, ants.WithPreAlloc(true), ants.WithNonblocking(true)) cache, _ := lru.New[solana.PublicKey, []solana.PublicKey](10000) return &AddressTables{ rpcClient: rpcClient, 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..53244ba --- /dev/null +++ b/pkg/shreder/juptierv6.go @@ -0,0 +1,983 @@ +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 { + var ret uint64 + for _, step := range plan { + if step.InputIdx == 0 && + (step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) { + ret += amount * uint64(step.Percent) / 100 + } + } + return ret +} + +func pumpSwapSellAtIdx0V2(amount uint64, plan []RoutePlanStepV2) uint64 { + var ret uint64 + for _, step := range plan { + if step.InputIdx == 0 && + (step.Swap.Kind == PumpSwapSell || step.Swap.Kind == PumpSwapSellV2 || step.Swap.Kind == PumpSwapSellV3) { + ret += amount * uint64(step.Bps) / 10000 + } + } + return ret +} + +// 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 + 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 = pumpSwapSellAtIdx0V2(args.In, args.Plan) + + case bytes.Equal(disc, jupiterSharedAccountsRouteV2): + args, err := decodeJupiterV6SharedAccountsRouteV2Arg(instruction.Data[8:]) + if err != nil { + return nil, err + } + inputAmount = pumpSwapSellAtIdx0V2(args.In, args.RoutePlan) + + case bytes.Equal(disc, jupiterRoute): + args, err := decodeJupiterV6RouteArg(instruction.Data[8:]) + if err != nil { + return nil, err + } + _ = args + inputAmount = pumpSwapSellAtIdx0(args.In, args.Plan) + + case bytes.Equal(disc, jupiterSharedAccountsRoute): + args, err := decodeJupiterV6SharedAccountsRouteArg(instruction.Data[8:]) + if err != nil { + return nil, err + } + _ = args + inputAmount = pumpSwapSellAtIdx0(args.In, args.Plan) + + default: + return nil, nil + } + 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 + } + } 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 + } + } 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 + } + distMint, err := getStaticKey(tx.Message.StaticAccountKeys, int(instruction.Accounts[srcIdx+1])) + if err != nil { + return nil, err + } + if !distMint.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: "buy", + 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..83e5efa --- /dev/null +++ b/pkg/shreder/okxonchainlab.go @@ -0,0 +1 @@ +package shreder diff --git a/pkg/shreder/tx.go b/pkg/shreder/tx.go index 8b04290..041794d 100644 --- a/pkg/shreder/tx.go +++ b/pkg/shreder/tx.go @@ -29,6 +29,7 @@ func SetLogLevel(level slog.Level) { 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 ba7fdec..5efb0ff 100644 --- a/pkg/shreder/txparser.go +++ b/pkg/shreder/txparser.go @@ -42,8 +42,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} @@ -198,18 +213,30 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) instructions := versioned.Message.Instructions if loader != nil && len(versioned.Message.AddressTableLookups) > 0 { - // currently we only care about photon table lookup + 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...) - accounts2 := loader.GetAddressTable(lookup.AccountKey, lookup.ReadonlyIndexes) - if len(accounts2) != len(lookup.ReadonlyIndexes) { - break + + } + 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...) } - staticKeys = append(staticKeys, accounts2...) } } @@ -253,6 +280,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) case terminalProgramID: txRes, err := parseTermInstruction(versioned, i) parsed = appendParsed(parsed, txRes, err, txHash, "terminal") + //case jupiterV6ProgramID: + // txRes, err := parseJupiterV6Instruction(versioned, i) + // parsed = appendParsed(parsed, txRes, err, txHash, "jupiterv6") } } @@ -261,6 +291,9 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string) []*TxSignal { if err != nil { + //if errors.Is(err, &AccountNotFoundError{}) { + // + //} logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:])) return list } @@ -331,11 +364,8 @@ func formatSolAmount(lamports uint64) decimal.Decimal { } func getStaticKey(static []solana.PublicKey, index int) (solana.PublicKey, error) { - if index < 0 { - return solana.PublicKey{}, fmt.Errorf("account index %d less then 0", index) - } - if index >= len(static) { - return solana.PublicKey{}, fmt.Errorf("account index %d out of range", index) + if index < 0 || index >= len(static) { + return solana.PublicKey{}, NewAccountNotFoundError(index, len(static)) } return static[index], nil } @@ -381,6 +411,7 @@ func parsePumpCreate(tx *versionedTransaction, instruction *compiledInstruction) return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pump", Maker: creator.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -421,6 +452,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, @@ -484,6 +516,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (* return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pump", Maker: buyer.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -541,6 +574,7 @@ func parsePumpSell(tx *versionedTransaction, instruction *compiledInstruction) ( return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pump", Maker: seller.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -599,6 +633,7 @@ func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "azcz", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -642,6 +677,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, @@ -696,6 +732,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, @@ -766,6 +803,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, @@ -805,6 +843,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, @@ -844,6 +883,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, @@ -882,6 +922,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, @@ -948,6 +989,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, @@ -1003,6 +1045,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, @@ -1084,6 +1127,7 @@ func parseTermAmmSell(tx *versionedTransaction, instruction *compiledInstruction return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "term", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1119,6 +1163,7 @@ func parseTermBuy(tx *versionedTransaction, instruction *compiledInstruction) (* return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "term", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1153,6 +1198,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) ( return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "term", Maker: user.String(), Token0Address: mint.String(), Token1Address: wsolMint, @@ -1224,6 +1270,7 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction) return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pumpamm", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, @@ -1270,6 +1317,7 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction return &TxSignal{ TxHash: tx.Signatures[0].String(), + Label: "pumpamm", Maker: buyer.String(), Token0Address: base.String(), Token1Address: wsolMint, @@ -1323,6 +1371,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, @@ -1383,6 +1432,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, @@ -1421,6 +1471,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, @@ -1459,6 +1510,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, @@ -1512,6 +1564,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,