iSwap —— DEX 交换指令详解

本文档详细拆解 iSwap 协议中 DEX 交换相关指令的代码实现,包括 Raydium、Meteora 和 Jupiter 三种 DEX 的集成。


概述

iSwap 集成了三个主流 Solana DEX,提供代币交换能力:

DEX 类型 特点
Raydium AMM 经典 AMM,基于 OpenBook 订单簿
Meteora Dynamic AMM 动态流动性池
Jupiter 聚合器 多 DEX 路由,最优价格

两种交换模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────────┐
│ 用户直控模型 (User-controlled) │
│ ├── ray_swap_in │
│ └── meteora_swap_in │
│ │
│ 特点:用户直接签名,合约转发意图 │
│ 适用:即时交换、单次交易 │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 程序托管模型 (Program-controlled) │
│ └── jup_swap_in │
│ │
│ 特点:vault PDA 签名,程序自主操作 │
│ 适用:策略执行、自动化交易、金库管理 │
└─────────────────────────────────────────────────────────────┘

为什么设计不同?

  • 用户直控:用户在线签名,适合手动交易场景
  • 程序托管:用户不在线时,程序可以自主执行交换(如自动化策略、定时任务等)

1. ray_swap_in(Raydium 交换)

通过 CPI 调用 Raydium AMM 进行代币交换。

1.1 指令参数

参数 类型 说明
amount_in u64 输入代币数量
minimum_amount_out u64 最少输出代币数量(滑点保护)

1.2 账户约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// programs/iswap/src/instructions/ray_swap.rs
use anchor_lang::prelude::*;
use anchor_spl::token::Token;
use raydium_amm_cpi::SwapBaseIn;

#[derive(Accounts, Clone)]
pub struct RaySwapIn<'info> {
// ============ Raydium AMM 账户 ============

/// Raydium AMM 程序
/// CHECK: Safe
pub amm_program: UncheckedAccount<'info>,

/// AMM 账户
/// CHECK: Safe. amm Account
#[account(mut)]
pub amm: UncheckedAccount<'info>,

/// AMM 权限账户(PDA)
/// CHECK: Safe. Amm authority Account
#[account()]
pub amm_authority: UncheckedAccount<'info>,

/// AMM OpenOrders 账户
/// CHECK: Safe. amm open_orders Account
#[account(mut)]
pub amm_open_orders: UncheckedAccount<'info>,

/// AMM Coin Vault(代币 A 储备)
/// CHECK: Safe. amm_coin_vault Amm Account to swap FROM or To
#[account(mut)]
pub amm_coin_vault: UncheckedAccount<'info>,

/// AMM PC Vault(代币 B 储备)
/// CHECK: Safe. amm_pc_vault Amm Account to swap FROM or To
#[account(mut)]
pub amm_pc_vault: UncheckedAccount<'info>,

// ============ OpenBook 市场账户 ============

/// OpenBook 程序
/// CHECK: Safe. OpenBook program id
pub market_program: UncheckedAccount<'info>,

/// OpenBook 市场账户
/// CHECK: Safe. OpenBook market Account
#[account(mut)]
pub market: UncheckedAccount<'info>,

/// 市场买单簿
/// CHECK: Safe. bids Account
#[account(mut)]
pub market_bids: UncheckedAccount<'info>,

/// 市场卖单簿
/// CHECK: Safe. asks Account
#[account(mut)]
pub market_asks: UncheckedAccount<'info>,

/// 市场事件队列
/// CHECK: Safe. event_q Account
#[account(mut)]
pub market_event_queue: UncheckedAccount<'info>,

/// 市场 Coin Vault
/// CHECK: Safe. coin_vault Account
#[account(mut)]
pub market_coin_vault: UncheckedAccount<'info>,

/// 市场 PC Vault
/// CHECK: Safe. pc_vault Account
#[account(mut)]
pub market_pc_vault: UncheckedAccount<'info>,

/// 市场 Vault Signer(PDA)
/// CHECK: Safe. vault_signer Account
#[account(mut)]
pub market_vault_signer: UncheckedAccount<'info>,

// ============ 用户账户 ============

/// 用户的源代币账户(输入)
/// CHECK: Safe. user source token Account
#[account(mut)]
pub user_token_source: UncheckedAccount<'info>,

/// 用户的目标代币账户(输出)
/// CHECK: Safe. user destination token Account
#[account(mut)]
pub user_token_destination: UncheckedAccount<'info>,

/// 用户(签名者)
/// CHECK: Safe. user owner Account
#[account(mut)]
pub user_source_owner: Signer<'info>,

/// SPL Token 程序
/// CHECK: Safe. The spl token program
pub token_program: Program<'info, Token>,
}

