Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e53c396
chore: sync hive
Jan 5, 2026
308b0af
chore: sync hive
Jan 5, 2026
8d91f94
initial draft of src changes
Jan 12, 2026
2403720
removed hive
Jan 12, 2026
d1fa412
split apart wrap into wrap_asset
Jan 13, 2026
2855498
fix: remove balance tracking, rename replace -> unwrap_asset
Jan 14, 2026
cd647ef
fix: original tests running
Jan 14, 2026
e2977e2
depreciating no-yield but still allowing for migration to jmi, tests …
Jan 15, 2026
68e4a30
ext_swap wrap n unwrap asset now both have whitelist protection
Jan 15, 2026
aab8a56
chore: added tests
Jan 16, 2026
9e226ff
Merge branch 'main' into feat/JMI
Jan 17, 2026
786ba8a
chore: assert on rounding test to confirm rounding works
Jan 19, 2026
6dc06e2
fix: underflow error semantic
ith-harvey Jan 20, 2026
3af8f64
fix: reverted back to replace_asset_with_m to match EVM imp
ith-harvey Jan 21, 2026
86518ff
fix: removed global_account seed requirement from asset_config
ith-harvey Jan 22, 2026
33c941f
chore: added additional check that asset_mint != m_mint in replace_as…
ith-harvey Jan 22, 2026
63c0e3c
fix: undo changes to ext_swap/wrap
ith-harvey Jan 22, 2026
a2f7f4f
fix: rename m_amount to m_principal and from_principal to ext_princip…
ith-harvey Jan 22, 2026
d2ab707
Update programs/m_ext/src/instructions/jmi/set_asset_cap.rs
ith-harvey Jan 22, 2026
4e20932
chore: moved asset_mint not m_mint validation to accounts section
ith-harvey Jan 22, 2026
a06d5d4
fix: used generic tokenInterface combining transfer_tokens_from_progr…
ith-harvey Jan 22, 2026
432404b
fix: removed pause since pause functionality already exists in whitel…
ith-harvey Jan 22, 2026
691b18f
fix: added clearer relationship that JMI always requrires no-yield ye…
ith-harvey Jan 22, 2026
b008a84
fix: removed token backing assertion, already reverts with insufficie…
ith-harvey Jan 23, 2026
5789e43
fix: removed unused error
ith-harvey Jan 29, 2026
8f12e00
fix: added additional extension protections to set_cap
ith-harvey Jan 29, 2026
ce7c5da
feat: created separate replace_authorities which holds permissions fo…
ith-harvey Jan 29, 2026
f7f5dc0
fix: restrict interest bearing tokens as SPL assets
ith-harvey Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ build-programs:
@mv target/deploy/m_ext.so target/deploy/scaled_ui.so
@mv target/idl/m_ext.json target/idl/scaled_ui.json
@mv target/types/m_ext.ts target/types/scaled_ui.ts
anchor build -p m_ext -- --features no-yield --no-default-features
@mv target/deploy/m_ext.so target/deploy/no_yield.so
@mv target/idl/m_ext.json target/idl/no_yield.json
@mv target/types/m_ext.ts target/types/no_yield.ts
anchor build -p m_ext -- --features jmi,no-yield --no-default-features
@mv target/deploy/m_ext.so target/deploy/jmi.so
@mv target/idl/m_ext.json target/idl/jmi.json
@mv target/types/m_ext.ts target/types/jmi.ts
anchor build -p m_ext -- --features crank --no-default-features
@mv target/deploy/m_ext.so target/deploy/crank.so
@mv target/idl/m_ext.json target/idl/crank.json
@mv target/types/m_ext.ts target/types/crank.ts
anchor build -p m_ext -- --features no-yield,migrate --no-default-features
anchor build -p m_ext -- --features no-yield,jmi,migrate --no-default-features
@mv target/idl/m_ext.json target/idl/migrate.json
@mv target/types/m_ext.ts target/types/migrate.ts

Expand All @@ -27,7 +27,7 @@ endef

build-test-programs:
$(call update-program-id,3joDhmLtHLrSBGfeAe1xQiv3gjikes3x8S4N3o6Ld8zB)
anchor build -p m_ext
anchor build -p m_ext -- --features jmi,no-yield --no-default-features
@mv target/deploy/m_ext.so tests/programs/ext_a.so
$(call update-program-id,HSMnbWEkB7sEQAGSzBPeACNUCXC9FgNeeESLnHtKfoy3)
anchor build -p m_ext -- --features scaled-ui --no-default-features
Expand Down
2 changes: 1 addition & 1 deletion programs/ext_swap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ anchor-lang.workspace = true
anchor-spl.workspace = true
earn.workspace = true
solana-security-txt.workspace = true
m_ext = {path = "../m_ext", features = ["cpi"]}
m_ext = {path = "../m_ext", features = ["cpi", "jmi"]}
Comment thread
Oighty marked this conversation as resolved.
4 changes: 4 additions & 0 deletions programs/ext_swap/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
pub mod initialize;
pub mod replace_asset_with_m;
pub mod swap;
pub mod unwrap;
pub mod whitelist;
pub mod wrap;
pub mod wrap_asset;

