需求
- 调用 raydiumV4 swap指令,其中 1% 币本位手续费
- 手续费可配置
- 内置白名单地址,可以指定 (地址-币本位手续费),用于VIP用户收取不同手续费率
演示Swap:https://github.com/chainstacklabs/raydium-sdk-swap-example-typescript
说明
在Solana中,程序之间的调用机制与以太坊的智能合约调用有些不同。在Solana中,每个程序都运行在独立的账户空间中,并通过“调用程序”机制进行交互。这种交互是通过invoke和invoke_signed指令来实现的。invoke和invoke_signed指令允许一个Solana程序调用另一个程序的功能。
- https://solana.com/zh/docs/core/cpi
- https://beta.solpg.io/github.com/ZYJLiu/doc-examples/tree/main/cpi-invoke
- 重点看: https://www.bilibili.com/video/BV1G9k6Y7EbZ/
- https://leapwhale.com/article/9dk17j5q
- https://foresightnews.pro/article/detail/53242
Solana程序调用其他程序的基本步骤
- 创建目标程序的账户和结构: 目标程序必须已经部署,并且在调用时可以通过指定该程序的公钥来进行访问。
- 构造调用参数:调用目标程序时需要传递必要的参数(例如,交易数据、账户信息等),这些参数会通过账户传递给目标程序。
- 使用 invoke 或 invoke_signed 指令: Solana提供了这两种指令来允许一个程序调用另一个程序。
- invoke:普通程序调用。用于一般的程序调用,所有账户和签名都由调用者提供。
- invoke_signed:这是签名调用,通常用于需要授权的情况,或者在程序调用时需要用到特定的签名。
- 通常用于调用涉及多个权限或签名的操作。
- 处理返回值: 调用其他程序后,目标程序会返回相应的结果,这些结果可以用来继续处理后续逻辑。
示例:如何调用另一个Solana程序
假设你有两个程序,Program A 和 Program B,并且你想在Program A中调用Program B的某个功能。
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
pubkey::Pubkey,
program::{invoke, invoke_signed},
sysvar::rent::Rent,
msg,
};
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// 程序A的逻辑
msg!("Calling Program B...");
let program_b_id = Pubkey::new_from_array([/* Program B 的公钥 */]);
// 构造调用 Program B 所需的参数
let accounts_to_pass = &[
// 你需要传递的账户信息
];
// 使用 invoke 调用 Program B
invoke(
&solana_program::instruction::Instruction {
program_id: program_b_id,
accounts: accounts_to_pass.to_vec(),
data: instruction_data.to_vec(),
},
accounts, // 传递调用账户
)?;
msg!("Program B called successfully!");
Ok(())
}
关键点说明
invoke
和invoke_signed
的核心作用是允许一个程序调用另一个程序,传递所需的账户信息和数据。accounts
参数是传递给目标程序的账户信息,通常包含源账户、目标账户等。instruction_data
是要传递给目标程序的自定义数据(例如,目标程序的某个函数的参数)。program_b_id
是目标程序 Program B 的公钥。
如果目标程序需要授权特定账户或要求额外的签名,你应该使用 invoke_signed
,并提供相关的签名数据。
invoke_signed(
&instruction,
accounts,
&[&[b"signer", &[nonce]]], // 提供签名数据
)?;
注意事项
- 程序调用时的账户验证: 在调用其他程序时,必须确保传递给目标程序的账户是有效的,并且程序能够正确验证这些账户的状态。
- 目标程序的验证: 目标程序会根据传入的参数和账户来验证请求是否合法。如果目标程序期望特定的数据结构或账户状态,调用时需要严格遵守这些要求。
测试
-
Form: Djqepoij12599tw11qndggtZyqKTvJt6j2gf64b69NEF
-
To: routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS
-
地址: https://solscan.io/account/Djqepoij12599tw11qndggtZyqKTvJt6j2gf64b69NEF
相关地址:
- Access the full Open-Source repo
- https://github.com/raydium-io/raydium-amm
内部逻辑
process_swap_base_out_with_user_account:RouteSwapBaseOutArgs { max_amount_in: 5170758, amount_out: 1000000 }
调用 raydium swap 方法
编写Solana程序,核心是调用Raydium的Swap方法,我们需要了解几个关键组件和概念。
Raydium 是一个在 Solana 网络上运行的去中心化交易所(DEX),它利用了 Solana 的高速交易特性,并且和 Serum 的订单簿系统集成。Raydium的 swap
方法实际上是在执行一个交易操作,在不同的市场池之间进行代币的交换。
实现目标
- 通过 Solana 程序调用 Raydium 的 Swap 方法。
- 调用时需要传入必要的参数,如交易所的池地址、代币的源和目标账户等。
- 利用 Solana 的
invoke
或invoke_signed
指令调用 Raydium 的智能合约(这个合约通常是由 Raydium 提供的)。
关键概念
-
Raydium Swap:Raydium 提供了
swap
方法来进行代币交换,涉及从一个代币池中提取代币并向另一个代币池存入代币。 -
Token Swap 流程:需要理解代币池地址,输入代币账户、输出代币账户,池地址等信息。
-
调用 Raydium 合约:Raydium 的交易实现是在 Solana 程序中通过合约调用实现的,通常会通过特定的程序 ID 来调用 Raydium 的合约。
步骤概览
- 准备 Raydium 合约的程序 ID 和池地址。
- 获取并传递必要的账户信息:源代币账户、目标代币账户、Raydium池地址等。
- 使用
invoke
或invoke_signed
调用 Raydium 的 Swap 方法。
Solana 程序示例:调用 Raydium Swap
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
pubkey::Pubkey,
program::{invoke, invoke_signed},
msg,
sysvar::rent::Rent,
};
/// 这是我们程序调用Raydium的Swap方法
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8], // 用于传递 swap 相关的数据
) -> ProgramResult {
// 1. 定义 Raydium 的程序ID 和池地址
let raydium_program_id = Pubkey::new_from_array([/* Raydium 程序公钥 */]);
// Raydium池地址,假设这里是一个示例池地址
let raydium_pool_address = Pubkey::new_from_array([/* Raydium 池地址公钥 */]);
// 2. 准备传递给 Raydium 的账户信息
let source_token_account = &accounts[0]; // 输入代币账户
let destination_token_account = &accounts[1]; // 输出代币账户
let pool_token_account = &accounts[2]; // 池中的代币账户
let authority_account = &accounts[3]; // 授权账户
// 3. 构造调用 Raydium Swap 的数据
// 这里需要根据 Raydium 的 Swap 合约的具体数据结构来构造,假设我们已经知道如何构造数据
let swap_data = instruction_data.to_vec(); // 在真实实现中,应该根据Raydium的Swap接口构造正确的instruction_data
msg!("Calling Raydium swap...");
// 4. 调用 Raydium 程序的 swap 方法
let instruction = solana_program::instruction::Instruction {
program_id: raydium_program_id,
accounts: vec![
// 这里指定需要传递给 Raydium 的账户
AccountMeta::new(*source_token_account.key, false), // 输入代币账户
AccountMeta::new(*destination_token_account.key, false), // 输出代币账户
AccountMeta::new(*pool_token_account.key, false), // 池代币账户
AccountMeta::new(*authority_account.key, true), // 授权账户,通常需要签名
],
data: swap_data, // 传递 Swap 数据
};
// 5. 执行 invoke 调用 Raydium 的 Swap 合约
invoke(
&instruction,
accounts, // 必须提供参与交易的账户信息
)?;
msg!("Raydium swap executed successfully!");
Ok(())
}
关键点说明
-
Raydium 程序 ID:
raydium_program_id
是 Raydium 合约的程序公钥。你需要从 Raydium 获取到这个程序公钥。 -
账户传递:在这个示例中,
accounts
数组中包含了多个账户,分别代表源代币账户、目标代币账户、池代币账户和授权账户。实际情况中,根据 Raydium 的要求,你可能需要传递更多或不同的账户。 -
Swap 数据构造:
instruction_data
是你调用 Raydium 的swap
方法时需要传递的数据。在这里,我们假设你已经通过某种方式(例如 Raydium SDK 或 API)构造好了正确的交换数据。 -
调用
invoke
方法:我们使用invoke
方法来调用 Raydium 程序。你需要确保传递的账户是正确的,并且每个账户都有必要的权限。 -
返回结果:调用成功后,你可以根据实际情况处理返回的结果。
实际部署与运行
在实际使用中,你需要确保以下几点:
-
Raydium 池地址:确保你使用的是正确的池地址。Raydium 上有多个不同的池,你需要使用你想要交换的特定池的地址。
-
代币账户:确保你有源代币账户和目标代币账户,并且它们已经正确设置并且具备足够的余额。
-
授权账户:如果操作需要签名,你需要使用正确的授权账户。
-
调试与测试:在开发过程中,可以使用 Solana Devnet 或 Testnet 进行调试和测试,确保程序逻辑正确。
结语
这个示例展示了如何在 Solana 程序中调用 Raydium 的 Swap 方法。在实际开发中,你可能还需要参考 Raydium 官方文档和 API,确保调用数据结构和合约逻辑的准确性。