1.3 指令逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
// programs/iswap/src/instructions/ray_swap.rs
pub fn ray_swap_base_in(
ctx: Context<RaySwapIn>,
amount_in: u64,
minimum_amount_out: u64,
) -> Result<()> {
// 直接 CPI 调用 Raydium AMM 的 swap_base_in 指令
raydium_amm_cpi::swap_base_in(
ctx.accounts.into(), // 转换为 Raydium CPI 账户结构
amount_in,
minimum_amount_out
)
}

1.4 CPI 上下文转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// programs/iswap/src/instructions/ray_swap.rs
impl<'a, 'b, 'c, 'info> From<&mut RaySwapIn<'info>>
for CpiContext<'a, 'b, 'c, 'info, SwapBaseIn<'info>>
{
fn from(accounts: &mut RaySwapIn<'info>) -> CpiContext<'a, 'b, 'c, 'info, SwapBaseIn<'info>> {
let cpi_accounts = SwapBaseIn {
amm: accounts.amm.clone(),
amm_authority: accounts.amm_authority.clone(),
amm_open_orders: accounts.amm_open_orders.clone(),
amm_coin_vault: accounts.amm_coin_vault.clone(),
amm_pc_vault: accounts.amm_pc_vault.clone(),
market_program: accounts.market_program.clone(),
market: accounts.market.clone(),
market_bids: accounts.market_bids.clone(),
market_asks: accounts.market_asks.clone(),
market_event_queue: accounts.market_event_queue.clone(),
market_coin_vault: accounts.market_coin_vault.clone(),
market_pc_vault: accounts.market_pc_vault.clone(),
market_vault_signer: accounts.market_vault_signer.clone(),
user_token_source: accounts.user_token_source.clone(),
user_token_destination: accounts.user_token_destination.clone(),
user_source_owner: accounts.user_source_owner.clone(),
token_program: accounts.token_program.clone(),
};
let cpi_program = accounts.amm_program.to_account_info();
CpiContext::new(cpi_program, cpi_accounts)
}
}

1.5 资金流向

1
2
用户源代币账户 ──amount_in──▶ AMM Vault
AMM Vault ──amount_out──▶ 用户目标代币账户

2. meteora_swap_in(Meteora 交换)

通过 CPI 调用 Meteora Dynamic AMM 进行代币交换。

2.1 指令参数

参数 类型 说明
amount_in u64 输入代币数量
min_amount_out u64 最少输出代币数量(滑点保护)

2.2 账户约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// programs/iswap/src/instructions/meteora_swap.rs
use crate::dynamic_amm;
use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct MeteoraSwapIn<'info> {
// ============ Meteora 池子账户 ============

/// 流动性池账户(PDA)
/// CHECK: Pool account (PDA)
#[account(mut)]
pub pool: UncheckedAccount<'info>,

// ============ 用户账户 ============

/// 用户源代币账户(输入)
/// CHECK: User token account. Token from this account will be transferred
/// into the vault by the pool in exchange for another token.
#[account(mut)]
pub user_source_token: UncheckedAccount<'info>,

/// 用户目标代币账户(输出)
/// CHECK: User token account. The exchanged token will be transferred
/// into this account from the pool.
#[account(mut)]
pub user_destination_token: UncheckedAccount<'info>,

// ============ Vault 账户 ============

/// Token A 的 Vault 账户
/// CHECK: Vault account for token a
#[account(mut)]
pub a_vault: UncheckedAccount<'info>,

/// Token B 的 Vault 账户
/// CHECK: Vault account for token b
#[account(mut)]
pub b_vault: UncheckedAccount<'info>,

/// Vault A 的 Token 账户
/// CHECK: Token vault account of vault A
#[account(mut)]
pub a_token_vault: UncheckedAccount<'info>,

/// Vault B 的 Token 账户
/// CHECK: Token vault account of vault B
#[account(mut)]
pub b_token_vault: UncheckedAccount<'info>,

// ============ Vault LP 相关 ============

/// Vault A 的 LP Token Mint
/// CHECK: Lp token mint of vault a
#[account(mut)]
pub a_vault_lp_mint: UncheckedAccount<'info>,

/// Vault B 的 LP Token Mint
/// CHECK: Lp token mint of vault b
#[account(mut)]
pub b_vault_lp_mint: UncheckedAccount<'info>,

/// Vault A 的 LP Token 账户
/// CHECK: LP token account of vault A
#[account(mut)]
pub a_vault_lp: UncheckedAccount<'info>,

/// Vault B 的 LP Token 账户
/// CHECK: LP token account of vault B
#[account(mut)]
pub b_vault_lp: UncheckedAccount<'info>,

// ============ 费用与程序 ============

/// 协议费用账户
/// CHECK: Protocol fee token account
#[account(mut)]
pub protocol_token_fee: UncheckedAccount<'info>,

/// 用户(签名者)
/// CHECK: User account. Must be owner of user_source_token.
pub user: Signer<'info>,

/// Meteora Vault 程序
/// CHECK: Vault program
pub vault_program: UncheckedAccount<'info>,

/// SPL Token 程序
/// CHECK: Token program
pub token_program: UncheckedAccount<'info>,

/// Meteora Dynamic AMM 程序
/// CHECK: Dynamic AMM program account
#[account(address = dynamic_amm::ID)]
pub dynamic_amm_program: UncheckedAccount<'info>,
}

