diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1bd382..51f8282 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ on: env: SOLANA_CLI_VERSION: 2.1.0 NODE_VERSION: 22.15.0 - ANCHOR_CLI_VERSION: 0.31.0 + ANCHOR_CLI_VERSION: 0.31.1 TOOLCHAIN: 1.76.0 jobs: diff --git a/Anchor.toml b/Anchor.toml index 586e8bf..da3f977 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -1,5 +1,7 @@ [toolchain] -package_manager = "yarn" +anchor_version = "0.31.1" +solana_version = "2.1.0" +package_manager = "pnpm" [features] resolution = true diff --git a/Cargo.lock b/Cargo.lock index 797d22e..b889290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1108,6 +1108,13 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jupiter" +version = "0.10.1" +dependencies = [ + "anchor-lang", +] + [[package]] name = "keccak" version = "0.1.5" @@ -3558,6 +3565,24 @@ dependencies = [ "cp-amm", "dlmm", "ruint", + "zap-sdk", +] + +[[package]] +name = "zap-sdk" +version = "0.1.0" +dependencies = [ + "borsh 0.10.4", + "jupiter", + "num_enum", + "ruint", + "solana-account-info", + "solana-instruction", + "solana-program", + "solana-program-error", + "solana-pubkey", + "spl-associated-token-account", + "thiserror 2.0.12", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5da31c0..69477ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,5 @@ [workspace] -members = [ - "programs/*" -] +members = ["programs/*", "libs/*", "zap-sdk"] resolver = "2" [profile.release] @@ -16,4 +14,5 @@ codegen-units = 1 [workspace.dependencies] anchor-lang = "0.31.1" anchor-spl = "0.31.1" -bytemuck = "1.20.0" \ No newline at end of file +bytemuck = "1.20.0" +ruint = "1.3.0" diff --git a/idls/jup_v6.json b/idls/jup_v6.json deleted file mode 100644 index 6716ccb..0000000 --- a/idls/jup_v6.json +++ /dev/null @@ -1,1481 +0,0 @@ -{ - "address": "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4", - "metadata": { - "name": "jupiter", - "version": "0.1.0", - "spec": "0.1.0" - }, - "instructions": [ - { - "name": "route", - "docs": [ - "route_plan Topologically sorted trade DAG" - ], - "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" - }, - { - "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" - }, - { - "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": "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": "token2022_program", - "optional": true - }, - { - "name": "event_authority" - }, - { - "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": "shared_accounts_route", - "docs": [ - "Route by using program owned token accounts and open orders accounts." - ], - "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": "token2022_program", - "optional": true - }, - { - "name": "event_authority" - }, - { - "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": "token2022_program", - "optional": true - }, - { - "name": "token_ledger" - }, - { - "name": "event_authority" - }, - { - "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": "shared_accounts_exact_out_route", - "docs": [ - "Route by using program owned token accounts and open orders accounts." - ], - "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": "token2022_program", - "optional": true - }, - { - "name": "event_authority" - }, - { - "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": "set_token_ledger", - "discriminator": [ - 228, - 85, - 185, - 112, - 78, - 79, - 77, - 2 - ], - "accounts": [ - { - "name": "token_ledger", - "writable": true - }, - { - "name": "token_account" - } - ], - "args": [] - }, - { - "name": "create_open_orders", - "discriminator": [ - 229, - 194, - 212, - 172, - 8, - 10, - 134, - 147 - ], - "accounts": [ - { - "name": "open_orders", - "writable": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "dex_program" - }, - { - "name": "system_program" - }, - { - "name": "rent" - }, - { - "name": "market" - } - ], - "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" - } - ], - "args": [ - { - "name": "bump", - "type": "u8" - } - ] - }, - { - "name": "create_program_open_orders", - "discriminator": [ - 28, - 226, - 32, - 148, - 188, - 136, - 113, - 171 - ], - "accounts": [ - { - "name": "open_orders", - "writable": true - }, - { - "name": "payer", - "writable": true, - "signer": true - }, - { - "name": "program_authority" - }, - { - "name": "dex_program" - }, - { - "name": "system_program" - }, - { - "name": "rent" - }, - { - "name": "market" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - } - ] - }, - { - "name": "claim", - "discriminator": [ - 62, - 198, - 214, - 193, - 213, - 159, - 108, - 210 - ], - "accounts": [ - { - "name": "wallet", - "writable": true - }, - { - "name": "program_authority", - "writable": true - }, - { - "name": "system_program" - } - ], - "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" - }, - { - "name": "program_authority" - }, - { - "name": "program_token_account", - "writable": true - }, - { - "name": "destination_token_account", - "writable": true - }, - { - "name": "mint" - }, - { - "name": "associated_token_token_program" - }, - { - "name": "associated_token_program" - }, - { - "name": "system_program" - } - ], - "args": [ - { - "name": "id", - "type": "u8" - } - ], - "returns": "u64" - }, - { - "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" - } - ], - "args": [] - } - ], - "accounts": [ - { - "name": "TokenLedger", - "discriminator": [ - 156, - 247, - 9, - 188, - 54, - 108, - 85, - 77 - ] - } - ], - "events": [ - { - "name": "SwapEvent", - "discriminator": [ - 64, - 198, - 205, - 232, - 38, - 8, - 113, - 226 - ] - }, - { - "name": "FeeEvent", - "discriminator": [ - 73, - 79, - 78, - 127, - 184, - 213, - 13, - 220 - ] - } - ], - "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" - } - ], - "types": [ - { - "name": "AmountWithSlippage", - "type": { - "kind": "struct", - "fields": [ - { - "name": "amount", - "type": "u64" - }, - { - "name": "slippage_bps", - "type": "u16" - } - ] - } - }, - { - "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": "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": "Clone", - "fields": [ - { - "name": "pool_index", - "type": "u8" - }, - { - "name": "quantity_is_input", - "type": "bool" - }, - { - "name": "quantity_is_collateral", - "type": "bool" - } - ] - }, - { - "name": "WhirlpoolSwapV2", - "fields": [ - { - "name": "a_to_b", - "type": "bool" - }, - { - "name": "remaining_accounts_info", - "type": { - "option": { - "defined": { - "name": "RemainingAccountsInfo" - } - } - } - } - ] - }, - { - "name": "OneIntro" - }, - { - "name": "PumpdotfunWrappedBuy" - }, - { - "name": "PumpdotfunWrappedSell" - }, - { - "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": "RemainingAccountsSlice", - "type": { - "kind": "struct", - "fields": [ - { - "name": "accounts_type", - "type": { - "defined": { - "name": "AccountsType" - } - } - }, - { - "name": "length", - "type": "u8" - } - ] - } - }, - { - "name": "RemainingAccountsInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "slices", - "type": { - "vec": { - "defined": { - "name": "RemainingAccountsSlice" - } - } - } - } - ] - } - }, - { - "name": "AccountsType", - "type": { - "kind": "enum", - "variants": [ - { - "name": "TransferHookA" - }, - { - "name": "TransferHookB" - } - ] - } - }, - { - "name": "TokenLedger", - "type": { - "kind": "struct", - "fields": [ - { - "name": "token_account", - "type": "pubkey" - }, - { - "name": "amount", - "type": "u64" - } - ] - } - }, - { - "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": "FeeEvent", - "type": { - "kind": "struct", - "fields": [ - { - "name": "account", - "type": "pubkey" - }, - { - "name": "mint", - "type": "pubkey" - }, - { - "name": "amount", - "type": "u64" - } - ] - } - } - ] - } \ No newline at end of file diff --git a/idls/jupiter.json b/idls/jupiter.json new file mode 100644 index 0000000..891d8a2 --- /dev/null +++ b/idls/jupiter.json @@ -0,0 +1,1163 @@ +{ + "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" } } } + } + ] + } + } + ] +} diff --git a/libs/jupiter/Cargo.toml b/libs/jupiter/Cargo.toml new file mode 100644 index 0000000..5094215 --- /dev/null +++ b/libs/jupiter/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jupiter" +version = "0.10.1" +description = "Created with Anchor" +edition = "2021" + +[dependencies] +anchor-lang = { workspace = true } diff --git a/libs/jupiter/src/lib.rs b/libs/jupiter/src/lib.rs new file mode 100644 index 0000000..5100bfe --- /dev/null +++ b/libs/jupiter/src/lib.rs @@ -0,0 +1,5 @@ +use anchor_lang::prelude::*; + +declare_program!(jupiter); + +pub use jupiter::*; diff --git a/programs/zap/Cargo.toml b/programs/zap/Cargo.toml index 3788b04..78e7eef 100644 --- a/programs/zap/Cargo.toml +++ b/programs/zap/Cargo.toml @@ -26,4 +26,5 @@ dlmm = { path = "../../libs/dlmm" } damm-v2 = { git = "https://github.com/MeteoraAg/damm-v2", features = [ "cpi", ], rev = "d9cef5aaec9cfa1a6d3b0b28d213c87c285127de", package = "cp-amm" } -ruint = "1.3.0" +ruint = { workspace = true } +zap-sdk = { path = "../../zap-sdk" } diff --git a/programs/zap/src/constants.rs b/programs/zap/src/constants.rs index 1b33c6e..76af340 100644 --- a/programs/zap/src/constants.rs +++ b/programs/zap/src/constants.rs @@ -1,25 +1,10 @@ use anchor_lang::constant; -use anchor_lang::{prelude::Pubkey, pubkey}; +use anchor_lang::prelude::Pubkey; -#[constant] -pub const DAMM_V2: Pubkey = pubkey!("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"); -// https://github.com/MeteoraAg/zap-program/blob/main/idls/damm_v2.json#L3512-L3521 -#[constant] -pub const DAMM_V2_SWAP_DISC: [u8; 8] = [248, 198, 158, 145, 225, 117, 135, 200]; - -#[constant] -pub const JUP_V6: Pubkey = pubkey!("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"); -// https://github.com/MeteoraAg/zap-program/blob/main/idls/jup_v6.json#L14-L23 -#[constant] -pub const JUP_V6_ROUTE_DISC: [u8; 8] = [229, 23, 203, 151, 122, 227, 173, 42]; -// https://github.com/MeteoraAg/zap-program/blob/main/idls/jup_v6.json#L257-L266 -#[constant] -pub const JUP_V6_SHARED_ACCOUNT_ROUTE_DISC: [u8; 8] = [193, 32, 155, 51, 65, 214, 156, 129]; - -pub const DLMM: Pubkey = pubkey!("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"); -// https://github.com/MeteoraAg/zap-program/blob/main/idls/dlmm.json#L3413-L3422 -#[constant] -pub const DLMM_SWAP2_DISC: [u8; 8] = [65, 75, 63, 76, 235, 91, 91, 136]; +use zap_sdk::constants::{ + DAMM_V2, DAMM_V2_SWAP_DISC, DLMM, DLMM_SWAP2_DISC, JUP_V6, JUP_V6_ROUTE_DISC, + JUP_V6_SHARED_ACCOUNT_ROUTE_DISC, +}; #[constant] pub const WHITELISTED_AMM_PROGRAMS: [(Pubkey, [u8; 8]); 4] = [ diff --git a/programs/zap/src/instructions/ix_zap_out.rs b/programs/zap/src/instructions/ix_zap_out.rs index dab7789..12b2987 100644 --- a/programs/zap/src/instructions/ix_zap_out.rs +++ b/programs/zap/src/instructions/ix_zap_out.rs @@ -7,7 +7,6 @@ use anchor_lang::{ use anchor_spl::token_interface::TokenAccount; use crate::{constants::WHITELISTED_AMM_PROGRAMS, error::ZapError, safe_math::SafeMath}; - #[derive(AnchorSerialize, AnchorDeserialize)] pub struct ZapOutParameters { pub percentage: u8, diff --git a/programs/zap/src/lib.rs b/programs/zap/src/lib.rs index 74e8104..ee8b31c 100644 --- a/programs/zap/src/lib.rs +++ b/programs/zap/src/lib.rs @@ -15,6 +15,7 @@ pub use state::*; pub mod utils; use dlmm::types::RemainingAccountsInfo; pub use utils::*; + declare_id!("zapvX9M3uf5pvy4wRPAbQgdQsM1xmuiFnkfHKPvwMiz"); #[program] diff --git a/tests/common/idl/jup_v6.ts b/tests/common/idl/jupiter.ts similarity index 100% rename from tests/common/idl/jup_v6.ts rename to tests/common/idl/jupiter.ts diff --git a/tests/common/jup.ts b/tests/common/jup.ts index 57b9b2c..680848b 100644 --- a/tests/common/jup.ts +++ b/tests/common/jup.ts @@ -1,8 +1,8 @@ import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import { PublicKey } from "@solana/web3.js"; import { LiteSVM } from "litesvm"; -import { Jupiter } from "./idl/jup_v6"; -import JupIDL from "../../idls/jup_v6.json"; +import { Jupiter } from "./idl/jupiter"; +import JupIDL from "../../idls/jupiter.json"; import { IdlTypes } from "@coral-xyz/anchor"; import { DAMM_V2_PROGRAM_ID } from "./damm_v2"; import { diff --git a/tests/fixtures/jup_v6.so b/tests/fixtures/jupiter.so similarity index 60% rename from tests/fixtures/jup_v6.so rename to tests/fixtures/jupiter.so index 21caeb0..8bcc1ec 100644 Binary files a/tests/fixtures/jup_v6.so and b/tests/fixtures/jupiter.so differ diff --git a/tests/test_zapout/zap_out_jup_v6.test.ts b/tests/test_zapout/zap_out_jup_v6.test.ts index d57b1e3..72d25c1 100644 --- a/tests/test_zapout/zap_out_jup_v6.test.ts +++ b/tests/test_zapout/zap_out_jup_v6.test.ts @@ -46,7 +46,7 @@ describe("Zap out Jup V6", () => { "./target/deploy/zap.so" ); svm.addProgramFromFile(DAMM_V2_PROGRAM_ID, "./tests/fixtures/damm_v2.so"); - svm.addProgramFromFile(JUP_V6_PROGRAM_ID, "./tests/fixtures/jup_v6.so"); + svm.addProgramFromFile(JUP_V6_PROGRAM_ID, "./tests/fixtures/jupiter.so"); user = Keypair.generate(); admin = Keypair.generate(); diff --git a/zap-sdk/Cargo.toml b/zap-sdk/Cargo.toml new file mode 100644 index 0000000..8308466 --- /dev/null +++ b/zap-sdk/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "zap-sdk" +version = "0.1.0" +edition = "2021" + +[dependencies] +borsh = "0.10" +jupiter = { path = "../libs/jupiter" } +ruint = { workspace = true } +num_enum = "0.7" +solana-account-info = "2" +solana-instruction = "2" +solana-program = "2" +solana-program-error = "2" +solana-pubkey = "2" +spl-associated-token-account = "6" +thiserror = "2" diff --git a/zap-sdk/src/constants.rs b/zap-sdk/src/constants.rs new file mode 100644 index 0000000..b74a225 --- /dev/null +++ b/zap-sdk/src/constants.rs @@ -0,0 +1,42 @@ +use solana_pubkey::Pubkey; + +pub const JUP_V6_SHARED_ACCOUNT_ROUTE_AMOUNT_IN_REVERSE_OFFSET: usize = 1 + 2 + 8 + 8; // Due to jupiter parameters have dynamic length type (vec), we have to do parameters_data.length - JUP_V6_SHARED_ACCOUNT_ROUTE_AMOUNT_IN_REVERSE_OFFSET +pub const JUP_V6_SHARED_ACCOUNT_ROUTE_SOURCE_ACCOUNT_INDEX: usize = 3; +pub const JUP_V6_SHARED_ACCOUNT_ROUTE_DESTINATION_ACCOUNT_INDEX: usize = 6; + +pub const JUP_V6_ROUTE_AMOUNT_IN_REVERSE_OFFSET: usize = 1 + 2 + 8 + 8; +pub const JUP_V6_ROUTE_SOURCE_ACCOUNT_INDEX: usize = 2; +pub const JUP_V6_ROUTE_DESTINATION_ACCOUNT_INDEX: usize = 4; + +pub const DLMM_SWAP2_AMOUNT_IN_OFFSET: u16 = 8; +pub const DLMM_SWAP2_SOURCE_ACCOUNT_INDEX: usize = 4; +pub const DLMM_SWAP2_DESTINATION_ACCOUNT_INDEX: usize = 5; + +pub const DAMM_V2_SWAP_AMOUNT_IN_OFFSET: u16 = 8; +pub const DAMM_V2_SWAP_SOURCE_ACCOUNT_INDEX: usize = 2; +pub const DAMM_V2_SWAP_DESTINATION_ACCOUNT_INDEX: usize = 3; + +pub const JUP_V6: Pubkey = Pubkey::from_str_const("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"); +// https://github.com/MeteoraAg/zap-program/blob/4b67bfc64e5a023a1b74386be8b82c3908934a0b/idls/jupiter.json#L161 +pub const JUP_V6_ROUTE_DISC: [u8; 8] = [229, 23, 203, 151, 122, 227, 173, 42]; +// https://github.com/MeteoraAg/zap-program/blob/4b67bfc64e5a023a1b74386be8b82c3908934a0b/idls/jupiter.json#L270 +pub const JUP_V6_SHARED_ACCOUNT_ROUTE_DISC: [u8; 8] = [193, 32, 155, 51, 65, 214, 156, 129]; + +pub const DLMM: Pubkey = Pubkey::from_str_const("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"); +// https://github.com/MeteoraAg/zap-program/blob/4b67bfc64e5a023a1b74386be8b82c3908934a0b/idls/dlmm.json#L5242-L5251 +pub const DLMM_SWAP2_DISC: [u8; 8] = [65, 75, 63, 76, 235, 91, 91, 136]; + +pub const DAMM_V2: Pubkey = Pubkey::from_str_const("cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"); +// https://github.com/MeteoraAg/zap-program/blob/4b67bfc64e5a023a1b74386be8b82c3908934a0b/idls/damm_v2.json#L2154 +pub const DAMM_V2_SWAP_DISC: [u8; 8] = [248, 198, 158, 145, 225, 117, 135, 200]; + +pub const ZAP: Pubkey = Pubkey::from_str_const("zapvX9M3uf5pvy4wRPAbQgdQsM1xmuiFnkfHKPvwMiz"); +pub const ZAP_OUT_DISC: [u8; 8] = [155, 108, 185, 112, 104, 210, 161, 64]; + +pub const USDC_ADDRESS: Pubkey = + Pubkey::from_str_const("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); + +pub const SOL_ADDRESS: Pubkey = + Pubkey::from_str_const("So11111111111111111111111111111111111111112"); + +pub const MINTS_DISALLOWED_TO_ZAP_OUT: [Pubkey; 2] = [USDC_ADDRESS, SOL_ADDRESS]; diff --git a/zap-sdk/src/damm_v2_zap.rs b/zap-sdk/src/damm_v2_zap.rs new file mode 100644 index 0000000..b2bbc21 --- /dev/null +++ b/zap-sdk/src/damm_v2_zap.rs @@ -0,0 +1,28 @@ +use crate::{ + constants::{ + DAMM_V2_SWAP_AMOUNT_IN_OFFSET, DAMM_V2_SWAP_DESTINATION_ACCOUNT_INDEX, + DAMM_V2_SWAP_SOURCE_ACCOUNT_INDEX, + }, + RawZapOutAmmInfo, ZapInfoProcessor, ZapOutParameters, +}; +use solana_program::entrypoint_deprecated::ProgramResult; +use solana_program_error::ProgramError; + +pub struct ZapDammV2InfoProcessor; + +impl ZapInfoProcessor for ZapDammV2InfoProcessor { + fn validate_payload(&self, _payload: &[u8]) -> ProgramResult { + Ok(()) + } + + fn extract_raw_zap_out_amm_info( + &self, + _zap_params: &ZapOutParameters, + ) -> Result { + Ok(RawZapOutAmmInfo { + source_index: DAMM_V2_SWAP_SOURCE_ACCOUNT_INDEX, + destination_index: DAMM_V2_SWAP_DESTINATION_ACCOUNT_INDEX, + amount_in_offset: DAMM_V2_SWAP_AMOUNT_IN_OFFSET, + }) + } +} diff --git a/zap-sdk/src/dlmm_zap.rs b/zap-sdk/src/dlmm_zap.rs new file mode 100644 index 0000000..12d2101 --- /dev/null +++ b/zap-sdk/src/dlmm_zap.rs @@ -0,0 +1,29 @@ +use solana_program::entrypoint_deprecated::ProgramResult; +use solana_program_error::ProgramError; + +use crate::{ + constants::{ + DLMM_SWAP2_AMOUNT_IN_OFFSET, DLMM_SWAP2_DESTINATION_ACCOUNT_INDEX, + DLMM_SWAP2_SOURCE_ACCOUNT_INDEX, + }, + RawZapOutAmmInfo, ZapInfoProcessor, ZapOutParameters, +}; + +pub struct ZapDlmmInfoProcessor; + +impl ZapInfoProcessor for ZapDlmmInfoProcessor { + fn validate_payload(&self, _payload: &[u8]) -> ProgramResult { + Ok(()) + } + + fn extract_raw_zap_out_amm_info( + &self, + _zap_params: &ZapOutParameters, + ) -> Result { + Ok(RawZapOutAmmInfo { + source_index: DLMM_SWAP2_SOURCE_ACCOUNT_INDEX, + destination_index: DLMM_SWAP2_DESTINATION_ACCOUNT_INDEX, + amount_in_offset: DLMM_SWAP2_AMOUNT_IN_OFFSET, + }) + } +} diff --git a/zap-sdk/src/error.rs b/zap-sdk/src/error.rs new file mode 100644 index 0000000..c5d7f34 --- /dev/null +++ b/zap-sdk/src/error.rs @@ -0,0 +1,53 @@ +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use solana_program::program_error::ProgramError; +use thiserror::Error; + +#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[repr(u32)] +pub enum ZapSdkError { + #[error("Math operation overflow")] + MathOverflow = 0, + + #[error("Invalid zapout parameters")] + InvalidZapOutParameters = 1, + + #[error("Type cast error")] + TypeCastFailed = 2, + + #[error("Missing zap out instruction")] + MissingZapOutInstruction = 3, + + #[error("Invalid withdraw protocol fee zap accounts")] + InvalidWithdrawProtocolFeeZapAccounts = 4, + + #[error("SOL,USDC protocol fee cannot be withdrawn via zap")] + MintRestrictedFromZap = 5, + + #[error("CPI disabled")] + CpiDisabled = 6, + + #[error("Invalid zap accounts")] + InvalidZapAccounts = 7, +} + +impl ZapSdkError { + pub fn name(&self) -> String { + match self { + Self::MathOverflow => "MathOverflow", + Self::InvalidZapOutParameters => "InvalidZapOutParameters", + Self::TypeCastFailed => "TypeCastFailed", + Self::MissingZapOutInstruction => "MissingZapOutInstruction", + Self::InvalidWithdrawProtocolFeeZapAccounts => "InvalidWithdrawProtocolFeeZapAccounts", + Self::MintRestrictedFromZap => "MintRestrictedFromZap", + Self::CpiDisabled => "CpiDisabled", + Self::InvalidZapAccounts => "InvalidZapAccounts", + } + .to_string() + } +} + +impl From for ProgramError { + fn from(e: ZapSdkError) -> Self { + ProgramError::Custom(e as u32) + } +} diff --git a/zap-sdk/src/jup_v6_zap.rs b/zap-sdk/src/jup_v6_zap.rs new file mode 100644 index 0000000..de3bbdf --- /dev/null +++ b/zap-sdk/src/jup_v6_zap.rs @@ -0,0 +1,157 @@ +use crate::{ + constants::{ + JUP_V6_ROUTE_AMOUNT_IN_REVERSE_OFFSET, JUP_V6_ROUTE_DESTINATION_ACCOUNT_INDEX, + JUP_V6_ROUTE_SOURCE_ACCOUNT_INDEX, JUP_V6_SHARED_ACCOUNT_ROUTE_AMOUNT_IN_REVERSE_OFFSET, + JUP_V6_SHARED_ACCOUNT_ROUTE_DESTINATION_ACCOUNT_INDEX, + JUP_V6_SHARED_ACCOUNT_ROUTE_SOURCE_ACCOUNT_INDEX, + }, + error::ZapSdkError, + safe_math::{SafeCast, SafeMath}, + RawZapOutAmmInfo, ZapInfoProcessor, ZapOutParameters, +}; +use borsh::BorshDeserialize; +use jupiter::types::RoutePlanStep; +use jupiter::types::Swap; +use solana_program_error::{ProgramError, ProgramResult}; + +pub struct ZapJupV6RouteInfoProcessor; + +fn ensure_whitelisted_swap_leg(route_plan_steps: &[RoutePlanStep]) -> ProgramResult { + for step in route_plan_steps { + match step.swap { + Swap::Meteora + | Swap::MeteoraDammV2 + | Swap::MeteoraDammV2WithRemainingAccounts + | Swap::MeteoraDlmm + | Swap::MeteoraDlmmSwapV2 { .. } + | Swap::Mercurial + | Swap::Whirlpool { .. } + | Swap::WhirlpoolSwapV2 { .. } + | Swap::Raydium + | Swap::RaydiumV2 + | Swap::RaydiumCP + | Swap::RaydiumClmm + | Swap::RaydiumClmmV2 => { + // whitelisted swap leg + } + _ => return Err(ZapSdkError::InvalidZapOutParameters.into()), + } + } + + Ok(()) +} + +/// Validates that the route plan fully converges +/// - Every input index (original and intermediate) must be 100% consumed +/// - All swap paths must converge to exactly one terminal output +pub(crate) fn ensure_route_plan_fully_converges( + route_plan_steps: &[RoutePlanStep], +) -> ProgramResult { + // Verify each unique input_index sums to exactly 100% + for (i, step) in route_plan_steps.iter().enumerate() { + // Only process first occurrence of each input_index + let seen = route_plan_steps[..i] + .iter() + .any(|s| s.input_index == step.input_index); + if seen { + continue; + } + + let percent_sum = route_plan_steps + .iter() + .filter(|s| s.input_index == step.input_index) + .try_fold(0u8, |acc, s| acc.checked_add(s.percent)) + .ok_or(ZapSdkError::MathOverflow)?; + + if percent_sum != 100 { + return Err(ZapSdkError::InvalidZapOutParameters.into()); + } + } + + // Count terminal outputs: unique outputs never used as inputs + let terminal_count = route_plan_steps + .iter() + .enumerate() + .filter(|(i, step)| { + let is_first = !route_plan_steps[..*i] + .iter() + .any(|s| s.output_index == step.output_index); + let is_terminal = !route_plan_steps + .iter() + .any(|s| s.input_index == step.output_index); + is_first && is_terminal + }) + .count(); + + if terminal_count != 1 { + return Err(ZapSdkError::InvalidZapOutParameters.into()); + } + + Ok(()) +} + +impl ZapInfoProcessor for ZapJupV6RouteInfoProcessor { + fn validate_payload(&self, payload: &[u8]) -> ProgramResult { + let route_params = jupiter::client::args::Route::try_from_slice(payload)?; + ensure_whitelisted_swap_leg(&route_params.route_plan)?; + ensure_route_plan_fully_converges(&route_params.route_plan)?; + + // Ensure no platform_fee_bps is 0, so operator can't steal funds by providing their account as platform_fee_account + if route_params.platform_fee_bps != 0 { + return Err(ZapSdkError::InvalidZapOutParameters.into()); + } + + Ok(()) + } + + fn extract_raw_zap_out_amm_info( + &self, + zap_params: &ZapOutParameters, + ) -> Result { + let amount_in_offset = zap_params + .payload_data + .len() + .safe_sub(JUP_V6_ROUTE_AMOUNT_IN_REVERSE_OFFSET)? + .safe_cast()?; + + Ok(RawZapOutAmmInfo { + source_index: JUP_V6_ROUTE_SOURCE_ACCOUNT_INDEX, + destination_index: JUP_V6_ROUTE_DESTINATION_ACCOUNT_INDEX, + amount_in_offset, + }) + } +} + +pub struct ZapJupV6SharedRouteInfoProcessor; + +impl ZapInfoProcessor for ZapJupV6SharedRouteInfoProcessor { + fn validate_payload(&self, payload: &[u8]) -> ProgramResult { + let route_params = jupiter::client::args::SharedAccountsRoute::try_from_slice(payload)?; + ensure_whitelisted_swap_leg(&route_params.route_plan)?; + ensure_route_plan_fully_converges(&route_params.route_plan)?; + + // Ensure no platform_fee_bps is 0, so operator can't steal funds by providing their account as platform_fee_account + if route_params.platform_fee_bps != 0 { + return Err(ZapSdkError::InvalidZapOutParameters.into()); + } + + Ok(()) + } + + fn extract_raw_zap_out_amm_info( + &self, + zap_params: &ZapOutParameters, + ) -> Result { + let amount_in_offset = zap_params + .payload_data + .len() + .safe_sub(JUP_V6_SHARED_ACCOUNT_ROUTE_AMOUNT_IN_REVERSE_OFFSET)? + .safe_cast()?; + + Ok(RawZapOutAmmInfo { + source_index: JUP_V6_SHARED_ACCOUNT_ROUTE_SOURCE_ACCOUNT_INDEX, + destination_index: JUP_V6_SHARED_ACCOUNT_ROUTE_DESTINATION_ACCOUNT_INDEX, + amount_in_offset, + }) + } +} diff --git a/zap-sdk/src/lib.rs b/zap-sdk/src/lib.rs new file mode 100644 index 0000000..58d60c1 --- /dev/null +++ b/zap-sdk/src/lib.rs @@ -0,0 +1,63 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::entrypoint::ProgramResult; +pub mod constants; +use constants::*; +pub mod damm_v2_zap; +use damm_v2_zap::*; +pub mod dlmm_zap; +use dlmm_zap::ZapDlmmInfoProcessor; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; + +use crate::{ + error::ZapSdkError, + jup_v6_zap::{ZapJupV6RouteInfoProcessor, ZapJupV6SharedRouteInfoProcessor}, +}; +pub mod error; +pub mod jup_v6_zap; +pub mod safe_math; +pub mod tests; +pub mod utils; + +#[derive(BorshSerialize, BorshDeserialize, PartialEq, Debug)] +pub struct ZapOutParameters { + pub percentage: u8, + pub offset_amount_in: u16, + pub pre_user_token_balance: u64, + pub max_swap_amount: u64, // avoid the issue someone send token to user token account when user zap out + pub payload_data: Vec, +} + +pub struct RawZapOutAmmInfo { + source_index: usize, + destination_index: usize, + amount_in_offset: u16, +} + +pub trait ZapInfoProcessor { + fn validate_payload(&self, payload: &[u8]) -> ProgramResult; + fn extract_raw_zap_out_amm_info( + &self, + zap_params: &ZapOutParameters, + ) -> Result; +} + +const DAMM_V2_SWAP_DISC_REF: &[u8] = &DAMM_V2_SWAP_DISC; +const DLMM_SWAP2_DISC_REF: &[u8] = &DLMM_SWAP2_DISC; +const JUP_V6_ROUTE_DISC_REF: &[u8] = &JUP_V6_ROUTE_DISC; +const JUP_V6_SHARED_ACCOUNT_ROUTE_DISC_REF: &[u8] = &JUP_V6_SHARED_ACCOUNT_ROUTE_DISC; + +pub fn get_zap_amm_processor( + amm_disc: &[u8], + amm_program_address: Pubkey, +) -> Result, ProgramError> { + match (amm_disc, amm_program_address) { + (DLMM_SWAP2_DISC_REF, DLMM) => Ok(Box::new(ZapDlmmInfoProcessor)), + (DAMM_V2_SWAP_DISC_REF, DAMM_V2) => Ok(Box::new(ZapDammV2InfoProcessor)), + (JUP_V6_ROUTE_DISC_REF, JUP_V6) => Ok(Box::new(ZapJupV6RouteInfoProcessor)), + (JUP_V6_SHARED_ACCOUNT_ROUTE_DISC_REF, JUP_V6) => { + Ok(Box::new(ZapJupV6SharedRouteInfoProcessor)) + } + _ => Err(ZapSdkError::InvalidZapOutParameters.into()), + } +} diff --git a/zap-sdk/src/safe_math.rs b/zap-sdk/src/safe_math.rs new file mode 100644 index 0000000..1bf0204 --- /dev/null +++ b/zap-sdk/src/safe_math.rs @@ -0,0 +1,104 @@ +use crate::error::ZapSdkError; +use ruint::aliases::U256; +use solana_program::msg; +use std::panic::Location; + +/// safe math module +pub trait SafeMath: Sized { + /// safe add + fn safe_add(self, rhs: Self) -> Result; + /// safe mul + fn safe_mul(self, rhs: Self) -> Result; + /// safe div + fn safe_div(self, rhs: Self) -> Result; + /// safe sub + fn safe_sub(self, rhs: Self) -> Result; +} + +macro_rules! checked_impl { + ($t:ty, $offset:ty) => { + impl SafeMath<$offset> for $t { + #[inline(always)] + fn safe_add(self, v: $t) -> Result<$t, ZapSdkError> { + match self.checked_add(v) { + Some(result) => Ok(result), + None => { + let caller = Location::caller(); + msg!("Math error thrown at {}:{}", caller.file(), caller.line()); + Err(ZapSdkError::MathOverflow) + } + } + } + + #[inline(always)] + fn safe_sub(self, v: $t) -> Result<$t, ZapSdkError> { + match self.checked_sub(v) { + Some(result) => Ok(result), + None => { + let caller = Location::caller(); + msg!("Math error thrown at {}:{}", caller.file(), caller.line()); + Err(ZapSdkError::MathOverflow) + } + } + } + + #[inline(always)] + fn safe_mul(self, v: $t) -> Result<$t, ZapSdkError> { + match self.checked_mul(v) { + Some(result) => Ok(result), + None => { + let caller = Location::caller(); + msg!("Math error thrown at {}:{}", caller.file(), caller.line()); + Err(ZapSdkError::MathOverflow) + } + } + } + + #[inline(always)] + fn safe_div(self, v: $t) -> Result<$t, ZapSdkError> { + match self.checked_div(v) { + Some(result) => Ok(result), + None => { + let caller = Location::caller(); + msg!("Math error thrown at {}:{}", caller.file(), caller.line()); + Err(ZapSdkError::MathOverflow) + } + } + } + } + }; +} + +checked_impl!(u16, u32); +checked_impl!(i32, u32); +checked_impl!(u32, u32); +checked_impl!(u64, u32); +checked_impl!(i64, u32); +checked_impl!(u128, u32); +checked_impl!(i128, u32); +checked_impl!(usize, u32); +checked_impl!(U256, usize); + +pub trait SafeCast: Sized { + fn safe_cast(self) -> Result; +} + +macro_rules! try_into_impl { + ($t:ty, $v:ty) => { + impl SafeCast<$v> for $t { + #[track_caller] + fn safe_cast(self) -> Result<$v, ZapSdkError> { + match self.try_into() { + Ok(result) => Ok(result), + Err(_) => { + let caller = Location::caller(); + msg!("TypeCast is failed at {}:{}", caller.file(), caller.line()); + Err(ZapSdkError::TypeCastFailed) + } + } + } + } + }; +} + +try_into_impl!(usize, u16); diff --git a/zap-sdk/src/tests/jup_v6_tests.rs b/zap-sdk/src/tests/jup_v6_tests.rs new file mode 100644 index 0000000..7ddb985 --- /dev/null +++ b/zap-sdk/src/tests/jup_v6_tests.rs @@ -0,0 +1,67 @@ +use jupiter::types::{RoutePlanStep, Swap}; + +use crate::jup_v6_zap::ensure_route_plan_fully_converges; + +#[test] +fn test_route_plan_converges_with_100_percent() { + let route_plan_1_market = vec![RoutePlanStep { + swap: Swap::Meteora, + percent: 100, + input_index: 0, + output_index: 1, + }]; + assert!(ensure_route_plan_fully_converges(&route_plan_1_market).is_ok()); + let route_plan_multi_market: Vec = vec![ + RoutePlanStep { + swap: Swap::Meteora, + percent: 50, + input_index: 0, + output_index: 1, + }, + RoutePlanStep { + swap: Swap::Raydium, + percent: 50, + input_index: 0, + output_index: 1, + }, + RoutePlanStep { + swap: Swap::MeteoraDlmm, + percent: 100, + input_index: 1, + output_index: 2, + }, + ]; + assert!(ensure_route_plan_fully_converges(&route_plan_multi_market).is_ok()); +} + +#[test] +fn test_route_plan_fails_with_partial_percent() { + let route_plan_1_market = vec![RoutePlanStep { + swap: Swap::Meteora, + percent: 50, + input_index: 0, + output_index: 1, + }]; + assert!(ensure_route_plan_fully_converges(&route_plan_1_market).is_err()); + let route_plan_1_market = vec![ + RoutePlanStep { + swap: Swap::Meteora, + percent: 100, + input_index: 0, + output_index: 1, + }, + RoutePlanStep { + swap: Swap::Raydium, + percent: 50, + input_index: 1, + output_index: 2, + }, + RoutePlanStep { + swap: Swap::MeteoraDlmm, + percent: 50, + input_index: 1, + output_index: 3, + }, + ]; + assert!(ensure_route_plan_fully_converges(&route_plan_1_market).is_err()); +} diff --git a/zap-sdk/src/tests/mod.rs b/zap-sdk/src/tests/mod.rs new file mode 100644 index 0000000..c15fefb --- /dev/null +++ b/zap-sdk/src/tests/mod.rs @@ -0,0 +1,5 @@ +#[cfg(test)] +pub mod jup_v6_tests; + +#[cfg(test)] +pub mod safe_math_tests; diff --git a/zap-sdk/src/tests/safe_math_tests.rs b/zap-sdk/src/tests/safe_math_tests.rs new file mode 100644 index 0000000..07e78be --- /dev/null +++ b/zap-sdk/src/tests/safe_math_tests.rs @@ -0,0 +1,30 @@ +#[cfg(test)] +use crate::safe_math::SafeMath; + +#[test] +fn safe_add() { + assert_eq!(u64::MAX.safe_add(u64::MAX).is_err(), true); + assert_eq!(100u64.safe_add(100u64).is_ok(), true); + assert_eq!(100u64.safe_add(100u64).unwrap(), 200u64); +} + +#[test] +fn safe_sub() { + assert_eq!(0u64.safe_sub(u64::MAX).is_err(), true); + assert_eq!(200u64.safe_sub(100u64).is_ok(), true); + assert_eq!(200u64.safe_sub(100u64).unwrap(), 100u64); +} + +#[test] +fn safe_mul() { + assert_eq!(u64::MAX.safe_mul(u64::MAX).is_err(), true); + assert_eq!(100u64.safe_mul(100u64).is_ok(), true); + assert_eq!(100u64.safe_mul(100u64).unwrap(), 10000u64); +} + +#[test] +fn safe_div() { + assert_eq!(100u64.safe_div(0u64).is_err(), true); + assert_eq!(200u64.safe_div(100u64).is_ok(), true); + assert_eq!(200u64.safe_div(100u64), Ok(2u64)); +} diff --git a/zap-sdk/src/utils.rs b/zap-sdk/src/utils.rs new file mode 100644 index 0000000..c0bb287 --- /dev/null +++ b/zap-sdk/src/utils.rs @@ -0,0 +1,222 @@ +use crate::constants::{SOL_ADDRESS, USDC_ADDRESS}; +use crate::error::ZapSdkError; +use crate::safe_math::SafeMath; +use crate::{constants, get_zap_amm_processor, RawZapOutAmmInfo, ZapOutParameters}; +use borsh::BorshDeserialize; +use solana_account_info::AccountInfo; +use solana_instruction::AccountMeta; +use solana_program::sysvar::instructions::{ + load_current_index_checked, load_instruction_at_checked, +}; +use solana_program_error::{ProgramError, ProgramResult}; +use solana_pubkey::Pubkey; +use spl_associated_token_account::get_associated_token_address; + +fn validate_zap_parameters<'info>( + zap_params: &ZapOutParameters, + max_claim_amount: u64, + amount_in_offset: u16, + claimer_token_account: &AccountInfo<'info>, +) -> ProgramResult { + if zap_params.percentage != 100 { + return Err(ZapSdkError::InvalidZapOutParameters.into()); + } + + if zap_params.offset_amount_in != amount_in_offset { + return Err(ZapSdkError::InvalidZapOutParameters.into()); + } + + // Ensure no stealing from operator by setting a higher pre_token_balance than actual balance to steal fund + // Eg: Operator set 100 pre balance, but actual balance is 0 + // Actual claimed amount is 300 + // Zap will attempt to swap post - pre = 300 - 100 = 200 + // Leftover 100 will be stolen by operator + if zap_params.pre_user_token_balance != accessor::amount(claimer_token_account)? { + return Err(ZapSdkError::InvalidZapOutParameters.into()); + } + + if zap_params.max_swap_amount < max_claim_amount { + return Err(ZapSdkError::InvalidZapOutParameters.into()); + } + + Ok(()) +} + +// Search for zap out instruction in the next instruction after the current one +fn search_and_validate_zap_out_instruction<'info>( + current_index: u16, + max_claim_amount: u64, + sysvar_instructions_account: &AccountInfo<'info>, + claimer_token_account: &AccountInfo<'info>, + treasury_address: Pubkey, + treasury_paired_destination_token_address: Pubkey, +) -> ProgramResult { + // Zap out instruction must be next to current instruction + let next_index = current_index.safe_add(1)?; + let ix = load_instruction_at_checked(next_index.into(), sysvar_instructions_account)?; + + if ix.program_id != constants::ZAP { + return Err(ZapSdkError::MissingZapOutInstruction.into()); + } + + let disc = ix + .data + .get(..8) + .ok_or_else(|| ZapSdkError::InvalidZapOutParameters)?; + + if disc != constants::ZAP_OUT_DISC { + return Err(ZapSdkError::MissingZapOutInstruction.into()); + } + + let zap_params = ZapOutParameters::try_from_slice(&ix.data[8..])?; + + let ZapOutAmmInfo { + zap_user_token_in_address, + amm_source_token_address: source_token_address, + amm_destination_token_address: destination_token_address, + amount_in_offset, + } = extract_amm_accounts_and_info(&zap_params, &ix.accounts)?; + + // Zap out from operator fee receiving account + validate_zap_parameters( + &zap_params, + max_claim_amount, + amount_in_offset, + claimer_token_account, + )?; + + // There's no validation to make sure that `user_token_in_account` is the same as `amm_source_token_address` + // Operator could steal the fund by providing a fake token account with 0 to bypass the zap swap invoke + // https://github.com/MeteoraAg/zap-program/blob/117e7d5586aa27cf97e6fde6266e25ee4e496f18/programs/zap/src/instructions/ix_zap_out.rs#L91 + if zap_user_token_in_address != *claimer_token_account.key { + return Err(ZapSdkError::InvalidZapAccounts.into()); + } + + // Zap out from operator fee receiving account + if source_token_address != *claimer_token_account.key { + return Err(ZapSdkError::InvalidZapAccounts.into()); + } + + let treasury_usdc_address = get_associated_token_address(&treasury_address, &USDC_ADDRESS); + let treasury_sol_address = get_associated_token_address(&treasury_address, &SOL_ADDRESS); + + // Zap to paired mint in the pool, or SOL, or USDC treasury + if destination_token_address != treasury_paired_destination_token_address + && destination_token_address != treasury_usdc_address + && destination_token_address != treasury_sol_address + { + return Err(ZapSdkError::InvalidZapAccounts.into()); + } + + Ok(()) +} + +pub fn validate_zap_out_to_treasury<'info>( + claimed_amount: u64, + calling_program_id: Pubkey, + claimer_token_account: &AccountInfo<'info>, + sysvar_instructions_account: &AccountInfo<'info>, + treasury_address: Pubkey, + treasury_paired_destination_token_address: Pubkey, +) -> ProgramResult { + let current_index = load_current_index_checked(sysvar_instructions_account)?; + + let current_instruction = + load_instruction_at_checked(current_index.into(), sysvar_instructions_account)?; + + // Ensure the instruction is direct instruction call + if current_instruction.program_id != calling_program_id { + return Err(ZapSdkError::CpiDisabled.into()); + } + + search_and_validate_zap_out_instruction( + current_index, + claimed_amount, + sysvar_instructions_account, + claimer_token_account, + treasury_address, + treasury_paired_destination_token_address, + )?; + + Ok(()) +} + +pub struct ZapOutAmmInfo { + // Account used to compare delta changes with pre_balance to decide swap amount + pub zap_user_token_in_address: Pubkey, + pub amm_source_token_address: Pubkey, + pub amm_destination_token_address: Pubkey, + pub amount_in_offset: u16, +} + +fn extract_amm_accounts_and_info( + zap_params: &ZapOutParameters, + zap_account: &[AccountMeta], +) -> Result { + // Accounts in ZapOutCtx + const ZAP_OUT_ACCOUNTS_LEN: usize = 2; + + let zap_user_token_in_address = zap_account + .get(0) + .map(|acc| acc.pubkey) + .ok_or_else(|| ZapSdkError::InvalidZapAccounts)?; + + let zap_amm_program_address = zap_account + .get(1) + .map(|acc| acc.pubkey) + .ok_or_else(|| ZapSdkError::InvalidZapAccounts)?; + + let amm_disc = zap_params + .payload_data + .get(..8) + .ok_or_else(|| ZapSdkError::InvalidZapOutParameters)?; + + let zap_info_processor = get_zap_amm_processor(amm_disc, zap_amm_program_address)?; + + let amm_payload = zap_params + .payload_data + .get(8..) + .ok_or_else(|| ZapSdkError::InvalidZapOutParameters)?; + + zap_info_processor.validate_payload(&amm_payload)?; + + let RawZapOutAmmInfo { + source_index, + destination_index, + amount_in_offset, + } = zap_info_processor.extract_raw_zap_out_amm_info(zap_params)?; + + // Start from remaining accounts of zap program + let amm_accounts = zap_account + .get(ZAP_OUT_ACCOUNTS_LEN..) + .ok_or_else(|| ZapSdkError::InvalidZapAccounts)?; + + let source_token_address = amm_accounts + .get(source_index) + .map(|acc| acc.pubkey) + .ok_or_else(|| ZapSdkError::InvalidZapAccounts)?; + + let destination_token_address = amm_accounts + .get(destination_index) + .map(|acc| acc.pubkey) + .ok_or_else(|| ZapSdkError::InvalidZapAccounts)?; + + Ok(ZapOutAmmInfo { + zap_user_token_in_address, + amm_source_token_address: source_token_address, + amm_destination_token_address: destination_token_address, + amount_in_offset, + }) +} + +mod accessor { + use solana_account_info::AccountInfo; + use solana_program_error::ProgramError; + + pub fn amount(account: &AccountInfo) -> Result { + let bytes = account.try_borrow_data()?; + let mut amount_bytes = [0u8; 8]; + amount_bytes.copy_from_slice(&bytes[64..72]); + Ok(u64::from_le_bytes(amount_bytes)) + } +}