pub use initialize::*;
pub use replace_asset_with_m::*;
pub use swap::*;
pub use unwrap::*;
pub use whitelist::*;
pub use wrap::*;
pub use wrap_asset::*;
252 changes: 252 additions & 0 deletions programs/ext_swap/src/instructions/replace_asset_with_m.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
use m_ext::cpi::accounts::{ReplaceAssetWithM as ExtReplaceAssetWithM, Unwrap};
use m_ext::state::{EXT_GLOBAL_SEED, MINT_AUTHORITY_SEED, M_VAULT_SEED};

use crate::{
errors::SwapError,
state::{SwapGlobal, GLOBAL_SEED, REPLACE_AUTHORITY_SEED},
};

#[derive(Accounts)]
pub struct ReplaceAssetWithM<'info> {
pub signer: Signer<'info>,

// Required if the fallback_replace_authority is not whitelisted on the extension
pub replace_authority: Option<Signer<'info>>,

/// CHECK: PDA used as replace authority for JMI extensions
#[account(
seeds = [REPLACE_AUTHORITY_SEED],
bump,
)]
pub fallback_replace_authority: AccountInfo<'info>,

/*
* Program globals
*/
#[account(
seeds = [GLOBAL_SEED],
bump = swap_global.bump,
)]
pub swap_global: Box<Account<'info, SwapGlobal>>,

/// Source extension global (for unwrap)
#[account(
mut,
seeds = [EXT_GLOBAL_SEED],
seeds::program = from_ext_program.key(),
bump,
)]
/// CHECK: CPI will validate the global account
pub from_global: AccountInfo<'info>,

/// JMI extension global (for replace_asset_with_m)
#[account(
mut,
seeds = [EXT_GLOBAL_SEED],
seeds::program = jmi_ext_program.key(),
bump,
)]
/// CHECK: CPI will validate the global account
pub jmi_global: AccountInfo<'info>,

/*
* Mints
*/
#[account(mut)]
/// Validated by unwrap on the extension program
pub from_mint: Box<InterfaceAccount<'info, Mint>>,
#[account(mint::token_program = m_token_program)]
pub m_mint: Box<InterfaceAccount<'info, Mint>>,
#[account(mint::token_program = asset_token_program)]
pub asset_mint: Box<InterfaceAccount<'info, Mint>>,

/*
* Asset config for JMI
*/
#[account(mut)]
/// CHECK: CPI will validate the asset config
pub asset_config: AccountInfo<'info>,

/*
* Token Accounts
*/
#[account(
mut,
token::mint = from_mint,
token::token_program = from_token_program,
)]
pub from_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
token::mint = asset_mint,
token::token_program = asset_token_program,
)]
pub to_asset_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
associated_token::mint = m_mint,
associated_token::authority = swap_global,
associated_token::token_program = m_token_program,
)]
pub swap_m_account: Box<InterfaceAccount<'info, TokenAccount>>,

/*
* Authorities & Vaults for source extension (unwrap)
*/
#[account(
seeds = [M_VAULT_SEED],
seeds::program = from_ext_program.key(),
bump,
)]
/// CHECK: account does not hold data
pub from_m_vault_auth: AccountInfo<'info>,
#[account(
seeds = [MINT_AUTHORITY_SEED],
seeds::program = from_ext_program.key(),
bump,
)]
/// CHECK: account does not hold data
pub from_mint_authority: AccountInfo<'info>,
#[account(
mut,
associated_token::mint = m_mint,
associated_token::authority = from_m_vault_auth,
associated_token::token_program = m_token_program,
)]
pub from_m_vault: Box<InterfaceAccount<'info, TokenAccount>>,

/*
* Vaults for JMI extension (replace_asset_with_m)
*/
#[account(
seeds = [M_VAULT_SEED],
seeds::program = jmi_ext_program.key(),
bump,
)]
/// CHECK: account does not hold data
pub jmi_m_vault_auth: AccountInfo<'info>,
#[account(
mut,
associated_token::mint = m_mint,
associated_token::authority = jmi_m_vault_auth,
associated_token::token_program = m_token_program,
)]
pub jmi_m_vault: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
associated_token::mint = asset_mint,
associated_token::authority = jmi_m_vault_auth,
associated_token::token_program = asset_token_program,
)]
pub jmi_asset_vault: Box<InterfaceAccount<'info, TokenAccount>>,