2.3 指令逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// programs/iswap/src/instructions/meteora_swap.rs

/// 执行 Meteora Dynamic AMM 交换
///
/// # 参数
/// * `ctx` - 包含所有账户和程序的上下文
/// * `in_amount` - 输入代币数量
/// * `minimum_out_amount` - 最少输出代币数量(滑点保护)
///
/// # 返回
/// 返回 `Result` 表示成功或失败
pub fn meteora_swap_in(
ctx: Context<MeteoraSwapIn>,
in_amount: u64,
minimum_out_amount: u64,
) -> Result<()> {
// 1. 构建 Meteora CPI 账户结构
let accounts = dynamic_amm::cpi::accounts::Swap {
pool: ctx.accounts.pool.to_account_info(),
user_source_token: ctx.accounts.user_source_token.to_account_info(),
user_destination_token: ctx.accounts.user_destination_token.to_account_info(),
a_vault: ctx.accounts.a_vault.to_account_info(),
b_vault: ctx.accounts.b_vault.to_account_info(),
a_token_vault: ctx.accounts.a_token_vault.to_account_info(),
b_token_vault: ctx.accounts.b_token_vault.to_account_info(),
a_vault_lp_mint: ctx.accounts.a_vault_lp_mint.to_account_info(),
b_vault_lp_mint: ctx.accounts.b_vault_lp_mint.to_account_info(),
a_vault_lp: ctx.accounts.a_vault_lp.to_account_info(),
b_vault_lp: ctx.accounts.b_vault_lp.to_account_info(),
protocol_token_fee: ctx.accounts.protocol_token_fee.to_account_info(),
user: ctx.accounts.user.to_account_info(),
vault_program: ctx.accounts.vault_program.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
};

// 2. 创建 CPI 上下文
let cpi_context = CpiContext::new(
ctx.accounts.dynamic_amm_program.to_account_info(),
accounts
);

// 3. 调用 Meteora swap 指令
dynamic_amm::cpi::swap(cpi_context, in_amount, minimum_out_amount)
}

2.4 Meteora 架构说明

Meteora Dynamic AMM 采用双层 Vault 架构:

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────────────────────┐
│ Pool │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ A Vault │ │ B Vault │ │
│ │ ┌───────┐ │ │ ┌───────┐ │ │
│ │ │Token A│ │ │ │Token B│ │ │
│ │ │ Vault │ │ │ │ Vault │ │ │
│ │ └───────┘ │ │ └───────┘ │ │
│ │ LP Mint │ │ LP Mint │ │
│ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────┘

3. jup_swap_in(Jupiter 交换)

通过 vault PDA 调用 Jupiter 聚合器进行代币交换。

3.1 设计特点

与 Raydium/Meteora 不同,Jupiter 交换使用程序托管模型

对比项 Ray/Meteora Jupiter
资金来源 用户 ATA vault PDA 的 ATA
签名者 用户 vault PDA
适用场景 即时交换 自动化/策略执行

3.2 指令参数

参数 类型 说明
data Vec Jupiter 路由指令数据(由 Jupiter API 生成)

3.3 账户约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// programs/iswap/src/instructions/jup_swap.rs
use anchor_lang::{
prelude::*,
solana_program::{instruction::Instruction, program::invoke_signed},
};
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
use crate::jupiter_aggregator::program::Jupiter;

const VAULT_SEED: &[u8] = b"vault"; // vault PDA 种子

#[derive(Accounts)]
pub struct JupSwapIn<'info> {
// ============ Token Mint 账户 ============

/// 输入代币 Mint
pub input_mint: InterfaceAccount<'info, Mint>,

/// 输入代币的 Token 程序(支持 Token-2022)
pub input_mint_program: Interface<'info, TokenInterface>,

/// 输出代币 Mint
pub output_mint: InterfaceAccount<'info, Mint>,

/// 输出代币的 Token 程序(支持 Token-2022)
pub output_mint_program: Interface<'info, TokenInterface>,

// ============ Vault PDA 账户 ============

/// Vault 账户(PDA)
/// Seeds: ["vault"]
/// 作为交换的签名者和资金托管方
#[account(
mut,
seeds = [VAULT_SEED],
bump
)]
pub vault: SystemAccount<'info>,

