Skip to content

feat(swap): add Oxedium single-sided liquidity protocol#34

Open
zelmkhan wants to merge 9 commits intoblueshift-gg:masterfrom
zelmkhan:feat/oxedium-swap
Open

feat(swap): add Oxedium single-sided liquidity protocol#34
zelmkhan wants to merge 9 commits intoblueshift-gg:masterfrom
zelmkhan:feat/oxedium-swap

Conversation

@zelmkhan
Copy link
Copy Markdown

@zelmkhan zelmkhan commented Mar 17, 2026

Summary

Adds swap support for the Oxedium single-sided liquidity protocol (oV3SkLhiXSG946FaqDf1yNocFMhE1ZvomGsoWF8Mzap ).

Oxedium is an AMM where LPs deposit a single token into a vault and traders swap between vaults using Pyth oracle prices to determine the exchange rate.

  • Add crates/swap/oxedium crate implementing the Swap<'info> trait
  • OxediumSwapAccounts — 16 accounts (program sentinel + 15 instruction accounts)
  • Instruction data: 8-byte Anchor discriminator (sha256("global:swap")[0..8]) + amount_in + minimum_out = 24 bytes
  • No extra swap data required (type Data = ())
  • Wire up oxedium-swap feature flag in root Cargo.toml
  • Export oxedium module in src/lib.rs
  • Add SwapContext::Oxedium variant and protocol-detection routing in src/context.rs

Account layout

# Account Writable Signer
0 oxedium_program (detector) no no
1 signer yes yes
2 token_mint_in no no
3 token_mint_out no no
4 pyth_price_in no no
5 pyth_price_out no no
6 signer_ata_in yes no
7 signer_ata_out yes no
8 vault_pda_in yes no
9 vault_pda_out yes no
10 vault_ata_in yes no
11 vault_ata_out yes no
12 oxe_global_pda no no
13 associated_token_program no no
14 token_program no no
15 system_program no no

Test plan

  • Verify cargo check --features oxedium-swap compiles without errors
  • Verify cargo check (all features) compiles without errors
  • Test swap routing via try_from_swap_context with Oxedium program ID as first account

zelmkhan and others added 5 commits March 17, 2026 05:30
Implements the `Swap` trait for the Oxedium protocol
(oxe3zgfkaGph4X5bv4RZRFUcQXZcXxK9fxDizCtPZv7), a single-sided
liquidity AMM on Solana that uses Pyth oracle prices for cross-vault
token swaps.

- Add `crates/swap/oxedium` crate with `OxediumSwapAccounts` (16 accounts)
  and `Oxedium` struct implementing `Swap<'info>`
- Instruction data: 8-byte Anchor discriminator + amount_in + minimum_out (24 bytes)
- No extra swap data required (`type Data = ()`)
- Wire up `oxedium-swap` feature flag in root `Cargo.toml`
- Export `oxedium` module in `src/lib.rs`
- Add `SwapContext::Oxedium` variant and routing in `src/context.rs`
…gg#30)

* Fix pinocchio GitHub link and typo in description

* `program` -> `programs` in `Cargo.toml` description
* feat: add omnipair-swap feature

* feat: add omnipair-swap client

* chore: add fixtures for omnipair-swap

* feat: add omnipair-swap cpi test

* refactor: simplify code
Copy link
Copy Markdown
Contributor

@BretasArthur1 BretasArthur1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @zelmkhan overall looks good, but please remove claude as contributor from your history

@zelmkhan zelmkhan force-pushed the feat/oxedium-swap branch from e2eda4d to 35695d6 Compare March 23, 2026 16:37
@L0STE
Copy link
Copy Markdown
Contributor

L0STE commented Mar 24, 2026

The context.rs wiring has two bugs where Oxedium code was inserted into the middle of Omnipair blocks instead of as self-contained blocks:

1. swap_signed match arm is unclosed — the Oxedium arm opens swap_signed( but never passes arguments or closes. Omnipair's arm starts mid-expression:

// broken — missing args, ), and }
(SwapContext::Oxedium(accounts), SwapData::Oxedium(())) => {
    crate::oxedium::Oxedium::swap_signed(
// omnipair starts here

Should be a complete arm:

#[cfg(feature = "oxedium-swap")]
(SwapContext::Oxedium(accounts), SwapData::Oxedium(())) => {
    crate::oxedium::Oxedium::swap_signed(
        accounts,
        in_amount,
        minimum_out_amount,
        &(),
        signer_seeds,
    )
}

2. try_from_swap_context — Oxedium if block never closes, so it nests around the Omnipair block. The split_at, try_from, and return lines for Oxedium ended up inside Omnipair's scope. Needs to be its own standalone block before the Omnipair one:

#[cfg(feature = "oxedium-swap")]
if address_eq(detector_account.address(), &crate::oxedium::OXEDIUM_PROGRAM_ID) {
    let n = crate::oxedium::OxediumSwapAccounts::NUM_ACCOUNTS;
    if accounts.len() < n {
        return Err(ProgramError::NotEnoughAccountKeys);
    }
    let (mine, rest) = accounts.split_at(n);
    let ctx = crate::oxedium::OxediumSwapAccounts::try_from(mine)?;
    return Ok((SwapContext::Oxedium(ctx), rest));
}

Both fixes are just restructuring — copy the Omnipair pattern as a complete block rather than inserting lines into it.

Also missing a test file (tests/swap/oxedium.rs) — can't merge without one. Rebase onto master after fixing.

Fix two broken match arms in context.rs:
- Complete the swap_signed dispatch arm with proper arguments
- Separate the oxedium and omnipair if-blocks in try_from_swap_context

Add integration test with mainnet fixtures (accounts, .so, pyth oracles).

Add beethoven-client support:
- oxedium feature flag
- OxediumSwapInput struct and build_accounts()
- resolve() that derives vault PDAs and reads pyth accounts from RPC
@zelmkhan
Copy link
Copy Markdown
Author

@L0STE @BretasArthur1 Please check

Copy link
Copy Markdown
Contributor

@L0STE L0STE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, a few minor style nits:

  • src/context.rs: Missing blank line between Oxedium and Omnipair variants in both SwapContext and SwapData enums (every other variant has one)
  • src/context.rs: The try_from_swap_data arm for Oxedium is between Gamma and ScaleAmm, but the enum declaration puts it between ScaleVmm and Omnipair — would be easier to scan if they matched
  • crates/swap/oxedium/src/lib.rs:285: The "no extra data" doc comment is on the NUM_ACCOUNTS impl block rather than near type Data = ()

zelmkhan and others added 2 commits March 26, 2026 23:51
- Add missing blank lines before Omnipair variants in SwapContext and SwapData enums
- Reorder try_from_swap_data arms to match enum declaration order (Oxedium after ScaleVmm)
- Move "no extra data" doc comment from NUM_ACCOUNTS impl block to type Data = ()
@zelmkhan zelmkhan requested a review from BretasArthur1 March 29, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants