diff --git a/idl/pump.json b/idl/pump.json index ff25a33..9977ae3 100644 --- a/idl/pump.json +++ b/idl/pump.json @@ -768,21 +768,6 @@ { "name": "fee_program", "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" - }, - { - "name": "bonding_curve_v2", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101, 45, 118, 50] - }, - { - "kind": "account", - "path": "mint" - } - ] - } } ], "args": [ @@ -1179,21 +1164,6 @@ { "name": "fee_program", "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" - }, - { - "name": "bonding_curve_v2", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101, 45, 118, 50] - }, - { - "kind": "account", - "path": "mint" - } - ] - } } ], "args": [ @@ -3987,21 +3957,6 @@ { "name": "fee_program", "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" - }, - { - "name": "bonding_curve_v2", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [98, 111, 110, 100, 105, 110, 103, 45, 99, 117, 114, 118, 101, 45, 118, 50] - }, - { - "kind": "account", - "path": "mint" - } - ] - } } ], "args": [ diff --git a/idl/pump_amm.json b/idl/pump_amm.json index 22d8332..47d7b95 100644 --- a/idl/pump_amm.json +++ b/idl/pump_amm.json @@ -688,21 +688,6 @@ { "name": "fee_program", "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" - }, - { - "name": "pool_v2", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [112, 111, 111, 108, 45, 118, 50] - }, - { - "kind": "account", - "path": "base_mint" - } - ] - } } ], "args": [ @@ -1134,21 +1119,6 @@ { "name": "fee_program", "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" - }, - { - "name": "pool_v2", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [112, 111, 111, 108, 45, 118, 50] - }, - { - "kind": "account", - "path": "base_mint" - } - ] - } } ], "args": [ @@ -3192,21 +3162,6 @@ { "name": "fee_program", "address": "pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ" - }, - { - "name": "pool_v2", - "pda": { - "seeds": [ - { - "kind": "const", - "value": [112, 111, 111, 108, 45, 118, 50] - }, - { - "kind": "account", - "path": "base_mint" - } - ] - } } ], "args": [ @@ -4880,7 +4835,7 @@ }, { "code": 6052, - "name": "TokensInVaultLessThanCashbackEarned" + "name": "CashbackEarnedDoesNotMatchTokenInVault" } ], "types": [ diff --git a/idl/pump_fees.json b/idl/pump_fees.json index fc21546..66ed115 100644 --- a/idl/pump_fees.json +++ b/idl/pump_fees.json @@ -7,6 +7,115 @@ "description": "Created with Anchor" }, "instructions": [ + { + "name": "claim_social_fee_pda", + "discriminator": [ + 225, + 21, + 251, + 133, + 161, + 30, + 199, + 226 + ], + "accounts": [ + { + "name": "recipient", + "writable": true + }, + { + "name": "social_fee_pda", + "writable": true + }, + { + "name": "fee_program_global", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 102, + 101, + 101, + 45, + 112, + 114, + 111, + 103, + 114, + 97, + 109, + 45, + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "social_claim_authority", + "signer": true, + "relations": [ + "fee_program_global" + ] + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "user_id", + "type": "string" + }, + { + "name": "platform", + "type": "u8" + } + ], + "returns": { + "option": { + "defined": { + "name": "SocialFeePdaClaimed" + } + } + } + }, { "name": "create_fee_sharing_config", "docs": [ @@ -371,6 +480,106 @@ ], "args": [] }, + { + "name": "create_social_fee_pda", + "discriminator": [ + 144, + 224, + 59, + 211, + 78, + 248, + 202, + 220 + ], + "accounts": [ + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "social_fee_pda", + "writable": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "fee_program_global", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 102, + 101, + 101, + 45, + 112, + 114, + 111, + 103, + 114, + 97, + 109, + 45, + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "user_id", + "type": "string" + }, + { + "name": "platform", + "type": "u8" + } + ] + }, { "name": "get_fees", "docs": [ @@ -528,6 +737,165 @@ ], "args": [] }, + { + "name": "initialize_fee_program_global", + "discriminator": [ + 35, + 215, + 130, + 84, + 233, + 56, + 124, + 167 + ], + "accounts": [ + { + "name": "authority", + "writable": true, + "signer": true, + "relations": [ + "pump_global" + ] + }, + { + "name": "pump_global", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ], + "program": { + "kind": "const", + "value": [ + 1, + 86, + 224, + 246, + 147, + 102, + 90, + 207, + 68, + 219, + 21, + 104, + 191, + 23, + 91, + 170, + 81, + 137, + 203, + 151, + 245, + 210, + 255, + 59, + 101, + 93, + 43, + 182, + 253, + 109, + 24, + 176 + ] + } + } + }, + { + "name": "fee_program_global", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 102, + 101, + 101, + 45, + 112, + 114, + 111, + 103, + 114, + 97, + 109, + 45, + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "social_claim_authority", + "type": "pubkey" + }, + { + "name": "disable_flags", + "type": "u8" + }, + { + "name": "claim_rate_limit", + "type": "u64" + } + ] + }, { "name": "reset_fee_sharing_config", "docs": [ @@ -825,6 +1193,374 @@ ], "args": [] }, + { + "name": "set_authority", + "discriminator": [ + 133, + 250, + 37, + 21, + 110, + 163, + 26, + 121 + ], + "accounts": [ + { + "name": "authority", + "writable": true, + "signer": true, + "relations": [ + "fee_program_global" + ] + }, + { + "name": "fee_program_global", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 102, + 101, + 101, + 45, + 112, + 114, + 111, + 103, + 114, + 97, + 109, + 45, + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "new_authority", + "type": "pubkey" + } + ] + }, + { + "name": "set_claim_rate_limit", + "discriminator": [ + 185, + 211, + 159, + 174, + 212, + 49, + 88, + 4 + ], + "accounts": [ + { + "name": "authority", + "writable": true, + "signer": true, + "relations": [ + "fee_program_global" + ] + }, + { + "name": "fee_program_global", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 102, + 101, + 101, + 45, + 112, + 114, + 111, + 103, + 114, + 97, + 109, + 45, + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "claim_rate_limit", + "type": "u64" + } + ] + }, + { + "name": "set_disable_flags", + "discriminator": [ + 194, + 217, + 112, + 35, + 114, + 222, + 51, + 190 + ], + "accounts": [ + { + "name": "authority", + "writable": true, + "signer": true, + "relations": [ + "fee_program_global" + ] + }, + { + "name": "fee_program_global", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 102, + 101, + 101, + 45, + 112, + 114, + 111, + 103, + 114, + 97, + 109, + 45, + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "disable_flags", + "type": "u8" + } + ] + }, + { + "name": "set_social_claim_authority", + "discriminator": [ + 147, + 54, + 184, + 154, + 136, + 237, + 185, + 153 + ], + "accounts": [ + { + "name": "authority", + "writable": true, + "signer": true, + "relations": [ + "fee_program_global" + ] + }, + { + "name": "fee_program_global", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 102, + 101, + 101, + 45, + 112, + 114, + 111, + 103, + 114, + 97, + 109, + 45, + 103, + 108, + 111, + 98, + 97, + 108 + ] + } + ] + } + }, + { + "name": "event_authority", + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 95, + 95, + 101, + 118, + 101, + 110, + 116, + 95, + 97, + 117, + 116, + 104, + 111, + 114, + 105, + 116, + 121 + ] + } + ] + } + }, + { + "name": "program" + } + ], + "args": [ + { + "name": "social_claim_authority", + "type": "pubkey" + } + ] + }, { "name": "transfer_fee_sharing_authority", "docs": [ @@ -1823,6 +2559,19 @@ 155 ] }, + { + "name": "FeeProgramGlobal", + "discriminator": [ + 162, + 165, + 245, + 49, + 29, + 37, + 55, + 242 + ] + }, { "name": "Global", "discriminator": [ @@ -1861,6 +2610,19 @@ 93, 75 ] + }, + { + "name": "SocialFeePda", + "discriminator": [ + 139, + 96, + 53, + 17, + 42, + 169, + 206, + 150 + ] } ], "events": [ @@ -1890,6 +2652,19 @@ 126 ] }, + { + "name": "InitializeFeeProgramGlobalEvent", + "discriminator": [ + 40, + 233, + 156, + 78, + 95, + 0, + 8, + 199 + ] + }, { "name": "ResetFeeSharingConfigEvent", "discriminator": [ @@ -1916,6 +2691,84 @@ 62 ] }, + { + "name": "SetAuthorityEvent", + "discriminator": [ + 18, + 175, + 132, + 66, + 208, + 201, + 87, + 242 + ] + }, + { + "name": "SetClaimRateLimitEvent", + "discriminator": [ + 13, + 143, + 143, + 235, + 181, + 19, + 51, + 40 + ] + }, + { + "name": "SetDisableFlagsEvent", + "discriminator": [ + 5, + 8, + 179, + 65, + 49, + 55, + 145, + 126 + ] + }, + { + "name": "SetSocialClaimAuthorityEvent", + "discriminator": [ + 60, + 118, + 127, + 132, + 239, + 52, + 254, + 14 + ] + }, + { + "name": "SocialFeePdaClaimed", + "discriminator": [ + 50, + 18, + 193, + 65, + 237, + 210, + 234, + 236 + ] + }, + { + "name": "SocialFeePdaCreated", + "discriminator": [ + 183, + 183, + 218, + 147, + 24, + 124, + 137, + 169 + ] + }, { "name": "TransferFeeSharingAuthorityEvent", "discriminator": [ @@ -2087,6 +2940,16 @@ "code": 6020, "name": "ShareholderAccountMismatch", "msg": "Remaining account key doesn't match shareholder address" + }, + { + "code": 6021, + "name": "FeatureDeactivated", + "msg": "Feature is currently deactivated" + }, + { + "code": 6022, + "name": "UserIdTooLong", + "msg": "User ID exceeds maximum length" } ], "types": [ @@ -2242,6 +3105,43 @@ ] } }, + { + "name": "FeeProgramGlobal", + "type": { + "kind": "struct", + "fields": [ + { + "name": "bump", + "type": "u8" + }, + { + "name": "authority", + "type": "pubkey" + }, + { + "name": "disable_flags", + "type": "u8" + }, + { + "name": "social_claim_authority", + "type": "pubkey" + }, + { + "name": "claim_rate_limit", + "type": "u64" + }, + { + "name": "_reserved", + "type": { + "array": [ + "u8", + 256 + ] + } + } + ] + } + }, { "name": "FeeTier", "type": { @@ -2400,6 +3300,34 @@ ] } }, + { + "name": "InitializeFeeProgramGlobalEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "authority", + "type": "pubkey" + }, + { + "name": "social_claim_authority", + "type": "pubkey" + }, + { + "name": "disable_flags", + "type": "u8" + }, + { + "name": "claim_rate_limit", + "type": "u64" + } + ] + } + }, { "name": "Pool", "type": { @@ -2524,6 +3452,74 @@ ] } }, + { + "name": "SetAuthorityEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "old_authority", + "type": "pubkey" + }, + { + "name": "new_authority", + "type": "pubkey" + } + ] + } + }, + { + "name": "SetClaimRateLimitEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "claim_rate_limit", + "type": "u64" + } + ] + } + }, + { + "name": "SetDisableFlagsEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "disable_flags", + "type": "u8" + } + ] + } + }, + { + "name": "SetSocialClaimAuthorityEvent", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "social_claim_authority", + "type": "pubkey" + } + ] + } + }, { "name": "Shareholder", "type": { @@ -2586,6 +3582,134 @@ ] } }, + { + "name": "SocialFeePda", + "docs": [ + "Platform identifier: 0=pump, 1=twitter, etc." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "bump", + "type": "u8" + }, + { + "name": "version", + "type": "u8" + }, + { + "name": "user_id", + "docs": [ + "Max 20 characters to fit u64::MAX (18,446,744,073,709,551,615) as a string.", + "Actual storage: 4 bytes (length prefix) + 20 bytes (content) = 24 bytes." + ], + "type": "string" + }, + { + "name": "platform", + "type": "u8" + }, + { + "name": "total_claimed", + "type": "u64" + }, + { + "name": "last_claimed", + "type": "u64" + }, + { + "name": "_reserved", + "type": { + "array": [ + "u8", + 128 + ] + } + } + ] + } + }, + { + "name": "SocialFeePdaClaimed", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "user_id", + "type": "string" + }, + { + "name": "platform", + "type": "u8" + }, + { + "name": "social_fee_pda", + "type": "pubkey" + }, + { + "name": "recipient", + "type": "pubkey" + }, + { + "name": "social_claim_authority", + "type": "pubkey" + }, + { + "name": "amount_claimed", + "type": "u64" + }, + { + "name": "claimable_before", + "type": "u64" + }, + { + "name": "lifetime_claimed", + "type": "u64" + }, + { + "name": "recipient_balance_before", + "type": "u64" + }, + { + "name": "recipient_balance_after", + "type": "u64" + } + ] + } + }, + { + "name": "SocialFeePdaCreated", + "type": { + "kind": "struct", + "fields": [ + { + "name": "timestamp", + "type": "i64" + }, + { + "name": "user_id", + "type": "string" + }, + { + "name": "platform", + "type": "u8" + }, + { + "name": "social_fee_pda", + "type": "pubkey" + }, + { + "name": "created_by", + "type": "pubkey" + } + ] + } + }, { "name": "TransferFeeSharingAuthorityEvent", "type": { @@ -2747,6 +3871,29 @@ "type": "bytes", "value": "[102, 101, 101, 95, 99, 111, 110, 102, 105, 103]" }, + { + "name": "FEE_PROGRAM_GLOBAL_SEED", + "type": { + "array": [ + "u8", + 18 + ] + }, + "value": "[102, 101, 101, 45, 112, 114, 111, 103, 114, 97, 109, 45, 103, 108, 111, 98, 97, 108]" + }, + { + "name": "PUMP_GLOBAL_SEED", + "docs": [ + "Bonding Curve Program Global Seed" + ], + "type": { + "array": [ + "u8", + 6 + ] + }, + "value": "[103, 108, 111, 98, 97, 108]" + }, { "name": "SHARING_CONFIG_SEED", "type": { @@ -2756,6 +3903,16 @@ ] }, "value": "[115, 104, 97, 114, 105, 110, 103, 45, 99, 111, 110, 102, 105, 103]" + }, + { + "name": "SOCIAL_FEE_PDA_SEED", + "type": { + "array": [ + "u8", + 14 + ] + }, + "value": "[115, 111, 99, 105, 97, 108, 45, 102, 101, 101, 45, 112, 100, 97]" } ] } \ No newline at end of file diff --git a/src/common/seed.rs b/src/common/seed.rs index b9481c5..cecc8d7 100644 --- a/src/common/seed.rs +++ b/src/common/seed.rs @@ -25,6 +25,15 @@ pub async fn update_rents(client: &SolanaRpcClient) -> Result<(), anyhow::Error> Ok(()) } +/// 165 字节 Token 账户的典型租金(lamports),RPC 超时时用作回退 +const DEFAULT_TOKEN_ACCOUNT_RENT: u64 = 2_039_280; + +/// 当 RPC 超时或不可用时设置默认租金,避免客户端创建卡死 +pub fn set_default_rents() { + SPL_TOKEN_RENT.store(DEFAULT_TOKEN_ACCOUNT_RENT, Ordering::Release); + SPL_TOKEN_2022_RENT.store(DEFAULT_TOKEN_ACCOUNT_RENT, Ordering::Release); +} + pub fn start_rent_updater(client: Arc) { tokio::spawn(async move { loop { diff --git a/src/instruction/pumpfun.rs b/src/instruction/pumpfun.rs index 72627e8..7029e53 100755 --- a/src/instruction/pumpfun.rs +++ b/src/instruction/pumpfun.rs @@ -9,8 +9,8 @@ use crate::{ use crate::{ instruction::utils::pumpfun::{ accounts, get_bonding_curve_pda, get_bonding_curve_v2_pda, get_creator, - get_user_volume_accumulator_pda, global_constants::{self}, BUY_DISCRIMINATOR, - BUY_EXACT_SOL_IN_DISCRIMINATOR, + get_mayhem_fee_recipient_meta_random, get_user_volume_accumulator_pda, + global_constants::{self}, BUY_DISCRIMINATOR, BUY_EXACT_SOL_IN_DISCRIMINATOR, }, utils::calc::{ common::{calculate_with_slippage_buy, calculate_with_slippage_sell}, @@ -137,9 +137,9 @@ impl InstructionBuilder for PumpFunInstructionBuilder { buy_data[16..24].copy_from_slice(&max_sol_cost.to_le_bytes()); } - // Determine fee recipient based on mayhem mode + // Determine fee recipient based on mayhem mode (pump-public-docs: 2nd account = Mayhem fee recipient; use any one randomly) let fee_recipient_meta = if is_mayhem_mode { - global_constants::MAYHEM_FEE_RECIPIENT_META + get_mayhem_fee_recipient_meta_random() } else { global_constants::FEE_RECIPIENT_META }; @@ -259,9 +259,9 @@ impl InstructionBuilder for PumpFunInstructionBuilder { sell_data[8..16].copy_from_slice(&token_amount.to_le_bytes()); sell_data[16..24].copy_from_slice(&min_sol_output.to_le_bytes()); - // Determine fee recipient based on mayhem mode + // Determine fee recipient based on mayhem mode (pump-public-docs: 2nd account = Mayhem fee recipient; use any one randomly) let fee_recipient_meta = if is_mayhem_mode { - global_constants::MAYHEM_FEE_RECIPIENT_META + get_mayhem_fee_recipient_meta_random() } else { global_constants::FEE_RECIPIENT_META }; diff --git a/src/instruction/pumpswap.rs b/src/instruction/pumpswap.rs index 069c54c..8644d0f 100755 --- a/src/instruction/pumpswap.rs +++ b/src/instruction/pumpswap.rs @@ -1,8 +1,8 @@ use crate::{ constants::trade::trade::DEFAULT_SLIPPAGE, instruction::utils::pumpswap::{ - accounts, fee_recipient_ata, get_pool_v2_pda, get_user_volume_accumulator_pda, - get_user_volume_accumulator_wsol_ata, BUY_DISCRIMINATOR, + accounts, fee_recipient_ata, get_mayhem_fee_recipient_random, get_pool_v2_pda, + get_user_volume_accumulator_pda, get_user_volume_accumulator_wsol_ata, BUY_DISCRIMINATOR, BUY_EXACT_QUOTE_IN_DISCRIMINATOR, SELL_DISCRIMINATOR, }, trading::{ @@ -119,16 +119,18 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { params.open_seed_optimize, ); - // Determine fee recipient based on mayhem mode + // Determine fee recipient based on mayhem mode (pump-public-docs: 10th = Mayhem fee recipient, 11th = WSOL ATA of Mayhem; use any one randomly) let is_mayhem_mode = protocol_params.is_mayhem_mode; - let fee_recipient = - if is_mayhem_mode { accounts::MAYHEM_FEE_RECIPIENT } else { accounts::FEE_RECIPIENT }; - let fee_recipient_meta = if is_mayhem_mode { - accounts::MAYHEM_FEE_RECIPIENT_META + let (fee_recipient, fee_recipient_meta) = if is_mayhem_mode { + get_mayhem_fee_recipient_random() } else { - accounts::FEE_RECIPIENT_META + (accounts::FEE_RECIPIENT, accounts::FEE_RECIPIENT_META) + }; + let fee_recipient_ata = if is_mayhem_mode { + fee_recipient_ata(fee_recipient, crate::constants::WSOL_TOKEN_ACCOUNT) + } else { + fee_recipient_ata(fee_recipient, quote_mint) }; - let fee_recipient_ata = fee_recipient_ata(fee_recipient, quote_mint); // ======================================== // Build instructions @@ -320,16 +322,18 @@ impl InstructionBuilder for PumpSwapInstructionBuilder { sol_amount = params.fixed_output_amount.unwrap(); } - // Determine fee recipient based on mayhem mode + // Determine fee recipient based on mayhem mode (pump-public-docs: 10th = Mayhem fee recipient, 11th = WSOL ATA of Mayhem; use any one randomly) let is_mayhem_mode = protocol_params.is_mayhem_mode; - let fee_recipient = - if is_mayhem_mode { accounts::MAYHEM_FEE_RECIPIENT } else { accounts::FEE_RECIPIENT }; - let fee_recipient_meta = if is_mayhem_mode { - accounts::MAYHEM_FEE_RECIPIENT_META + let (fee_recipient, fee_recipient_meta) = if is_mayhem_mode { + get_mayhem_fee_recipient_random() } else { - accounts::FEE_RECIPIENT_META + (accounts::FEE_RECIPIENT, accounts::FEE_RECIPIENT_META) + }; + let fee_recipient_ata = if is_mayhem_mode { + fee_recipient_ata(fee_recipient, crate::constants::WSOL_TOKEN_ACCOUNT) + } else { + fee_recipient_ata(fee_recipient, quote_mint) }; - let fee_recipient_ata = fee_recipient_ata(fee_recipient, quote_mint); let user_base_token_account = crate::common::fast_fn::get_associated_token_address_with_program_id_fast_use_seed( diff --git a/src/instruction/utils/pumpfun.rs b/src/instruction/utils/pumpfun.rs index 5431b4c..ff4b753 100644 --- a/src/instruction/utils/pumpfun.rs +++ b/src/instruction/utils/pumpfun.rs @@ -1,6 +1,7 @@ use crate::common::{bonding_curve::BondingCurveAccount, SolanaRpcClient}; use anyhow::anyhow; -use solana_sdk::pubkey::Pubkey; +use rand::seq::IndexedRandom; +use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey}; use std::sync::Arc; /// Constants used as seeds for deriving PDAs (Program Derived Addresses) @@ -59,8 +60,18 @@ pub mod global_constants { is_writable: true, }; - pub const MAYHEM_FEE_RECIPIENT: Pubkey = - pubkey!("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"); + /// Mayhem fee recipients (pump-public-docs: use any one randomly) + pub const MAYHEM_FEE_RECIPIENTS: [Pubkey; 8] = [ + pubkey!("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"), + pubkey!("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"), + pubkey!("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"), + pubkey!("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"), + pubkey!("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"), + pubkey!("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"), + pubkey!("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"), + pubkey!("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"), + ]; + pub const MAYHEM_FEE_RECIPIENT: Pubkey = MAYHEM_FEE_RECIPIENTS[0]; pub const MAYHEM_FEE_RECIPIENT_META: solana_sdk::instruction::AccountMeta = solana_sdk::instruction::AccountMeta { pubkey: MAYHEM_FEE_RECIPIENT, @@ -161,6 +172,19 @@ pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234]; pub const BUY_EXACT_SOL_IN_DISCRIMINATOR: [u8; 8] = [56, 252, 116, 8, 158, 223, 205, 95]; pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173]; +/// Returns a random Mayhem fee recipient AccountMeta (pump-public-docs: Bonding Curve 2nd account = Mayhem fee recipient; use any one randomly). +#[inline] +pub fn get_mayhem_fee_recipient_meta_random() -> AccountMeta { + let recipient = *global_constants::MAYHEM_FEE_RECIPIENTS + .choose(&mut rand::rng()) + .unwrap_or(&global_constants::MAYHEM_FEE_RECIPIENTS[0]); + AccountMeta { + pubkey: recipient, + is_signer: false, + is_writable: true, + } +} + pub struct Symbol; impl Symbol { diff --git a/src/instruction/utils/pumpswap.rs b/src/instruction/utils/pumpswap.rs index 352be58..ddc9a0c 100644 --- a/src/instruction/utils/pumpswap.rs +++ b/src/instruction/utils/pumpswap.rs @@ -2,12 +2,17 @@ use crate::{ common::{ spl_associated_token_account::get_associated_token_address_with_program_id, SolanaRpcClient, }, - constants::TOKEN_PROGRAM, + constants::{TOKEN_PROGRAM, WSOL_TOKEN_ACCOUNT}, instruction::utils::pumpswap_types::{pool_decode, Pool}, }; use anyhow::anyhow; +use rand::seq::IndexedRandom; use solana_account_decoder::UiAccountEncoding; -use solana_sdk::pubkey::Pubkey; +use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey}; + +/// PumpSwap 池账户总长度(见 pump-public-docs Breaking Change):8 字节 discriminator + 244 字节 Pool。 +/// 官方文档:pool structure needs to be 244 bytes (was 243),含 is_mayhem_mode。DataSize 必须与此一致,否则 getProgramAccounts 会返回 0。 +const POOL_ACCOUNT_DATA_LEN: u64 = 8 + 244; /// Constants used as seeds for deriving PDAs (Program Derived Addresses) pub mod seeds { @@ -29,6 +34,10 @@ pub mod seeds { /// Seed for pool v2 PDA (required by program upgrade, readonly at end of account list) pub const POOL_V2_SEED: &[u8] = b"pool-v2"; + /// Legacy pool PDA seed (used with index, creator, base_mint, quote_mint) + pub const POOL_SEED: &[u8] = b"pool"; + /// Pump program: pool-authority PDA seed (creator for canonical pool) + pub const POOL_AUTHORITY_SEED: &[u8] = b"pool-authority"; } /// Constants related to program accounts and authorities @@ -53,6 +62,8 @@ pub mod accounts { pubkey!("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"); pub const AMM_PROGRAM: Pubkey = pubkey!("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"); + /// Pump Bonding Curve program(canonical pool 的 creator 来自此程序的 pool-authority PDA) + pub const PUMP_PROGRAM_ID: Pubkey = pubkey!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); pub const LP_FEE_BASIS_POINTS: u64 = 25; pub const PROTOCOL_FEE_BASIS_POINTS: u64 = 5; @@ -68,9 +79,19 @@ pub mod accounts { pub const DEFAULT_COIN_CREATOR_VAULT_AUTHORITY: Pubkey = pubkey!("8N3GDaZ2iwN65oxVatKTLPNooAVUJTbfiVJ1ahyqwjSk"); - /// Mayhem fee recipient (for mayhem mode coins) - pub const MAYHEM_FEE_RECIPIENT: Pubkey = - pubkey!("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"); + /// Mayhem fee recipients (pump-public-docs: use any one randomly for throughput) + pub const MAYHEM_FEE_RECIPIENTS: [Pubkey; 8] = [ + pubkey!("GesfTA3X2arioaHp8bbKdjG9vJtskViWACZoYvxp4twS"), + pubkey!("4budycTjhs9fD6xw62VBducVTNgMgJJ5BgtKq7mAZwn6"), + pubkey!("8SBKzEQU4nLSzcwF4a74F2iaUDQyTfjGndn6qUWBnrpR"), + pubkey!("4UQeTP1T39KZ9Sfxzo3WR5skgsaP6NZa87BAkuazLEKH"), + pubkey!("8sNeir4QsLsJdYpc9RZacohhK1Y5FLU3nC5LXgYB4aa6"), + pubkey!("Fh9HmeLNUMVCvejxCtCL2DbYaRyBFVJ5xrWkLnMH6fdk"), + pubkey!("463MEnMeGyJekNZFQSTUABBEbLnvMTALbT6ZmsxAbAdq"), + pubkey!("6AUH3WEHucYZyC61hqpqYUWVto5qA5hjHuNQ32GNnNxA"), + ]; + /// Default Mayhem fee recipient (first of MAYHEM_FEE_RECIPIENTS) + pub const MAYHEM_FEE_RECIPIENT: Pubkey = MAYHEM_FEE_RECIPIENTS[0]; // META @@ -142,6 +163,20 @@ pub const BUY_DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234]; pub const BUY_EXACT_QUOTE_IN_DISCRIMINATOR: [u8; 8] = [198, 46, 21, 82, 180, 217, 232, 112]; pub const SELL_DISCRIMINATOR: [u8; 8] = [51, 230, 133, 164, 1, 127, 131, 173]; +/// Returns a random Mayhem fee recipient and its AccountMeta (pump-public-docs: use any one randomly). +#[inline] +pub fn get_mayhem_fee_recipient_random() -> (Pubkey, AccountMeta) { + let recipient = *accounts::MAYHEM_FEE_RECIPIENTS + .choose(&mut rand::rng()) + .unwrap_or(&accounts::MAYHEM_FEE_RECIPIENTS[0]); + let meta = AccountMeta { + pubkey: recipient, + is_signer: false, + is_writable: false, + }; + (recipient, meta) +} + /// Pool v2 PDA (seeds: ["pool-v2", base_mint]). Required at end of buy/sell/buy_exact_quote_in accounts. #[inline] pub fn get_pool_v2_pda(base_mint: &Pubkey) -> Option { @@ -152,6 +187,35 @@ pub fn get_pool_v2_pda(base_mint: &Pubkey) -> Option { Some(pda) } +/// Pump 程序上的 pool-authority PDA(canonical pool 的 creator),与 @pump-fun/pump-swap-sdk 一致。 +#[inline] +pub fn get_pump_pool_authority_pda(mint: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[seeds::POOL_AUTHORITY_SEED, mint.as_ref()], + &accounts::PUMP_PROGRAM_ID, + ) + .0 +} + +/// Canonical Pump 池 PDA:index=0,creator=pumpPoolAuthorityPda(mint),base_mint=mint,quote_mint=WSOL。 +/// 与 @pump-fun/pump-swap-sdk 的 canonicalPumpPoolPda(mint) 一致,用于从 bonding curve 迁移后的标准池查找。 +#[inline] +pub fn get_canonical_pool_pda(mint: &Pubkey) -> Pubkey { + const CANONICAL_POOL_INDEX: u16 = 0; + let authority = get_pump_pool_authority_pda(mint); + let (pda, _) = Pubkey::find_program_address( + &[ + seeds::POOL_SEED, + &CANONICAL_POOL_INDEX.to_le_bytes(), + authority.as_ref(), + mint.as_ref(), + WSOL_TOKEN_ACCOUNT.as_ref(), + ], + &accounts::AMM_PROGRAM, + ); + pda +} + // Find a pool for a specific mint pub async fn find_pool(rpc: &SolanaRpcClient, mint: &Pubkey) -> Result { let (pool_address, _) = find_by_mint(rpc, mint).await?; @@ -231,11 +295,12 @@ pub async fn find_by_base_mint( rpc: &SolanaRpcClient, base_mint: &Pubkey, ) -> Result<(Pubkey, Pool), anyhow::Error> { - // Use getProgramAccounts to find pools for the given mint + // Use getProgramAccounts to find pools for the given mint. + // base_mint 在账户布局中的偏移:8(discriminator) + 1(bump) + 2(index) + 32(creator) = 43 let filters = vec![ - // solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool account size + solana_rpc_client_api::filter::RpcFilterType::DataSize(POOL_ACCOUNT_DATA_LEN), solana_rpc_client_api::filter::RpcFilterType::Memcmp( - solana_client::rpc_filter::Memcmp::new_base58_encoded(43, &base_mint.to_bytes()), + solana_client::rpc_filter::Memcmp::new_base58_encoded(43, base_mint.as_ref()), ), ]; let config = solana_rpc_client_api::config::RpcProgramAccountsConfig { @@ -282,11 +347,12 @@ pub async fn find_by_quote_mint( rpc: &SolanaRpcClient, quote_mint: &Pubkey, ) -> Result<(Pubkey, Pool), anyhow::Error> { - // Use getProgramAccounts to find pools for the given mint + // Use getProgramAccounts to find pools for the given mint. + // quote_mint 在账户布局中的偏移:8 + 1 + 2 + 32 + 32 = 75 let filters = vec![ - // solana_rpc_client_api::filter::RpcFilterType::DataSize(211), // Pool account size + solana_rpc_client_api::filter::RpcFilterType::DataSize(POOL_ACCOUNT_DATA_LEN), solana_rpc_client_api::filter::RpcFilterType::Memcmp( - solana_client::rpc_filter::Memcmp::new_base58_encoded(75, "e_mint.to_bytes()), + solana_client::rpc_filter::Memcmp::new_base58_encoded(75, quote_mint.as_ref()), ), ]; let config = solana_rpc_client_api::config::RpcProgramAccountsConfig { @@ -329,17 +395,52 @@ pub async fn find_by_quote_mint( Ok((address, pool)) } +/// 按 mint 查找 PumpSwap 池(本函数仅用于 PumpSwap,其他 DEX 勿用)。 +/// +/// 查找顺序(与 @pump-fun/pump-swap-sdk 一致): +/// 1. Pool v2 PDA ["pool-v2", base_mint] — 一次 getAccount +/// 2. Canonical pool PDA ["pool", 0, pumpPoolAuthority(mint), mint, WSOL] — 迁移后的标准池 +/// 3. getProgramAccounts 按 base_mint / quote_mint 过滤 pub async fn find_by_mint( rpc: &SolanaRpcClient, mint: &Pubkey, ) -> Result<(Pubkey, Pool), anyhow::Error> { - if let Ok((address, pool)) = find_by_base_mint(rpc, mint).await { - return Ok((address, pool)); + let mut diag = Vec::::new(); + + // 1. PumpSwap v2 PDA(seeds: ["pool-v2", base_mint]) + if let Some(pool_address) = get_pool_v2_pda(mint) { + diag.push(format!("PDA(v2)={}", pool_address)); + match fetch_pool(rpc, &pool_address).await { + Ok(pool) if pool.base_mint == *mint => return Ok((pool_address, pool)), + Ok(_) => diag.push("PDA(v2) 账户存在但 base_mint 不匹配".into()), + Err(e) => diag.push(format!("PDA(v2) get_account/decode 失败: {}", e)), + } } - if let Ok((address, pool)) = find_by_quote_mint(rpc, mint).await { - return Ok((address, pool)); + + // 2. Canonical pool PDA(与 pump-swap-sdk canonicalPumpPoolPda(mint) 一致) + let canonical_address = get_canonical_pool_pda(mint); + diag.push(format!("canonical={}", canonical_address)); + match fetch_pool(rpc, &canonical_address).await { + Ok(pool) if pool.base_mint == *mint => return Ok((canonical_address, pool)), + Ok(_) => diag.push("canonical 账户存在但 base_mint 不匹配".into()), + Err(e) => diag.push(format!("canonical get_account/decode 失败: {}", e)), } - Err(anyhow!("No pool found for mint {}", mint)) + + // 3. 回退:getProgramAccounts 按 base_mint / quote_mint + match find_by_base_mint(rpc, mint).await { + Ok((address, pool)) => return Ok((address, pool)), + Err(e) => diag.push(format!("getProgramAccounts(base_mint): {}", e)), + } + match find_by_quote_mint(rpc, mint).await { + Ok((address, pool)) => return Ok((address, pool)), + Err(e) => diag.push(format!("getProgramAccounts(quote_mint): {}", e)), + } + + Err(anyhow!( + "No pool found for mint {}. 诊断: {}。若使用自建 RPC 请确认已开启 getProgramAccounts 或换用公共 RPC 重试;若代币未在 PumpSwap 建池请先在 pump.fun/DEX 上确认", + mint, + diag.join("; ") + )) } pub async fn get_token_balances( diff --git a/src/instruction/utils/pumpswap_types.rs b/src/instruction/utils/pumpswap_types.rs index 9410820..b44eaec 100644 --- a/src/instruction/utils/pumpswap_types.rs +++ b/src/instruction/utils/pumpswap_types.rs @@ -17,9 +17,12 @@ pub struct Pool { pub is_mayhem_mode: bool, /// Whether this pool's coin has cashback enabled pub is_cashback_coin: bool, + /// Reserved for future fields (pump-public-docs: pool structure = 244 bytes total) + pub _reserved: [u8; 7], } -pub const POOL_SIZE: usize = 1 + 2 + 32 * 6 + 8 + 32 + 1 + 1; +/// Borsh 解码用的 Pool 长度。链上池为 244 字节(pump-public-docs Breaking Change),与 POOL_SIZE 一致。 +pub const POOL_SIZE: usize = 244; pub fn pool_decode(data: &[u8]) -> Option { if data.len() < POOL_SIZE { diff --git a/src/lib.rs b/src/lib.rs index c229118..547c280 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,6 +54,25 @@ fn validate_protocol_params(dex_type: DexType, params: &DexParamEnum) -> bool { } } +/// 按 mint 查找池地址(通用入口,根据 DEX 类型分发,仅 PumpSwap 等已实现的类型会走优化路径)。 +/// +/// * `dex_type`:PumpSwap 时先走 PDA 再回退 getProgramAccounts,其他类型返回未实现错误。 +pub async fn find_pool_by_mint( + rpc: &SolanaRpcClient, + mint: &Pubkey, + dex_type: DexType, +) -> Result { + match dex_type { + DexType::PumpSwap => { + crate::instruction::utils::pumpswap::find_pool(rpc, mint).await + } + _ => Err(anyhow::anyhow!( + "find_pool_by_mint not implemented for {:?}", + dex_type + )), + } +} + /// Type of the token to buy #[derive(Clone, PartialEq)] pub enum TradeTokenType { @@ -97,27 +116,47 @@ impl TradingInfrastructure { config.commitment.clone(), )); - // Initialize rent cache and start background updater - common::seed::update_rents(&rpc).await.unwrap(); + // Initialize rent cache (with timeout so slow RPC doesn't block forever) + const RENT_UPDATE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15); + match tokio::time::timeout(RENT_UPDATE_TIMEOUT, common::seed::update_rents(&rpc)).await { + Ok(Ok(())) => {} + Ok(Err(e)) => { + if sdk_log::sdk_log_enabled() { + warn!(target: "sol_trade_sdk", "rent update failed: {}, using defaults", e); + } + common::seed::set_default_rents(); + } + Err(_) => { + if sdk_log::sdk_log_enabled() { + warn!(target: "sol_trade_sdk", "rent update timed out ({}s), using defaults; check RPC", RENT_UPDATE_TIMEOUT.as_secs()); + } + common::seed::set_default_rents(); + } + } common::seed::start_rent_updater(rpc.clone()); - // Create SWQOS clients with blacklist checking + // Create SWQOS clients with blacklist checking(单节点超时 5s,避免某一家卡死整段初始化) + const SWQOS_CLIENT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); let mut swqos_clients: Vec> = vec![]; for swqos in &config.swqos_configs { - // Check blacklist, skip disabled providers if swqos.is_blacklisted() { if sdk_log::sdk_log_enabled() { warn!(target: "sol_trade_sdk", "⚠️ SWQOS {:?} is blacklisted, skipping", swqos.swqos_type()); } continue; } - match SwqosConfig::get_swqos_client( - config.rpc_url.clone(), - config.commitment.clone(), - swqos.clone(), - ).await { - Ok(swqos_client) => swqos_clients.push(swqos_client), - Err(err) => { + match tokio::time::timeout( + SWQOS_CLIENT_TIMEOUT, + SwqosConfig::get_swqos_client( + config.rpc_url.clone(), + config.commitment.clone(), + swqos.clone(), + ), + ) + .await + { + Ok(Ok(swqos_client)) => swqos_clients.push(swqos_client), + Ok(Err(err)) => { if sdk_log::sdk_log_enabled() { warn!( target: "sol_trade_sdk", @@ -126,6 +165,16 @@ impl TradingInfrastructure { ); } } + Err(_) => { + if sdk_log::sdk_log_enabled() { + warn!( + target: "sol_trade_sdk", + "swqos {:?} init timed out ({}s), skipping", + swqos.swqos_type(), + SWQOS_CLIENT_TIMEOUT.as_secs() + ); + } + } } } @@ -348,7 +397,7 @@ impl TradingClient { } } - /// Helper to ensure WSOL ATA exists for a wallet + /// 确保钱包存在 WSOL ATA;不存在则发交易创建(会花费租金 + 手续费,初始化阶段唯一会扣钱的逻辑) async fn ensure_wsol_ata(payer: &Arc, rpc: &Arc) { let wsol_ata = crate::common::fast_fn::get_associated_token_address_with_program_id_fast( @@ -496,9 +545,32 @@ impl TradingClient { // Initialize wallet-specific caches crate::common::fast_fn::fast_init(&payer.pubkey()); - // Handle WSOL ATA creation if configured + // ═══════════════════════════════════════════════════════════════════════════════ + // 初始化阶段会花费租金/手续费的唯一路径:创建 WSOL ATA(ensure_wsol_ata) + // - 触发条件:create_wsol_ata_on_startup == true 且钱包 SOL >= MIN_SOL_FOR_WSOL_ATA_LAMPORTS + // - 花费:ATA 租金(约 0.00203928 SOL)+ 交易手续费;钱包不足时已跳过 + // - 其它初始化(TradingInfrastructure::new、update_rents、get_swqos_client)仅 RPC/HTTP,不发送交易 + // ═══════════════════════════════════════════════════════════════════════════════ if trade_config.create_wsol_ata_on_startup { - Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await; + const MIN_SOL_FOR_WSOL_ATA_LAMPORTS: u64 = 500_000; // 约 0.0005 SOL,用于 ATA 租金 + 手续费 + const BALANCE_CHECK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); + let balance = tokio::time::timeout( + BALANCE_CHECK_TIMEOUT, + infrastructure.rpc.get_balance(&payer.pubkey()), + ) + .await + .unwrap_or(Ok(0)) + .unwrap_or(0); + if balance >= MIN_SOL_FOR_WSOL_ATA_LAMPORTS { + Self::ensure_wsol_ata(&payer, &infrastructure.rpc).await; + } else if sdk_log::sdk_log_enabled() { + info!( + target: "sol_trade_sdk", + "⏭️ 跳过创建 WSOL ATA:钱包 SOL 不足(当前 {} lamports,需要至少 {})", + balance, + MIN_SOL_FOR_WSOL_ATA_LAMPORTS + ); + } } let instance = Self { diff --git a/src/trading/common/utils.rs b/src/trading/common/utils.rs index 68399c1..a36ab8e 100644 --- a/src/trading/common/utils.rs +++ b/src/trading/common/utils.rs @@ -2,7 +2,11 @@ use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transaction use solana_system_interface::instruction::transfer; use crate::common::{ - fast_fn::get_associated_token_address_with_program_id_fast, spl_token::close_account, + fast_fn::{ + get_associated_token_address_with_program_id_fast, + get_associated_token_address_with_program_id_fast_use_seed, + }, + spl_token::close_account, SolanaRpcClient, }; use anyhow::anyhow; @@ -36,10 +40,30 @@ pub async fn get_token_balance( payer: &Pubkey, mint: &Pubkey, ) -> Result { - let ata = crate::common::fast_fn::get_associated_token_address_with_program_id_fast( + get_token_balance_with_options( + rpc, payer, mint, &crate::constants::TOKEN_PROGRAM, + false, + ) + .await +} + +/// 使用与交易指令一致的 ATA 推导(可选 seed)查询余额;卖出/余额查询应与买入使用同一 ATA 地址。 +#[inline] +pub async fn get_token_balance_with_options( + rpc: &SolanaRpcClient, + payer: &Pubkey, + mint: &Pubkey, + token_program: &Pubkey, + use_seed: bool, +) -> Result { + let ata = get_associated_token_address_with_program_id_fast_use_seed( + payer, + mint, + token_program, + use_seed, ); let balance = rpc.get_token_account_balance(&ata).await?; let balance_u64 = diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 27005fd..b11e2e3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -31,6 +31,23 @@ impl TradingClient { trading::common::utils::get_token_balance(&self.infrastructure.rpc, &self.payer.pubkey(), mint).await } + /// 使用与交易一致的 ATA 推导(含 seed 优化)查询 payer 某 mint 的余额;卖出前查余额应使用此接口并传入池的 base_token_program,否则若使用 seed ATA 会查错账户。 + #[inline] + pub async fn get_payer_token_balance_with_program( + &self, + mint: &Pubkey, + token_program: &Pubkey, + ) -> Result { + trading::common::utils::get_token_balance_with_options( + &self.infrastructure.rpc, + &self.payer.pubkey(), + mint, + token_program, + self.use_seed_optimize, + ) + .await + } + #[inline] pub fn get_payer_pubkey(&self) -> Pubkey { self.payer.pubkey()