/// Vault 的输入代币 ATA
#[account(
mut,
associated_token::mint = input_mint,
associated_token::authority = vault,
associated_token::token_program = input_mint_program,
)]
pub vault_input_token_account: InterfaceAccount<'info, TokenAccount>,

/// Vault 的输出代币 ATA
#[account(
mut,
associated_token::mint = output_mint,
associated_token::authority = vault,
associated_token::token_program = output_mint_program,
)]
pub vault_output_token_account: InterfaceAccount<'info, TokenAccount>,

// ============ 程序账户 ============

/// Jupiter 聚合器程序
pub jupiter_program: Program<'info, Jupiter>,
}

3.4 指令逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// programs/iswap/src/instructions/jup_swap.rs
use std::str::FromStr;

/// Jupiter 程序 ID
pub fn jupiter_program_id() -> Pubkey {
Pubkey::from_str("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4").unwrap()
}

pub fn jup_swap_in(ctx: Context<JupSwapIn>, data: Vec<u8>) -> Result<()> {
// 1. 验证 Jupiter 程序地址
require_keys_eq!(*ctx.accounts.jupiter_program.key, jupiter_program_id());

// 2. 构建账户元数据列表
// 将 vault PDA 设为签名者
let accounts: Vec<AccountMeta> = ctx
.remaining_accounts
.iter()
.map(|acc| {
let is_signer = acc.key == &ctx.accounts.vault.key(); // vault 是签名者
AccountMeta {
pubkey: *acc.key,
is_signer,
is_writable: acc.is_writable,
}
})
.collect();

// 3. 收集账户信息
let accounts_infos: Vec<AccountInfo> = ctx
.remaining_accounts
.iter()
.map(|acc| AccountInfo { ..acc.clone() })
.collect();

// 4. 构建 PDA 签名种子
let signer_seeds: &[&[&[u8]]] = &[&[VAULT_SEED, &[ctx.bumps.vault]]];

// 5. 调用 Jupiter 程序(使用 PDA 签名)
invoke_signed(
&Instruction {
program_id: ctx.accounts.jupiter_program.key(),
accounts,
data, // Jupiter 路由指令数据
},
&accounts_infos,
signer_seeds, // vault PDA 签名
)?;

Ok(())
}

3.5 资金流向

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────────────────────────────────────┐
│ Vault PDA │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Input Token ATA │ ──swap──▶│ Output Token ATA│ │
│ │ (减少) │ │ (增加) │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘


Jupiter 聚合器
(多 DEX 路由)

3.6 使用场景

由于 vault PDA 托管资金并作为签名者,适用于:

  1. 自动化策略:定时执行的交换任务
  2. 金库管理:协议金库的资产调整
  3. 代付交易:用户不需要直接签名的场景

4. 两种模型对比

4.1 用户直控模型(Raydium / Meteora)

1
2
3
// 用户直接签名
pub user_source_owner: Signer<'info>, // Raydium
pub user: Signer<'info>, // Meteora

特点

  • 用户持有资金,直接授权交换
  • 合约只是”转发意图”
  • 无需托管,即时完成
  • 更简单、更安全

适用

  • 用户手动发起的即时交换
  • 前端调用的 swap 操作

4.2 程序托管模型(Jupiter)

1
2
3
4
5
6
7
// vault PDA 签名
#[account(seeds = [b"vault"], bump)]
pub vault: SystemAccount<'info>,

// PDA 作为签名者
let signer_seeds: &[&[&[u8]]] = &[&[VAULT_SEED, &[ctx.bumps.vault]]];
invoke_signed(&instruction, &accounts, signer_seeds)?;

特点

  • 程序持有资金,自主执行交换
  • 用户不需要在线签名
  • 支持复杂的自动化逻辑
  • 需要信任程序的安全性

适用

  • 自动化策略执行
  • 后台定时任务
  • 金库资产再平衡

总结

指令列表

指令 DEX 模型 签名者
ray_swap_in Raydium AMM 用户直控 用户
meteora_swap_in Meteora Dynamic AMM 用户直控 用户
jup_swap_in Jupiter 聚合器 程序托管 vault PDA

账户依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ray_swap_in
├── Raydium AMM 程序
├── OpenBook 市场账户
└── 用户 Token 账户

meteora_swap_in
├── Meteora Dynamic AMM 程序
├── Meteora Vault 程序
└── 用户 Token 账户

jup_swap_in
├── Jupiter 聚合器程序
├── Vault PDA (seeds: ["vault"])
└── Vault 的 Token 账户

PDA 种子

账户 Seeds 所属指令
vault ["vault"] jup_swap_in

Token 标准支持

指令 SPL Token Token-2022
ray_swap_in
meteora_swap_in
jup_swap_in

Jupiter 交换通过 TokenInterface 支持 Token-2022 扩展标准。