/*
* Token Programs
*/
pub from_token_program: Interface<'info, TokenInterface>,
pub asset_token_program: Interface<'info, TokenInterface>,
pub m_token_program: Interface<'info, TokenInterface>,

/*
* Programs
*/
/// CHECK: checked against whitelisted extensions
pub from_ext_program: UncheckedAccount<'info>,
Comment thread
Oighty marked this conversation as resolved.
/// CHECK: checked against whitelisted extensions
pub jmi_ext_program: UncheckedAccount<'info>,
pub system_program: Program<'info, System>,
}

impl<'info> ReplaceAssetWithM<'info> {
fn validate(&self, ext_principal: u64) -> Result<()> {
// Validate both extensions are whitelisted
for ext_program in [&self.from_ext_program, &self.jmi_ext_program] {
if !self.swap_global.is_extension_whitelisted(ext_program.key) {
return err!(SwapError::InvalidExtension);
}
}

if ext_principal == 0 {
return err!(SwapError::InvalidAmount);
}

Ok(())
}

#[access_control(ctx.accounts.validate(ext_principal))]
pub fn handler(ctx: Context<'_, '_, '_, 'info, Self>, ext_principal: u64) -> Result<()> {
let m_pre_balance = ctx.accounts.swap_m_account.amount;

// Set replace authority as authority if none provided
let replace_authority = match &ctx.accounts.replace_authority {
Some(auth) => auth.to_account_info(),
None => ctx.accounts.fallback_replace_authority.to_account_info(),
};

// 1. Unwrap source extension → M (to swap_m_account)
m_ext::cpi::unwrap(
CpiContext::new_with_signer(
ctx.accounts.from_ext_program.to_account_info(),
Unwrap {
token_authority: ctx.accounts.signer.to_account_info(),
unwrap_authority: Some(replace_authority.clone()),
m_mint: ctx.accounts.m_mint.to_account_info(),
ext_mint: ctx.accounts.from_mint.to_account_info(),
global_account: ctx.accounts.from_global.to_account_info(),
m_vault: ctx.accounts.from_m_vault_auth.to_account_info(),
ext_mint_authority: ctx.accounts.from_mint_authority.to_account_info(),
to_m_token_account: ctx.accounts.swap_m_account.to_account_info(),
vault_m_token_account: ctx.accounts.from_m_vault.to_account_info(),
from_ext_token_account: ctx.accounts.from_token_account.to_account_info(),
m_token_program: ctx.accounts.m_token_program.to_account_info(),
ext_token_program: ctx.accounts.from_token_program.to_account_info(),
},
&[&[
REPLACE_AUTHORITY_SEED,
&[ctx.bumps.fallback_replace_authority],
]],
),
ext_principal,
)?;

// 2. Calculate M received
ctx.accounts.swap_m_account.reload()?;
let m_principal = ctx.accounts.swap_m_account.amount - m_pre_balance;

// 3. Call JMI replace_asset_with_m (swap_global signs for token transfer, replace_authority for authorization)
m_ext::cpi::replace_asset_with_m(
CpiContext::new_with_signer(
ctx.accounts.jmi_ext_program.to_account_info(),
ExtReplaceAssetWithM {
token_authority: ctx.accounts.swap_global.to_account_info(),
replace_authority: Some(replace_authority),
m_mint: ctx.accounts.m_mint.to_account_info(),
asset_mint: ctx.accounts.asset_mint.to_account_info(),
global_account: ctx.accounts.jmi_global.to_account_info(),
asset_config: ctx.accounts.asset_config.to_account_info(),
m_vault: ctx.accounts.jmi_m_vault_auth.to_account_info(),
from_m_token_account: ctx.accounts.swap_m_account.to_account_info(),
vault_m_token_account: ctx.accounts.jmi_m_vault.to_account_info(),
vault_asset_token_account: ctx.accounts.jmi_asset_vault.to_account_info(),
to_asset_token_account: ctx.accounts.to_asset_token_account.to_account_info(),
m_token_program: ctx.accounts.m_token_program.to_account_info(),
asset_token_program: ctx.accounts.asset_token_program.to_account_info(),
},
&[
&[
REPLACE_AUTHORITY_SEED,
&[ctx.bumps.fallback_replace_authority],
],
&[GLOBAL_SEED, &[ctx.accounts.swap_global.bump]],
],
),
m_principal,
)?;

msg!("{} ext_principal -> {} m_principal -> asset", ext_principal, m_principal);

Ok(())
}
}
Loading
Loading