diff --git a/tokens/token-swap/steel/Cargo.toml b/tokens/token-swap/steel/Cargo.toml new file mode 100644 index 000000000..2f352494e --- /dev/null +++ b/tokens/token-swap/steel/Cargo.toml @@ -0,0 +1,23 @@ +[workspace] +resolver = "2" +members = ["api", "program"] + +[workspace.package] +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +homepage = "" +documentation = "" +repository = "" +readme = "./README.md" +keywords = ["solana"] + +[workspace.dependencies] +token-swap-api = { path = "./api", version = "0.1.0" } +bytemuck = "1.14" +num_enum = "0.7" +solana-program = "1.18" +steel = { version = "2.0", features = ["spl"] } +thiserror = "1.0" +spl-token = "^4" +spl-math = { version = "0.3.0", features = ["no-entrypoint"] } diff --git a/tokens/token-swap/steel/README.md b/tokens/token-swap/steel/README.md new file mode 100644 index 000000000..66c43b05f --- /dev/null +++ b/tokens/token-swap/steel/README.md @@ -0,0 +1,45 @@ +# Token swap example amm in Steel + +**TokenSwap** - Your Gateway to Effortless Trading! Welcome to the world of Automated Market Makers (AMM), where seamless trading is made possible with the power of automation. The primary goal of AMMs is to act as automatic buyers and sellers, readily available whenever users wish to trade their assets. + +## API +- [`Consts`](api/src/consts.rs) – Program constants. +- [`Error`](api/src/error.rs) – Custom program errors. +- [`Instruction`](api/src/instruction.rs) – Declared instructions. + +## Instructions +- [`CreateAmm`](program/src/create_amm.rs) – Create amm ... +- [`CreatePool`](program/src/create_pool.rs) – Create liquidity pool +- [`DepositLiquidity`](program/src/deposit_liquidity.rs) – Desposit liquidity to pool +- [`WithdrawLiquidity`](program/src/withdraw_liquidity.rs) – Withdraw liquidity from pool +- [`Swap`](program/src/swap.rs) – Swap exact token amount + +## State +- [`Amm`](api/src/state/amm.rs) – Amm state +- [`Pool`](api/src/state/pool.rs) – Pool state + +## How to run + +Compile your program: + +```sh +pnpm build +``` + +Run unit and integration tests: + +```sh +pnpm test +``` + +Run build and test + +```sh +pnpm build-and-test +``` + +Deploy your program: + +```sh +pnpm deploy +``` diff --git a/tokens/token-swap/steel/api/Cargo.toml b/tokens/token-swap/steel/api/Cargo.toml new file mode 100644 index 000000000..1b86e4f6d --- /dev/null +++ b/tokens/token-swap/steel/api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "token-swap-api" +description = "API for interacting with the TokenSwap program" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +readme.workspace = true +keywords.workspace = true + +[dependencies] +bytemuck.workspace = true +num_enum.workspace = true +solana-program.workspace = true +steel.workspace = true +thiserror.workspace = true +spl-token.workspace = true +spl-math.workspace = true diff --git a/tokens/token-swap/steel/api/src/consts.rs b/tokens/token-swap/steel/api/src/consts.rs new file mode 100644 index 000000000..74956061e --- /dev/null +++ b/tokens/token-swap/steel/api/src/consts.rs @@ -0,0 +1,11 @@ +use solana_program::pubkey; +use steel::Pubkey; + +pub const MINIMUM_LIQUIDITY: u64 = 100; + +pub const AUTHORITY_SEED: &[u8] = b"authority"; + +pub const LIQUIDITY_SEED: &[u8] = b"liquidity"; + +pub const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey = + pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); diff --git a/tokens/token-swap/steel/api/src/error.rs b/tokens/token-swap/steel/api/src/error.rs new file mode 100644 index 000000000..0ab3ce9be --- /dev/null +++ b/tokens/token-swap/steel/api/src/error.rs @@ -0,0 +1,20 @@ +use steel::*; + +#[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)] +#[repr(u32)] +pub enum TokenSwapError { + #[error("Invalid fee, must be between 0 and 10000")] + InvalidFee = 0, + #[error("Account is not existed")] + AccountIsNotExisted = 1, + #[error("Invalid account")] + InvalidAccount = 2, + #[error("Deposit too small")] + DepositTooSmall = 3, + #[error("Withdrawal too small")] + OutputTooSmall, + #[error("Invariant violated")] + InvariantViolated, +} + +error!(TokenSwapError); diff --git a/tokens/token-swap/steel/api/src/instruction.rs b/tokens/token-swap/steel/api/src/instruction.rs new file mode 100644 index 000000000..b6759b2a7 --- /dev/null +++ b/tokens/token-swap/steel/api/src/instruction.rs @@ -0,0 +1,49 @@ +use steel::*; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] +pub enum TokenSwapInstruction { + CreateAmm = 0, + CreatePool = 1, + DepositLiquidity = 2, + WithdrawLiquidity = 3, + Swap = 4, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct CreateAmm { + pub id: Pubkey, + pub fee: [u8; 2], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct CreatePool {} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct DepositLiquidity { + pub amount_a: [u8; 8], + pub amount_b: [u8; 8], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct WithdrawLiquidity { + pub amount: [u8; 8], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct Swap { + pub swap_a: u8, + pub input_amount: [u8; 8], + pub min_output_amount: [u8; 8], +} + +instruction!(TokenSwapInstruction, CreateAmm); +instruction!(TokenSwapInstruction, CreatePool); +instruction!(TokenSwapInstruction, DepositLiquidity); +instruction!(TokenSwapInstruction, WithdrawLiquidity); +instruction!(TokenSwapInstruction, Swap); diff --git a/tokens/token-swap/steel/api/src/lib.rs b/tokens/token-swap/steel/api/src/lib.rs new file mode 100644 index 000000000..aed2ed84f --- /dev/null +++ b/tokens/token-swap/steel/api/src/lib.rs @@ -0,0 +1,18 @@ +pub mod consts; +pub mod error; +pub mod instruction; +pub mod sdk; +pub mod state; + +pub mod prelude { + pub use crate::consts::*; + pub use crate::error::*; + pub use crate::instruction::*; + pub use crate::sdk::*; + pub use crate::state::*; +} + +use steel::*; + +// TODO Set program id +declare_id!("z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35"); diff --git a/tokens/token-swap/steel/api/src/sdk.rs b/tokens/token-swap/steel/api/src/sdk.rs new file mode 100644 index 000000000..437981ca5 --- /dev/null +++ b/tokens/token-swap/steel/api/src/sdk.rs @@ -0,0 +1,277 @@ +use steel::*; + +use crate::prelude::*; + +pub fn create_amm(payer: Pubkey, admin: Pubkey, id: Pubkey, fee: u16) -> Instruction { + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(payer, true), + AccountMeta::new_readonly(admin, false), + AccountMeta::new(amm_pda(id).0, false), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: CreateAmm { + id, + fee: fee.to_le_bytes(), + } + .to_bytes(), + } +} + +pub fn create_pool(payer: Pubkey, amm: Pubkey, mint_a: Pubkey, mint_b: Pubkey) -> Instruction { + let pool_authority = pool_authority_pda(amm, mint_a, mint_b).0; + let (pool_account_a, _) = Pubkey::find_program_address( + &[ + pool_authority.as_ref(), + spl_token::ID.as_ref(), + mint_a.as_ref(), + ], + &spl_token::ID, + ); + + let (pool_account_b, _) = Pubkey::find_program_address( + &[ + pool_authority.as_ref(), + spl_token::ID.as_ref(), + mint_b.as_ref(), + ], + &spl_token::ID, + ); + + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(payer, true), + AccountMeta::new_readonly(amm, false), + AccountMeta::new(pool_pda(amm, mint_a, mint_b).0, false), + AccountMeta::new_readonly(pool_authority, false), + AccountMeta::new(mint_liquidity_pda(amm, mint_a, mint_b).0, false), + AccountMeta::new(mint_a, false), + AccountMeta::new(mint_b, false), + AccountMeta::new(pool_account_a, false), + AccountMeta::new(pool_account_b, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(ASSOCIATED_TOKEN_PROGRAM_ID, false), + AccountMeta::new_readonly(sysvar::rent::ID, false), + ], + data: CreatePool {}.to_bytes(), + } +} + +pub fn deposit_liquidity( + payer: Pubkey, + depositor: Pubkey, + pool: Pubkey, + pool_authority: Pubkey, + amm: Pubkey, + mint_a: Pubkey, + mint_b: Pubkey, + amount_a: u64, + amount_b: u64, +) -> Instruction { + let (pool_account_a, _) = Pubkey::find_program_address( + &[ + pool_authority.as_ref(), + spl_token::ID.as_ref(), + mint_a.as_ref(), + ], + &spl_token::ID, + ); + + let (pool_account_b, _) = Pubkey::find_program_address( + &[ + pool_authority.as_ref(), + spl_token::ID.as_ref(), + mint_b.as_ref(), + ], + &spl_token::ID, + ); + + let mint_liquidity = mint_liquidity_pda(amm, mint_a, mint_b).0; + + let (depositor_account_liquidity, _) = Pubkey::find_program_address( + &[ + depositor.as_ref(), + spl_token::ID.as_ref(), + mint_liquidity.as_ref(), + ], + &spl_token::ID, + ); + + let (depositor_account_a, _) = Pubkey::find_program_address( + &[depositor.as_ref(), spl_token::ID.as_ref(), mint_a.as_ref()], + &spl_token::ID, + ); + + let (depositor_account_b, _) = Pubkey::find_program_address( + &[depositor.as_ref(), spl_token::ID.as_ref(), mint_b.as_ref()], + &spl_token::ID, + ); + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(payer, true), + AccountMeta::new(depositor, true), + AccountMeta::new_readonly(pool, false), + AccountMeta::new_readonly(pool_authority, false), + AccountMeta::new(mint_liquidity, false), + AccountMeta::new(mint_a, false), + AccountMeta::new(mint_b, false), + AccountMeta::new(pool_account_a, false), + AccountMeta::new(pool_account_b, false), + AccountMeta::new(depositor_account_liquidity, false), + AccountMeta::new(depositor_account_a, false), + AccountMeta::new(depositor_account_b, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(ASSOCIATED_TOKEN_PROGRAM_ID, false), + ], + data: DepositLiquidity { + amount_a: amount_a.to_le_bytes(), + amount_b: amount_b.to_le_bytes(), + } + .to_bytes(), + } +} + +pub fn withdraw_liquidity( + payer: Pubkey, + depositor: Pubkey, + pool: Pubkey, + pool_authority: Pubkey, + amm: Pubkey, + mint_a: Pubkey, + mint_b: Pubkey, + amount: u64, +) -> Instruction { + let (pool_account_a, _) = Pubkey::find_program_address( + &[ + pool_authority.as_ref(), + spl_token::ID.as_ref(), + mint_a.as_ref(), + ], + &spl_token::ID, + ); + + let (pool_account_b, _) = Pubkey::find_program_address( + &[ + pool_authority.as_ref(), + spl_token::ID.as_ref(), + mint_b.as_ref(), + ], + &spl_token::ID, + ); + + let mint_liquidity = mint_liquidity_pda(amm, mint_a, mint_b).0; + + let (depositor_account_liquidity, _) = Pubkey::find_program_address( + &[ + depositor.as_ref(), + spl_token::ID.as_ref(), + mint_liquidity.as_ref(), + ], + &spl_token::ID, + ); + + let (depositor_account_a, _) = Pubkey::find_program_address( + &[depositor.as_ref(), spl_token::ID.as_ref(), mint_a.as_ref()], + &spl_token::ID, + ); + + let (depositor_account_b, _) = Pubkey::find_program_address( + &[depositor.as_ref(), spl_token::ID.as_ref(), mint_b.as_ref()], + &spl_token::ID, + ); + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(payer, true), + AccountMeta::new(depositor, true), + AccountMeta::new_readonly(pool, false), + AccountMeta::new_readonly(pool_authority, false), + AccountMeta::new(mint_liquidity, false), + AccountMeta::new(mint_a, false), + AccountMeta::new(mint_b, false), + AccountMeta::new(pool_account_a, false), + AccountMeta::new(pool_account_b, false), + AccountMeta::new(depositor_account_liquidity, false), + AccountMeta::new(depositor_account_a, false), + AccountMeta::new(depositor_account_b, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(ASSOCIATED_TOKEN_PROGRAM_ID, false), + ], + data: WithdrawLiquidity { + amount: amount.to_le_bytes(), + } + .to_bytes(), + } +} + +pub fn swap( + payer: Pubkey, + trader: Pubkey, + pool: Pubkey, + pool_authority: Pubkey, + amm: Pubkey, + mint_a: Pubkey, + mint_b: Pubkey, + swap_a: bool, + input_amount: u64, + min_output_amount: u64, +) -> Instruction { + let (pool_account_a, _) = Pubkey::find_program_address( + &[ + pool_authority.as_ref(), + spl_token::ID.as_ref(), + mint_a.as_ref(), + ], + &spl_token::ID, + ); + + let (pool_account_b, _) = Pubkey::find_program_address( + &[ + pool_authority.as_ref(), + spl_token::ID.as_ref(), + mint_b.as_ref(), + ], + &spl_token::ID, + ); + + let (trader_account_a, _) = Pubkey::find_program_address( + &[trader.as_ref(), spl_token::ID.as_ref(), mint_a.as_ref()], + &spl_token::ID, + ); + + let (trader_account_b, _) = Pubkey::find_program_address( + &[trader.as_ref(), spl_token::ID.as_ref(), mint_b.as_ref()], + &spl_token::ID, + ); + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(payer, true), + AccountMeta::new(trader, true), + AccountMeta::new_readonly(amm, false), + AccountMeta::new_readonly(pool, false), + AccountMeta::new_readonly(pool_authority, false), + AccountMeta::new(mint_a, false), + AccountMeta::new(mint_b, false), + AccountMeta::new(pool_account_a, false), + AccountMeta::new(pool_account_b, false), + AccountMeta::new(trader_account_a, false), + AccountMeta::new(trader_account_b, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(ASSOCIATED_TOKEN_PROGRAM_ID, false), + ], + data: Swap { + swap_a: swap_a as u8, + input_amount: input_amount.to_le_bytes(), + min_output_amount: min_output_amount.to_le_bytes(), + } + .to_bytes(), + } +} diff --git a/tokens/token-swap/steel/api/src/state/amm.rs b/tokens/token-swap/steel/api/src/state/amm.rs new file mode 100644 index 000000000..b59fd540a --- /dev/null +++ b/tokens/token-swap/steel/api/src/state/amm.rs @@ -0,0 +1,23 @@ +use steel::*; + +use super::TokenSwapAccount; + +/// Fetch PDA of the amm account. +pub fn amm_pda(id: Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address(&[id.as_ref()], &crate::id()) +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct Amm { + /// The primary key of the AMM + pub id: Pubkey, + + /// Account that has admin authority over the AMM + pub admin: Pubkey, + + /// The LP fee taken on each trade, in basis points + pub fee: [u8; 2], +} + +account!(TokenSwapAccount, Amm); diff --git a/tokens/token-swap/steel/api/src/state/mod.rs b/tokens/token-swap/steel/api/src/state/mod.rs new file mode 100644 index 000000000..cb20f4488 --- /dev/null +++ b/tokens/token-swap/steel/api/src/state/mod.rs @@ -0,0 +1,13 @@ +mod amm; +mod pool; +pub use amm::*; +pub use pool::*; + +use steel::*; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +pub enum TokenSwapAccount { + Amm = 0, + Pool = 1, +} diff --git a/tokens/token-swap/steel/api/src/state/pool.rs b/tokens/token-swap/steel/api/src/state/pool.rs new file mode 100644 index 000000000..98fdb8ac6 --- /dev/null +++ b/tokens/token-swap/steel/api/src/state/pool.rs @@ -0,0 +1,116 @@ +use steel::*; + +use crate::{ + consts::{AUTHORITY_SEED, LIQUIDITY_SEED}, + error::TokenSwapError, +}; + +use super::TokenSwapAccount; + +/// Fetch PDA of the pool account. +pub fn pool_pda(amm: Pubkey, mint_a: Pubkey, mint_b: Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[amm.as_ref(), mint_a.as_ref(), mint_b.as_ref()], + &crate::id(), + ) +} + +pub fn validate_pool_account(pool: &AccountInfo, mint_a: Pubkey, mint_b: Pubkey) -> ProgramResult { + let pool_info_data = pool.as_account::(&crate::id())?; + pool.has_owner(&crate::id())?.has_seeds( + &[ + pool_info_data.amm.as_ref(), + pool_info_data.mint_a.as_ref(), + pool_info_data.mint_b.as_ref(), + ], + &crate::id(), + )?; + + if pool_info_data.mint_a != mint_a || pool_info_data.mint_b != mint_b { + return Err(TokenSwapError::InvalidAccount.into()); + } + + Ok(()) +} + +pub fn pool_authority_pda(amm: Pubkey, mint_a: Pubkey, mint_b: Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[ + amm.as_ref(), + mint_a.as_ref(), + mint_b.as_ref(), + AUTHORITY_SEED, + ], + &crate::id(), + ) +} + +pub fn validate_pool_authority( + pool: &Pool, + pool_authority: &AccountInfo, + mint_a: Pubkey, + mint_b: Pubkey, +) -> ProgramResult { + pool_authority.has_seeds( + &[ + pool.amm.as_ref(), + mint_a.as_ref(), + mint_b.as_ref(), + AUTHORITY_SEED, + ], + &crate::id(), + )?; + + Ok(()) +} + +pub fn mint_liquidity_pda(amm: Pubkey, mint_a: Pubkey, mint_b: Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[ + amm.as_ref(), + mint_a.as_ref(), + mint_b.as_ref(), + LIQUIDITY_SEED, + ], + &crate::id(), + ) +} + +pub fn validate_mint_liquidity( + pool: &Pool, + mint_liquidity: &AccountInfo, + mint_a: Pubkey, + mint_b: Pubkey, +) -> ProgramResult { + mint_liquidity + .is_writable()? + .has_seeds( + &[ + pool.amm.as_ref(), + mint_a.as_ref(), + mint_b.as_ref(), + LIQUIDITY_SEED, + ], + &crate::id(), + )? + .as_mint()?; + + Ok(()) +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct Pool { + /// Primary key of the AMM + pub amm: Pubkey, + + /// Mint of token A + pub mint_a: Pubkey, + + /// Mint of token B + pub mint_b: Pubkey, + + pub pool_authority_bump: u8, +} + +account!(TokenSwapAccount, Pool); diff --git a/tokens/token-swap/steel/package.json b/tokens/token-swap/steel/package.json new file mode 100644 index 000000000..7d1362bb3 --- /dev/null +++ b/tokens/token-swap/steel/package.json @@ -0,0 +1,31 @@ +{ + "name": "token-swap", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "pnpm ts-mocha -p ./tsconfig.json -t 1000000 ./tests/*.test.ts", + "build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures && pnpm test", + "build": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./program/target/so", + "deploy": "solana program deploy ./program/target/so/account_data_program.so" + }, + "keywords": [], + "author": "Leo Pham ", + "license": "ISC", + "dependencies": { + "@solana/spl-token": "^0.4.9", + "@solana/web3.js": "^1.95.4", + "bs58": "^6.0.0" + }, + "devDependencies": { + "@types/chai": "^4.3.7", + "@types/mocha": "^10.0.9", + "@types/node": "^22.7.9", + "borsh": "^2.0.0", + "chai": "^4.3.7", + "mocha": "^10.7.3", + "solana-bankrun": "^0.4.0", + "ts-mocha": "^10.0.0", + "typescript": "^5.6.3" + } +} diff --git a/tokens/token-swap/steel/pnpm-lock.yaml b/tokens/token-swap/steel/pnpm-lock.yaml new file mode 100644 index 000000000..b5978d34a --- /dev/null +++ b/tokens/token-swap/steel/pnpm-lock.yaml @@ -0,0 +1,1463 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@solana/spl-token': + specifier: ^0.4.9 + version: 0.4.9(@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10) + '@solana/web3.js': + specifier: ^1.95.4 + version: 1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + bs58: + specifier: ^6.0.0 + version: 6.0.0 + devDependencies: + '@types/chai': + specifier: ^4.3.7 + version: 4.3.20 + '@types/mocha': + specifier: ^10.0.9 + version: 10.0.9 + '@types/node': + specifier: ^22.7.9 + version: 22.8.0 + borsh: + specifier: ^2.0.0 + version: 2.0.0 + chai: + specifier: ^4.3.7 + version: 4.5.0 + mocha: + specifier: ^10.7.3 + version: 10.7.3 + solana-bankrun: + specifier: ^0.4.0 + version: 0.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + ts-mocha: + specifier: ^10.0.0 + version: 10.0.0(mocha@10.7.3) + typescript: + specifier: ^5.6.3 + version: 5.6.3 + +packages: + + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + engines: {node: '>=6.9.0'} + + '@noble/curves@1.6.0': + resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.5.0': + resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + engines: {node: ^14.21.3 || >=16} + + '@solana/buffer-layout-utils@0.2.0': + resolution: {integrity: sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g==} + engines: {node: '>= 10'} + + '@solana/buffer-layout@4.0.1': + resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} + engines: {node: '>=5.10'} + + '@solana/codecs-core@2.0.0-rc.1': + resolution: {integrity: sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==} + peerDependencies: + typescript: '>=5' + + '@solana/codecs-data-structures@2.0.0-rc.1': + resolution: {integrity: sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==} + peerDependencies: + typescript: '>=5' + + '@solana/codecs-numbers@2.0.0-rc.1': + resolution: {integrity: sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==} + peerDependencies: + typescript: '>=5' + + '@solana/codecs-strings@2.0.0-rc.1': + resolution: {integrity: sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: '>=5' + + '@solana/codecs@2.0.0-rc.1': + resolution: {integrity: sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==} + peerDependencies: + typescript: '>=5' + + '@solana/errors@2.0.0-rc.1': + resolution: {integrity: sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==} + hasBin: true + peerDependencies: + typescript: '>=5' + + '@solana/options@2.0.0-rc.1': + resolution: {integrity: sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==} + peerDependencies: + typescript: '>=5' + + '@solana/spl-token-group@0.0.7': + resolution: {integrity: sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.95.3 + + '@solana/spl-token-metadata@0.1.6': + resolution: {integrity: sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.95.3 + + '@solana/spl-token@0.4.9': + resolution: {integrity: sha512-g3wbj4F4gq82YQlwqhPB0gHFXfgsC6UmyGMxtSLf/BozT/oKd59465DbnlUK8L8EcimKMavxsVAMoLcEdeCicg==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.95.3 + + '@solana/web3.js@1.95.4': + resolution: {integrity: sha512-sdewnNEA42ZSMxqkzdwEWi6fDgzwtJHaQa5ndUGEJYtoOnM6X5cvPmjoTUp7/k7bRrVAxfBgDnvQQHD6yhlLYw==} + + '@swc/helpers@0.5.13': + resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + + '@types/chai@4.3.20': + resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/mocha@10.0.9': + resolution: {integrity: sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@22.8.0': + resolution: {integrity: sha512-84rafSBHC/z1i1E3p0cJwKA+CfYDNSXX9WSZBRopjIzLET8oNt6ht2tei4C7izwDeEiLLfdeSVBv1egOH916hg==} + + '@types/uuid@8.3.4': + resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} + + '@types/ws@7.4.7': + resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} + + '@types/ws@8.5.12': + resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} + + JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + + agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + + assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base-x@3.0.10: + resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} + + base-x@5.0.0: + resolution: {integrity: sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bigint-buffer@1.1.5: + resolution: {integrity: sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA==} + engines: {node: '>= 10.0.0'} + + bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bn.js@5.2.1: + resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + + borsh@0.7.0: + resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} + + borsh@2.0.0: + resolution: {integrity: sha512-kc9+BgR3zz9+cjbwM8ODoUB4fs3X3I5A/HtX7LZKxCLaMrEeDFoBpnhZY//DTS1VZBSs6S5v46RZRbZjRFspEg==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + bs58@4.0.1: + resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} + + bs58@6.0.0: + resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bufferutil@4.0.8: + resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} + engines: {node: '>=6.14.2'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + chai@4.5.0: + resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + deep-eql@4.1.4: + resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + engines: {node: '>=6'} + + delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + + diff@3.5.0: + resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} + engines: {node: '>=0.3.1'} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + + es6-promisify@5.0.0: + resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + eyes@0.1.8: + resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} + engines: {node: '> 0.1.90'} + + fast-stable-stringify@1.0.0: + resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} + + fastestsmallesttextencoderdecoder@1.0.22: + resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isomorphic-ws@4.0.1: + resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} + peerDependencies: + ws: '*' + + jayson@4.1.2: + resolution: {integrity: sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA==} + engines: {node: '>=8'} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mocha@10.7.3: + resolution: {integrity: sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==} + engines: {node: '>= 14.0.0'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + rpc-websockets@9.0.4: + resolution: {integrity: sha512-yWZWN0M+bivtoNLnaDbtny4XchdAIF5Q4g/ZsC5UC61Ckbp0QczwO8fg44rV3uYmY4WHd+EZQbn90W1d8ojzqQ==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + solana-bankrun-darwin-arm64@0.4.0: + resolution: {integrity: sha512-6dz78Teoz7ez/3lpRLDjktYLJb79FcmJk2me4/YaB8WiO6W43OdExU4h+d2FyuAryO2DgBPXaBoBNY/8J1HJmw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + solana-bankrun-darwin-universal@0.4.0: + resolution: {integrity: sha512-zSSw/Jx3KNU42pPMmrEWABd0nOwGJfsj7nm9chVZ3ae7WQg3Uty0hHAkn5NSDCj3OOiN0py9Dr1l9vmRJpOOxg==} + engines: {node: '>= 10'} + os: [darwin] + + solana-bankrun-darwin-x64@0.4.0: + resolution: {integrity: sha512-LWjs5fsgHFtyr7YdJR6r0Ho5zrtzI6CY4wvwPXr8H2m3b4pZe6RLIZjQtabCav4cguc14G0K8yQB2PTMuGub8w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + solana-bankrun-linux-x64-gnu@0.4.0: + resolution: {integrity: sha512-SrlVrb82UIxt21Zr/XZFHVV/h9zd2/nP25PMpLJVLD7Pgl2yhkhfi82xj3OjxoQqWe+zkBJ+uszA0EEKr67yNw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + solana-bankrun-linux-x64-musl@0.4.0: + resolution: {integrity: sha512-Nv328ZanmURdYfcLL+jwB1oMzX4ZzK57NwIcuJjGlf0XSNLq96EoaO5buEiUTo4Ls7MqqMyLbClHcrPE7/aKyA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + solana-bankrun@0.4.0: + resolution: {integrity: sha512-NMmXUipPBkt8NgnyNO3SCnPERP6xT/AMNMBooljGA3+rG6NN8lmXJsKeLqQTiFsDeWD74U++QM/DgcueSWvrIg==} + engines: {node: '>= 10'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + superstruct@2.0.2: + resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} + engines: {node: '>=14.0.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + text-encoding-utf-8@1.0.2: + resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-mocha@10.0.0: + resolution: {integrity: sha512-VRfgDO+iiuJFlNB18tzOfypJ21xn2xbuZyDvJvqpTbWgkAgD17ONGr8t+Tl8rcBtOBdjXp5e/Rk+d39f7XBHRw==} + engines: {node: '>= 6.X.X'} + hasBin: true + peerDependencies: + mocha: ^3.X.X || ^4.X.X || ^5.X.X || ^6.X.X || ^7.X.X || ^8.X.X || ^9.X.X || ^10.X.X + + ts-node@7.0.1: + resolution: {integrity: sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==} + engines: {node: '>=4.2.0'} + hasBin: true + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.0: + resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} + + type-detect@4.1.0: + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} + engines: {node: '>=4'} + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + utf-8-validate@5.0.10: + resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} + engines: {node: '>=6.14.2'} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yn@2.0.0: + resolution: {integrity: sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==} + engines: {node: '>=4'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@noble/curves@1.6.0': + dependencies: + '@noble/hashes': 1.5.0 + + '@noble/hashes@1.5.0': {} + + '@solana/buffer-layout-utils@0.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/web3.js': 1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + bigint-buffer: 1.1.5 + bignumber.js: 9.1.2 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@solana/buffer-layout@4.0.1': + dependencies: + buffer: 6.0.3 + + '@solana/codecs-core@2.0.0-rc.1(typescript@5.6.3)': + dependencies: + '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) + typescript: 5.6.3 + + '@solana/codecs-data-structures@2.0.0-rc.1(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) + '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) + typescript: 5.6.3 + + '@solana/codecs-numbers@2.0.0-rc.1(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) + '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) + typescript: 5.6.3 + + '@solana/codecs-strings@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) + '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) + fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 5.6.3 + + '@solana/codecs@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/options': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/errors@2.0.0-rc.1(typescript@5.6.3)': + dependencies: + chalk: 5.3.0 + commander: 12.1.0 + typescript: 5.6.3 + + '@solana/options@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + dependencies: + '@solana/codecs-core': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-data-structures': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-numbers': 2.0.0-rc.1(typescript@5.6.3) + '@solana/codecs-strings': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/errors': 2.0.0-rc.1(typescript@5.6.3) + typescript: 5.6.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/spl-token-group@0.0.7(@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + dependencies: + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/web3.js': 1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)': + dependencies: + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/web3.js': 1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + + '@solana/spl-token@0.4.9(@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(utf-8-validate@5.0.10)': + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@solana/spl-token-group': 0.0.7(@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3) + '@solana/web3.js': 1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + buffer: 6.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + + '@solana/web3.js@1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@babel/runtime': 7.26.0 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@solana/buffer-layout': 4.0.1 + agentkeepalive: 4.5.0 + bigint-buffer: 1.1.5 + bn.js: 5.2.1 + borsh: 0.7.0 + bs58: 4.0.1 + buffer: 6.0.3 + fast-stable-stringify: 1.0.0 + jayson: 4.1.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + node-fetch: 2.7.0 + rpc-websockets: 9.0.4 + superstruct: 2.0.2 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@swc/helpers@0.5.13': + dependencies: + tslib: 2.8.0 + + '@types/chai@4.3.20': {} + + '@types/connect@3.4.38': + dependencies: + '@types/node': 22.8.0 + + '@types/json5@0.0.29': + optional: true + + '@types/mocha@10.0.9': {} + + '@types/node@12.20.55': {} + + '@types/node@22.8.0': + dependencies: + undici-types: 6.19.8 + + '@types/uuid@8.3.4': {} + + '@types/ws@7.4.7': + dependencies: + '@types/node': 22.8.0 + + '@types/ws@8.5.12': + dependencies: + '@types/node': 22.8.0 + + JSONStream@1.3.5: + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + + agentkeepalive@4.5.0: + dependencies: + humanize-ms: 1.2.1 + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + arrify@1.0.1: {} + + assertion-error@1.1.0: {} + + balanced-match@1.0.2: {} + + base-x@3.0.10: + dependencies: + safe-buffer: 5.2.1 + + base-x@5.0.0: {} + + base64-js@1.5.1: {} + + bigint-buffer@1.1.5: + dependencies: + bindings: 1.5.0 + + bignumber.js@9.1.2: {} + + binary-extensions@2.3.0: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bn.js@5.2.1: {} + + borsh@0.7.0: + dependencies: + bn.js: 5.2.1 + bs58: 4.0.1 + text-encoding-utf-8: 1.0.2 + + borsh@2.0.0: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browser-stdout@1.3.1: {} + + bs58@4.0.1: + dependencies: + base-x: 3.0.10 + + bs58@6.0.0: + dependencies: + base-x: 5.0.0 + + buffer-from@1.1.2: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bufferutil@4.0.8: + dependencies: + node-gyp-build: 4.8.2 + optional: true + + camelcase@6.3.0: {} + + chai@4.5.0: + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.4 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.1.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.3.0: {} + + check-error@1.0.3: + dependencies: + get-func-name: 2.0.2 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@12.1.0: {} + + commander@2.20.3: {} + + debug@4.3.7(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} + + deep-eql@4.1.4: + dependencies: + type-detect: 4.1.0 + + delay@5.0.0: {} + + diff@3.5.0: {} + + diff@5.2.0: {} + + emoji-regex@8.0.0: {} + + es6-promise@4.2.8: {} + + es6-promisify@5.0.0: + dependencies: + es6-promise: 4.2.8 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eventemitter3@5.0.1: {} + + eyes@0.1.8: {} + + fast-stable-stringify@1.0.0: {} + + fastestsmallesttextencoderdecoder@1.0.22: {} + + file-uri-to-path@1.0.0: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat@5.0.2: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + get-func-name@2.0.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + has-flag@4.0.0: {} + + he@1.2.0: {} + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + ieee754@1.2.1: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-plain-obj@2.1.0: {} + + is-unicode-supported@0.1.0: {} + + isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)): + dependencies: + ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) + + jayson@4.1.2(bufferutil@4.0.8)(utf-8-validate@5.0.10): + dependencies: + '@types/connect': 3.4.38 + '@types/node': 12.20.55 + '@types/ws': 7.4.7 + JSONStream: 1.3.5 + commander: 2.20.3 + delay: 5.0.0 + es6-promisify: 5.0.0 + eyes: 0.1.8 + isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + json-stringify-safe: 5.0.1 + uuid: 8.3.2 + ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-stringify-safe@5.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + optional: true + + jsonparse@1.3.1: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + loupe@2.3.7: + dependencies: + get-func-name: 2.0.2 + + make-error@1.3.6: {} + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mocha@10.7.3: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.3.7(supports-color@8.1.1) + diff: 5.2.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.1.6 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 + yargs-unparser: 2.0.0 + + ms@2.1.3: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build@4.8.2: + optional: true + + normalize-path@3.0.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + path-exists@4.0.0: {} + + pathval@1.1.1: {} + + picomatch@2.3.1: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + regenerator-runtime@0.14.1: {} + + require-directory@2.1.1: {} + + rpc-websockets@9.0.4: + dependencies: + '@swc/helpers': 0.5.13 + '@types/uuid': 8.3.4 + '@types/ws': 8.5.12 + buffer: 6.0.3 + eventemitter3: 5.0.1 + uuid: 8.3.2 + ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 5.0.10 + + safe-buffer@5.2.1: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + solana-bankrun-darwin-arm64@0.4.0: + optional: true + + solana-bankrun-darwin-universal@0.4.0: + optional: true + + solana-bankrun-darwin-x64@0.4.0: + optional: true + + solana-bankrun-linux-x64-gnu@0.4.0: + optional: true + + solana-bankrun-linux-x64-musl@0.4.0: + optional: true + + solana-bankrun@0.4.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): + dependencies: + '@solana/web3.js': 1.95.4(bufferutil@4.0.8)(utf-8-validate@5.0.10) + bs58: 4.0.1 + optionalDependencies: + solana-bankrun-darwin-arm64: 0.4.0 + solana-bankrun-darwin-universal: 0.4.0 + solana-bankrun-darwin-x64: 0.4.0 + solana-bankrun-linux-x64-gnu: 0.4.0 + solana-bankrun-linux-x64-musl: 0.4.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: + optional: true + + strip-json-comments@3.1.1: {} + + superstruct@2.0.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + text-encoding-utf-8@1.0.2: {} + + through@2.3.8: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tr46@0.0.3: {} + + ts-mocha@10.0.0(mocha@10.7.3): + dependencies: + mocha: 10.7.3 + ts-node: 7.0.1 + optionalDependencies: + tsconfig-paths: 3.15.0 + + ts-node@7.0.1: + dependencies: + arrify: 1.0.1 + buffer-from: 1.1.2 + diff: 3.5.0 + make-error: 1.3.6 + minimist: 1.2.8 + mkdirp: 0.5.6 + source-map-support: 0.5.21 + yn: 2.0.0 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + optional: true + + tslib@2.8.0: {} + + type-detect@4.1.0: {} + + typescript@5.6.3: {} + + undici-types@6.19.8: {} + + utf-8-validate@5.0.10: + dependencies: + node-gyp-build: 4.8.2 + optional: true + + uuid@8.3.2: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + workerpool@6.5.1: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 5.0.10 + + ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10): + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 5.0.10 + + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yn@2.0.0: {} + + yocto-queue@0.1.0: {} diff --git a/tokens/token-swap/steel/program/Cargo.toml b/tokens/token-swap/steel/program/Cargo.toml new file mode 100644 index 000000000..bd0876262 --- /dev/null +++ b/tokens/token-swap/steel/program/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "token-swap-program" +description = "" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +readme.workspace = true +keywords.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +token-swap-api.workspace = true +solana-program.workspace = true +steel.workspace = true +spl-token.workspace = true +spl-math.workspace = true + +[dev-dependencies] +bs64 = "0.1.2" +rand = "0.8.5" +solana-program-test = "1.18" +solana-sdk = "1.18" +tokio = { version = "1.35", features = ["full"] } diff --git a/tokens/token-swap/steel/program/src/create_amm.rs b/tokens/token-swap/steel/program/src/create_amm.rs new file mode 100644 index 000000000..58cd181d8 --- /dev/null +++ b/tokens/token-swap/steel/program/src/create_amm.rs @@ -0,0 +1,40 @@ +use steel::*; +use token_swap_api::prelude::*; + +pub fn process_create_amm(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Load accounts. + let [payer_info, admin_info, amm_info, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let args = CreateAmm::try_from_bytes(data)?; + let id = args.id; + let fee = args.fee; + + // Check fee is valid. + if u16::from_le_bytes(fee) > 10000 { + return Err(TokenSwapError::InvalidFee.into()); + } + + // check payer is signer of the transaction + payer_info.is_signer()?; + amm_info + .is_empty()? + .is_writable()? + .has_seeds(&[id.as_ref()], &token_swap_api::ID)?; + system_program.is_program(&system_program::ID)?; + + // Initialize amm account. + create_account::( + amm_info, + system_program, + payer_info, + &token_swap_api::ID, + &[id.as_ref()], + )?; + let amm = amm_info.as_account_mut::(&token_swap_api::ID)?; + amm.admin = *admin_info.key; + amm.id = id; + amm.fee = fee; + Ok(()) +} diff --git a/tokens/token-swap/steel/program/src/create_pool.rs b/tokens/token-swap/steel/program/src/create_pool.rs new file mode 100644 index 000000000..dbc235e88 --- /dev/null +++ b/tokens/token-swap/steel/program/src/create_pool.rs @@ -0,0 +1,141 @@ +use solana_program::program_pack::Pack; +use spl_token::state::Mint; +use steel::*; +use token_swap_api::prelude::*; + +pub fn process_create_pool(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { + // Load accounts. + let [payer_info, amm_info, pool_info, pool_authority_info, mint_liquidity_info, mint_a_info, mint_b_info, pool_account_a_info, pool_account_b_info, token_program, system_program, associated_token_program, rent_sysvar] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Check payer account is signer. + payer_info.is_signer()?; + token_program.is_program(&spl_token::ID)?; + system_program.is_program(&system_program::ID)?; + associated_token_program.is_program(&ASSOCIATED_TOKEN_PROGRAM_ID)?; + + // Check amm account is owned by token_swap_api::ID. + amm_info.has_owner(&token_swap_api::ID)?; + + // Check pool account is owned by token_swap_api::ID. + pool_info.is_empty()?.is_writable()?.has_seeds( + &[ + amm_info.key.as_ref(), + mint_a_info.key.as_ref(), + mint_b_info.key.as_ref(), + ], + &token_swap_api::ID, + )?; + + // Check pool_authority account + pool_authority_info.is_empty()?.has_seeds( + &[ + amm_info.key.as_ref(), + mint_a_info.key.as_ref(), + mint_b_info.key.as_ref(), + AUTHORITY_SEED, + ], + &token_swap_api::ID, + )?; + + // Check mint_liquidity account + mint_liquidity_info.is_empty()?.is_writable()?.has_seeds( + &[ + amm_info.key.as_ref(), + mint_a_info.key.as_ref(), + mint_b_info.key.as_ref(), + LIQUIDITY_SEED, + ], + &token_swap_api::ID, + )?; + + // Verify mint_a and mint_b is a mint account. + let _mint_a = mint_a_info.as_mint()?; + let _mint_b = mint_b_info.as_mint()?; + + // Verify pool_account_a and pool_account_b is + pool_account_a_info.is_empty()?.is_writable()?; + + pool_account_b_info.is_empty()?.is_writable()?; + + // init pool account + create_account::( + pool_info, + system_program, + payer_info, + &token_swap_api::ID, + &[ + amm_info.key.as_ref(), + mint_a_info.key.as_ref(), + mint_b_info.key.as_ref(), + ], + )?; + + // get mint_liquidity_info bump to save + let (_, bump) = pool_authority_pda(*amm_info.key, *mint_a_info.key, *mint_b_info.key); + + let pool_info_data = pool_info.as_account_mut::(&token_swap_api::ID)?; + pool_info_data.amm = *amm_info.key; + pool_info_data.mint_a = *mint_a_info.key; + pool_info_data.mint_b = *mint_b_info.key; + pool_info_data.pool_authority_bump = bump; + + let (_, bump) = mint_liquidity_pda(*amm_info.key, *mint_a_info.key, *mint_b_info.key); + // allocate mint_liquidity account + allocate_account_with_bump( + mint_liquidity_info, + system_program, + payer_info, + Mint::LEN, + &spl_token::ID, + &[ + amm_info.key.as_ref(), + mint_a_info.key.as_ref(), + mint_b_info.key.as_ref(), + LIQUIDITY_SEED, + ], + bump, + )?; + + // init mint_liquidity account + solana_program::program::invoke( + &spl_token::instruction::initialize_mint( + &spl_token::ID, + mint_liquidity_info.key, + pool_authority_info.key, + Some(pool_authority_info.key), + 9, + )?, + &[ + token_program.clone(), + mint_liquidity_info.clone(), + pool_authority_info.clone(), + rent_sysvar.clone(), + ], + )?; + + create_associated_token_account( + payer_info, + pool_authority_info, + pool_account_a_info, + mint_a_info, + system_program, + token_program, + associated_token_program, + )?; + + create_associated_token_account( + payer_info, + pool_authority_info, + pool_account_b_info, + mint_b_info, + system_program, + token_program, + associated_token_program, + )?; + + Ok(()) +} diff --git a/tokens/token-swap/steel/program/src/deposit_liquidity.rs b/tokens/token-swap/steel/program/src/deposit_liquidity.rs new file mode 100644 index 000000000..619628c3b --- /dev/null +++ b/tokens/token-swap/steel/program/src/deposit_liquidity.rs @@ -0,0 +1,179 @@ +use spl_math::uint::U256; +use steel::*; +use token_swap_api::prelude::*; +pub fn process_deposit_liquidity(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Load accounts. + let [payer_info, depositor_info, pool_info, pool_authority_info, mint_liquidity_info, mint_a_info, mint_b_info, pool_account_a_info, pool_account_b_info, depositor_account_liquidity_info, depositor_account_a_info, depositor_account_b_info, token_program, system_program, associated_token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let args = DepositLiquidity::try_from_bytes(data)?; + let amount_a = u64::from_le_bytes(args.amount_a); + let amount_b = u64::from_le_bytes(args.amount_b); + + // Check payer account is signer and program is the correct program. + payer_info.is_signer()?; + token_program.is_program(&spl_token::ID)?; + system_program.is_program(&system_program::ID)?; + associated_token_program.is_program(&ASSOCIATED_TOKEN_PROGRAM_ID)?; + + // check if depositor is signer + depositor_info.is_signer()?; + + // Verify mint_a and mint_b is a mint account. + let _mint_a = mint_a_info.as_mint()?; + let _mint_b = mint_b_info.as_mint()?; + + // validate pool account + + if pool_info.data_is_empty() { + return Err(TokenSwapError::AccountIsNotExisted.into()); + } + validate_pool_account(pool_info, *mint_a_info.key, *mint_b_info.key)?; + + let pool_info_data = pool_info.as_account_mut::(&token_swap_api::ID)?; + + // validate pool authority + validate_pool_authority( + pool_info_data, + pool_authority_info, + *mint_a_info.key, + *mint_b_info.key, + )?; + + // validate mint liquidity + validate_mint_liquidity( + pool_info_data, + mint_liquidity_info, + *mint_a_info.key, + *mint_b_info.key, + )?; + + // // validate pool_account_a_info, pool_account_b_info + let pool_account_a = pool_account_a_info + .is_writable()? + .as_associated_token_account(pool_authority_info.key, mint_a_info.key)?; + let pool_account_b = pool_account_b_info + .is_writable()? + .as_associated_token_account(pool_authority_info.key, mint_b_info.key)?; + + // validate depositor_account_a_info and depositor_account_b_info + let depositor_account_a = depositor_account_a_info + .is_writable()? + .as_associated_token_account(depositor_info.key, mint_a_info.key)?; + let depositor_account_b = depositor_account_b_info + .is_writable()? + .as_associated_token_account(depositor_info.key, mint_b_info.key)?; + + // Prevent depositing assets the depositor does not own + let mut amount_a = if amount_a > depositor_account_a.amount { + depositor_account_a.amount + } else { + amount_a + }; + let mut amount_b = if amount_b > depositor_account_b.amount { + depositor_account_b.amount + } else { + amount_b + }; + + // Defining pool creation like this allows attackers to frontrun pool creation with bad ratios + let pool_creation = pool_account_a.amount == 0 && pool_account_b.amount == 0; + + (amount_a, amount_b) = if pool_creation { + // Add as is if there is no liquidity + (amount_a, amount_b) + } else { + let ratio = U256::from(pool_account_a.amount) + .checked_mul(U256::from(pool_account_b.amount)) + .unwrap(); + if pool_account_a.amount > pool_account_b.amount { + ( + U256::from(amount_b).checked_mul(ratio).unwrap().as_u64(), + amount_b, + ) + } else { + ( + amount_a, + U256::from(amount_a).checked_div(ratio).unwrap().as_u64(), + ) + } + }; + + // Transfer tokens to the pool + transfer( + depositor_info, + depositor_account_a_info, + pool_account_a_info, + token_program, + amount_a, + )?; + + transfer( + depositor_info, + depositor_account_b_info, + pool_account_b_info, + token_program, + amount_b, + )?; + + // Computing the amount of liquidity about to be deposited + let mut liquidity = U256::from(amount_a) + .checked_mul(U256::from(amount_b)) + .unwrap() + .integer_sqrt() + .as_u64(); + + // Lock some minimum liquidity on the first deposit + if pool_creation { + if liquidity < MINIMUM_LIQUIDITY { + return Err(TokenSwapError::DepositTooSmall.into()); + } + + liquidity -= MINIMUM_LIQUIDITY; + } + + if depositor_account_liquidity_info.data_is_empty() { + // Create the depositor's liquidity account if it does not exist + create_associated_token_account( + payer_info, + depositor_info, + depositor_account_liquidity_info, + mint_liquidity_info, + system_program, + token_program, + associated_token_program, + )?; + } + + // Mint the liquidity to user + let seeds = &[ + pool_info_data.amm.as_ref(), + pool_info_data.mint_a.as_ref(), + pool_info_data.mint_b.as_ref(), + AUTHORITY_SEED, + &[pool_info_data.pool_authority_bump], + ]; + let signer_seeds = &[&seeds[..]]; + solana_program::program::invoke_signed( + &spl_token::instruction::mint_to( + &spl_token::id(), + mint_liquidity_info.key, + depositor_account_liquidity_info.key, + pool_authority_info.key, + &[pool_authority_info.key], + liquidity, + )?, + &[ + token_program.clone(), + mint_liquidity_info.clone(), + depositor_account_liquidity_info.clone(), + pool_authority_info.clone(), + ], + signer_seeds, + )?; + + Ok(()) +} diff --git a/tokens/token-swap/steel/program/src/lib.rs b/tokens/token-swap/steel/program/src/lib.rs new file mode 100644 index 000000000..87ebc8f93 --- /dev/null +++ b/tokens/token-swap/steel/program/src/lib.rs @@ -0,0 +1,33 @@ +mod create_amm; +mod create_pool; +mod deposit_liquidity; +mod swap; +mod withdraw_liquidity; +use create_amm::*; +use create_pool::*; +use deposit_liquidity::*; +use swap::*; +use withdraw_liquidity::*; + +use steel::*; +use token_swap_api::prelude::*; + +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let (ix, data) = parse_instruction(&token_swap_api::ID, program_id, data)?; + + match ix { + TokenSwapInstruction::CreateAmm => process_create_amm(accounts, data)?, + TokenSwapInstruction::CreatePool => process_create_pool(accounts, data)?, + TokenSwapInstruction::DepositLiquidity => process_deposit_liquidity(accounts, data)?, + TokenSwapInstruction::WithdrawLiquidity => process_withdraw_liquidity(accounts, data)?, + TokenSwapInstruction::Swap => process_swap(accounts, data)?, + } + + Ok(()) +} + +entrypoint!(process_instruction); diff --git a/tokens/token-swap/steel/program/src/swap.rs b/tokens/token-swap/steel/program/src/swap.rs new file mode 100644 index 000000000..3792dcc5d --- /dev/null +++ b/tokens/token-swap/steel/program/src/swap.rs @@ -0,0 +1,188 @@ +use spl_math::uint::U256; +use steel::*; +use token_swap_api::prelude::*; +pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Load accounts. + let [payer_info, trader_info, amm_info, pool_info, pool_authority_info, mint_a_info, mint_b_info, pool_account_a_info, pool_account_b_info, trader_account_a_info, trader_account_b_info, token_program, system_program, associated_token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let args = Swap::try_from_bytes(data)?; + let swap_a: bool = args.swap_a == 1; + let input_amount = u64::from_le_bytes(args.input_amount); + let min_output_amount = u64::from_le_bytes(args.min_output_amount); + + // Check payer account is signer and program is the correct program. + payer_info.is_signer()?; + token_program.is_program(&spl_token::ID)?; + system_program.is_program(&system_program::ID)?; + associated_token_program.is_program(&ASSOCIATED_TOKEN_PROGRAM_ID)?; + + // check if depositor is signer + trader_info.is_signer()?; + + // Verify mint_a and mint_b is a mint account. + let _mint_a = mint_a_info.as_mint()?; + let _mint_b = mint_b_info.as_mint()?; + + // validate pool account + + if pool_info.data_is_empty() { + return Err(TokenSwapError::AccountIsNotExisted.into()); + } + validate_pool_account(pool_info, *mint_a_info.key, *mint_b_info.key)?; + + let pool_info_data = pool_info.as_account_mut::(&token_swap_api::ID)?; + + // validate pool authority + validate_pool_authority( + pool_info_data, + pool_authority_info, + *mint_a_info.key, + *mint_b_info.key, + )?; + + // Check amm account is owned by token_swap_api::ID. + amm_info.has_owner(&token_swap_api::ID)?; + + // // validate pool_account_a_info, pool_account_b_info + let pool_account_a = pool_account_a_info + .is_writable()? + .as_associated_token_account(pool_authority_info.key, mint_a_info.key)?; + let pool_account_b = pool_account_b_info + .is_writable()? + .as_associated_token_account(pool_authority_info.key, mint_b_info.key)?; + + // check if trader_account_a and trader_account_b is exists + + if trader_account_a_info.data_is_empty() { + // Create the depositor's liquidity account if it does not exist + create_associated_token_account( + payer_info, + trader_info, + trader_account_a_info, + mint_a_info, + system_program, + token_program, + associated_token_program, + )?; + } + + if trader_account_b_info.data_is_empty() { + // Create the depositor's liquidity account if it does not exist + create_associated_token_account( + payer_info, + trader_info, + trader_account_b_info, + mint_b_info, + system_program, + token_program, + associated_token_program, + )?; + } + + let trader_account_a = + trader_account_a_info.as_associated_token_account(trader_info.key, mint_a_info.key)?; + let trader_account_b = + trader_account_b_info.as_associated_token_account(trader_info.key, mint_b_info.key)?; + + // Prevent depositing assets the depositor does not own + let input = if swap_a && input_amount > trader_account_a.amount { + trader_account_a.amount + } else if !swap_a && input_amount > trader_account_b.amount { + trader_account_b.amount + } else { + input_amount + }; + + // Apply trading fee, used to compute the output + let amm = amm_info.as_account_mut::(&token_swap_api::ID)?; + let fee = u16::from_le_bytes(amm.fee); + let taxed_input = input - input * (fee as u64) / 10000; + + let output = if swap_a { + U256::from(taxed_input) + .checked_mul(U256::from(pool_account_b.amount)) + .unwrap() + .checked_div( + U256::from(pool_account_a.amount) + .checked_add(U256::from(taxed_input)) + .unwrap(), + ) + .unwrap() + } else { + U256::from(taxed_input) + .checked_mul(U256::from(pool_account_a.amount)) + .unwrap() + .checked_div( + U256::from(pool_account_b.amount) + .checked_add(U256::from(taxed_input)) + .unwrap(), + ) + .unwrap() + } + .as_u64(); + + if output < min_output_amount { + return Err(TokenSwapError::OutputTooSmall.into()); + } + + let pool_authority_seeds = &[ + pool_info_data.amm.as_ref(), + pool_info_data.mint_a.as_ref(), + pool_info_data.mint_b.as_ref(), + AUTHORITY_SEED, + ]; + + // // Compute the invariant before the trade + let invariant = pool_account_a.amount * pool_account_b.amount; + + if swap_a { + transfer( + trader_info, + trader_account_a_info, + pool_account_a_info, + token_program, + input, + )?; + transfer_signed_with_bump( + pool_authority_info, + pool_account_b_info, + trader_account_b_info, + token_program, + output, + pool_authority_seeds, + pool_info_data.pool_authority_bump, + )?; + } else { + transfer_signed_with_bump( + pool_authority_info, + pool_account_a_info, + trader_account_a_info, + token_program, + input, + pool_authority_seeds, + pool_info_data.pool_authority_bump, + )?; + transfer( + trader_info, + trader_account_b_info, + pool_account_b_info, + token_program, + output, + )?; + } + + let pool_account_a = pool_account_a_info + .as_associated_token_account(pool_authority_info.key, mint_a_info.key)?; + let pool_account_b = pool_account_b_info + .as_associated_token_account(pool_authority_info.key, mint_b_info.key)?; + + if invariant > pool_account_a.amount * pool_account_b.amount { + return Err(TokenSwapError::InvariantViolated.into()); + } + + Ok(()) +} diff --git a/tokens/token-swap/steel/program/src/withdraw_liquidity.rs b/tokens/token-swap/steel/program/src/withdraw_liquidity.rs new file mode 100644 index 000000000..95fb47c2c --- /dev/null +++ b/tokens/token-swap/steel/program/src/withdraw_liquidity.rs @@ -0,0 +1,123 @@ +use spl_math::uint::U256; +use steel::*; +use token_swap_api::prelude::*; +pub fn process_withdraw_liquidity(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Load accounts. + let [payer_info, depositor_info, pool_info, pool_authority_info, mint_liquidity_info, mint_a_info, mint_b_info, pool_account_a_info, pool_account_b_info, depositor_account_liquidity_info, depositor_account_a_info, depositor_account_b_info, token_program, system_program, associated_token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let args = WithdrawLiquidity::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Check payer account is signer and program is the correct program. + payer_info.is_signer()?; + token_program.is_program(&spl_token::ID)?; + system_program.is_program(&system_program::ID)?; + associated_token_program.is_program(&ASSOCIATED_TOKEN_PROGRAM_ID)?; + + // check if depositor is signer + depositor_info.is_signer()?; + + // Verify mint_a and mint_b is a mint account. + let _mint_a = mint_a_info.as_mint()?; + let _mint_b = mint_b_info.as_mint()?; + + // validate pool account + + if pool_info.data_is_empty() { + return Err(TokenSwapError::AccountIsNotExisted.into()); + } + validate_pool_account(pool_info, *mint_a_info.key, *mint_b_info.key)?; + + let pool_info_data = pool_info.as_account_mut::(&token_swap_api::ID)?; + + // validate pool authority + validate_pool_authority( + pool_info_data, + pool_authority_info, + *mint_a_info.key, + *mint_b_info.key, + )?; + + // validate mint liquidity + validate_mint_liquidity( + pool_info_data, + mint_liquidity_info, + *mint_a_info.key, + *mint_b_info.key, + )?; + + // // validate pool_account_a_info, pool_account_b_info + let pool_account_a = pool_account_a_info + .is_writable()? + .as_associated_token_account(pool_authority_info.key, mint_a_info.key)?; + let pool_account_b = pool_account_b_info + .is_writable()? + .as_associated_token_account(pool_authority_info.key, mint_b_info.key)?; + + // validate depositor_account_a_info and depositor_account_b_info + let _depositor_account_a = depositor_account_a_info + .is_writable()? + .as_associated_token_account(depositor_info.key, mint_a_info.key)?; + let _depositor_account_b = depositor_account_b_info + .is_writable()? + .as_associated_token_account(depositor_info.key, mint_b_info.key)?; + + let pool_authority_seeds = &[ + pool_info_data.amm.as_ref(), + pool_info_data.mint_a.as_ref(), + pool_info_data.mint_b.as_ref(), + AUTHORITY_SEED, + ]; + + // Transfer tokens from the pool + let mint_liquidity = mint_liquidity_info.as_mint()?; + let amount_a = U256::from(amount) + .checked_mul(U256::from(pool_account_a.amount)) + .unwrap() + .checked_div(U256::from(mint_liquidity.supply + MINIMUM_LIQUIDITY)) + .unwrap() + .as_u64(); + + transfer_signed_with_bump( + pool_authority_info, + pool_account_a_info, + depositor_account_a_info, + token_program, + amount_a, + pool_authority_seeds, + pool_info_data.pool_authority_bump, + )?; + + let amount_b = U256::from(amount) + .checked_mul(U256::from(pool_account_b.amount)) + .unwrap() + .checked_div(U256::from(mint_liquidity.supply + MINIMUM_LIQUIDITY)) + .unwrap() + .as_u64(); + + transfer_signed_with_bump( + pool_authority_info, + pool_account_b_info, + depositor_account_b_info, + token_program, + amount_b, + pool_authority_seeds, + pool_info_data.pool_authority_bump, + )?; + + // Burn the liquidity tokens + // It will fail if the amount is invalid + burn( + depositor_account_liquidity_info, + mint_liquidity_info, + depositor_info, + token_program, + amount, + )?; + + Ok(()) +} diff --git a/tokens/token-swap/steel/program/tests/test.rs b/tokens/token-swap/steel/program/tests/test.rs new file mode 100644 index 000000000..08cfaa618 --- /dev/null +++ b/tokens/token-swap/steel/program/tests/test.rs @@ -0,0 +1,60 @@ +use solana_program::hash::Hash; +use solana_program_test::{processor, BanksClient, ProgramTest}; +use solana_sdk::{ + native_token::LAMPORTS_PER_SOL, signature::Keypair, signer::Signer, system_instruction, + transaction::Transaction, +}; +use steel::*; +use token_swap_api::prelude::*; + +async fn setup() -> (BanksClient, Keypair, Hash) { + let mut program_test = ProgramTest::new( + "token_swap_program", + token_swap_api::ID, + processor!(token_swap_program::process_instruction), + ); + program_test.prefer_bpf(true); + program_test.start().await +} + +#[tokio::test] +async fn run_test() { + // Setup test + let (mut banks, payer, blockhash) = setup().await; + + let admin = Keypair::new(); + let id = Keypair::new(); + let fee = 1000; // 10% + + // // create admin account + // let tx = Transaction::new_signed_with_payer( + // &[system_instruction::create_account( + // &payer.pubkey(), + // &admin.pubkey(), + // 1 * LAMPORTS_PER_SOL, + // 0, + // &token_swap_api::ID, + // )], + // Some(&payer.pubkey()), + // &[&payer, &admin], + // blockhash, + // ); + + // let res = banks.process_transaction(tx).await; + // assert!(res.is_ok()); + + // Submit initialize transaction. + let ix = create_amm(payer.pubkey(), admin.pubkey(), id.pubkey(), fee); + let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); + let res = banks.process_transaction(tx).await; + + assert!(res.is_ok()); + + let amm_address = amm_pda(id.pubkey()).0; + let amm_account = banks.get_account(amm_address).await.unwrap().unwrap(); + let amm = Amm::try_from_bytes(&amm_account.data).unwrap(); + assert_eq!(amm_account.owner, token_swap_api::ID); + assert_eq!(amm.id, id.pubkey()); + assert_eq!(amm.admin, admin.pubkey()); + assert_eq!(u16::from_le_bytes(amm.fee), fee); +} diff --git a/tokens/token-swap/steel/tests/create_pool_and_swap.test.ts b/tokens/token-swap/steel/tests/create_pool_and_swap.test.ts new file mode 100644 index 000000000..94f9a432d --- /dev/null +++ b/tokens/token-swap/steel/tests/create_pool_and_swap.test.ts @@ -0,0 +1,294 @@ +import { Connection, Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, Transaction, TransactionInstruction } from '@solana/web3.js'; +import { AccountLayout, ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token'; +import { assert } from 'chai'; +import { describe, it } from 'mocha'; +import { BanksClient, ProgramTestContext, start } from 'solana-bankrun'; +import { + createAMint, + deserializeAmmAccount, + deserializePoolAccount, + getCreateAmmInstructionData, + getCreatePoolInstructionData, + getDepositLiquidityInstructionData, + getSwapInstructionData, + mintTo, +} from './utils'; + +const PROGRAM_ID = new PublicKey('z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35'); + +describe('Token Swap Program: Create and swap', () => { + let context: ProgramTestContext; + let client: BanksClient; + let payer: Keypair; + const mint_a = Keypair.generate(); + const mint_b = Keypair.generate(); + const admin = Keypair.generate(); + const trader = Keypair.generate(); + const fee = 500; // 5% + + const id = Keypair.generate(); + const [ammPda] = PublicKey.findProgramAddressSync([id.publicKey.toBuffer()], PROGRAM_ID); + + const [poolPda] = PublicKey.findProgramAddressSync([ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer()], PROGRAM_ID); + + const [poolAuthorityPda] = PublicKey.findProgramAddressSync( + [ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer(), Buffer.from('authority')], + PROGRAM_ID, + ); + + const [mintLiquidityPda] = PublicKey.findProgramAddressSync( + [ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer(), Buffer.from('liquidity')], + PROGRAM_ID, + ); + + const poolAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, poolAuthorityPda, true); + + const poolAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, poolAuthorityPda, true); + + let depositorAccountLp: PublicKey; + let depositorAccountA: PublicKey; + let depositorAccountB: PublicKey; + let traderAccountA: PublicKey; + let traderAccountB: PublicKey; + + const MINIMUM_LIQUIDITY = 100; + + const amountA = BigInt(4 * 10 ** 9); + const amountB = BigInt(1 * 10 ** 9); + const amountLp = BigInt(Math.sqrt(Number(amountA) * Number(amountB)) - MINIMUM_LIQUIDITY); + + before(async () => { + context = await start([{ name: 'token_swap_program', programId: PROGRAM_ID }], []); + client = context.banksClient; + payer = context.payer; + console.log(mint_a.publicKey.toBase58(), payer.publicKey.toBase58()); + await createAMint(context, payer, mint_a); + await createAMint(context, payer, mint_b); + + depositorAccountLp = getAssociatedTokenAddressSync(mintLiquidityPda, payer.publicKey, false); + + depositorAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, payer.publicKey, false); + + depositorAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, payer.publicKey, false); + + traderAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, trader.publicKey, false); + + traderAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, trader.publicKey, false); + + await mintTo(context, payer, payer.publicKey, mint_a.publicKey); + await mintTo(context, payer, payer.publicKey, mint_b.publicKey); + await mintTo(context, payer, trader.publicKey, mint_a.publicKey); + await mintTo(context, payer, trader.publicKey, mint_b.publicKey); + }); + + it('Should create a new amm successfully', async () => { + const tx = new Transaction(); + tx.add( + new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [ + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: admin.publicKey, isSigner: false, isWritable: false }, + { pubkey: ammPda, isSigner: false, isWritable: true }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + ], + data: getCreateAmmInstructionData(id.publicKey, fee), + }), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer); + + // process the transaction + await client.processTransaction(tx); + + const ammAccount = await client.getAccount(ammPda); + assert.isNotNull(ammAccount); + assert.equal(ammAccount?.owner.toBase58(), PROGRAM_ID.toBase58()); + const ammAccountData = deserializeAmmAccount(ammAccount.data); + + assert.equal(ammAccountData.id.toBase58(), id.publicKey.toBase58()); + assert.equal(ammAccountData.admin.toBase58(), admin.publicKey.toBase58()); + assert.equal(ammAccountData.fee, fee); + }); + + it('Should create a new pool successfully', async () => { + const tx = new Transaction(); + tx.add( + new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [ + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: ammPda, isSigner: false, isWritable: false }, + { pubkey: poolPda, isSigner: false, isWritable: true }, + { pubkey: poolAuthorityPda, isSigner: false, isWritable: false }, + { pubkey: mintLiquidityPda, isSigner: false, isWritable: true }, + { pubkey: mint_a.publicKey, isSigner: false, isWritable: false }, + { pubkey: mint_b.publicKey, isSigner: false, isWritable: false }, + { pubkey: poolAccountA, isSigner: false, isWritable: true }, + { pubkey: poolAccountB, isSigner: false, isWritable: true }, + { + pubkey: TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false, + }, + ], + data: getCreatePoolInstructionData(), + }), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer); + + // process the transaction + await client.processTransaction(tx); + + const poolPdaAccount = await client.getAccount(poolPda); + assert.isNotNull(poolPdaAccount); + assert.equal(poolPdaAccount?.owner.toBase58(), PROGRAM_ID.toBase58()); + + const data = deserializePoolAccount(poolPdaAccount.data); + assert.equal(data.amm.toBase58(), ammPda.toBase58()); + assert.equal(data.mintA.toBase58(), mint_a.publicKey.toBase58()); + assert.equal(data.mintB.toBase58(), mint_b.publicKey.toBase58()); + }); + + it('Should deposit liquidity successfully', async () => { + const tx = new Transaction(); + tx.add( + new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [ + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: poolPda, isSigner: false, isWritable: true }, + { pubkey: poolAuthorityPda, isSigner: false, isWritable: false }, + { pubkey: mintLiquidityPda, isSigner: false, isWritable: true }, + { pubkey: mint_a.publicKey, isSigner: false, isWritable: false }, + { pubkey: mint_b.publicKey, isSigner: false, isWritable: false }, + { pubkey: poolAccountA, isSigner: false, isWritable: true }, + { pubkey: poolAccountB, isSigner: false, isWritable: true }, + { pubkey: depositorAccountLp, isSigner: false, isWritable: true }, + { pubkey: depositorAccountA, isSigner: false, isWritable: true }, + { pubkey: depositorAccountB, isSigner: false, isWritable: true }, + { + pubkey: TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + ], + data: getDepositLiquidityInstructionData(amountA, amountB), + }), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer); + + // process the transaction + await client.processTransaction(tx); + + const rawDepositorAccountLp = await client.getAccount(depositorAccountLp); + assert.isNotNull(rawDepositorAccountLp); + const decodedDepositorAccountLp = AccountLayout.decode(rawDepositorAccountLp?.data); + assert.equal(decodedDepositorAccountLp.amount, amountLp); + + const expectedLpAmount = Math.sqrt(Number(amountA) * Number(amountB)) - MINIMUM_LIQUIDITY; + + const rawLiquidityMint = await client.getAccount(mintLiquidityPda); + assert.isNotNull(rawLiquidityMint); + const decodedLiquidityMint = MintLayout.decode(rawLiquidityMint.data); + assert.equal(decodedLiquidityMint.supply, BigInt(expectedLpAmount)); + + const rawPoolAccountA = await client.getAccount(poolAccountA); + assert.isNotNull(rawPoolAccountA); + const decodedPoolAccountA = AccountLayout.decode(rawPoolAccountA?.data); + assert.equal(decodedPoolAccountA.amount, amountA); + + const rawPoolAccountB = await client.getAccount(poolAccountB); + assert.isNotNull(rawPoolAccountB); + const decodedPoolAccountB = AccountLayout.decode(rawPoolAccountB?.data); + assert.equal(decodedPoolAccountB.amount, amountB); + }); + + it('Should swap successfully', async () => { + const swapAmount = BigInt(10 ** 9); + const minimunAmountOut = BigInt(100); + const tx = new Transaction(); + tx.add( + new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [ + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: trader.publicKey, isSigner: true, isWritable: true }, + { pubkey: ammPda, isSigner: false, isWritable: true }, + { pubkey: poolPda, isSigner: false, isWritable: true }, + { pubkey: poolAuthorityPda, isSigner: false, isWritable: false }, + { pubkey: mint_a.publicKey, isSigner: false, isWritable: false }, + { pubkey: mint_b.publicKey, isSigner: false, isWritable: false }, + { pubkey: poolAccountA, isSigner: false, isWritable: true }, + { pubkey: poolAccountB, isSigner: false, isWritable: true }, + { pubkey: traderAccountA, isSigner: false, isWritable: true }, + { pubkey: traderAccountB, isSigner: false, isWritable: true }, + { + pubkey: TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + ], + data: getSwapInstructionData(true, swapAmount, minimunAmountOut), + }), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer, trader); + + // process the transaction + await client.processTransaction(tx); + + const rawTraderAccountA = await client.getAccount(traderAccountA); + assert.isNotNull(rawTraderAccountA); + const decodedTraderAccountA = AccountLayout.decode(rawTraderAccountA?.data); + + assert.equal(decodedTraderAccountA.amount, BigInt(999000000000)); + + const rawTraderAccountB = await client.getAccount(traderAccountB); + assert.isNotNull(rawTraderAccountB); + const decodedTraderAccountB = AccountLayout.decode(rawTraderAccountB?.data); + assert.equal(decodedTraderAccountB.amount, BigInt(1000191919191)); + }); +}); diff --git a/tokens/token-swap/steel/tests/create_pool_and_withdraw_all_liquid.test.ts b/tokens/token-swap/steel/tests/create_pool_and_withdraw_all_liquid.test.ts new file mode 100644 index 000000000..f22868e9c --- /dev/null +++ b/tokens/token-swap/steel/tests/create_pool_and_withdraw_all_liquid.test.ts @@ -0,0 +1,313 @@ +import { Connection, Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, Transaction, TransactionInstruction } from '@solana/web3.js'; +import { AccountLayout, ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token'; +import { assert } from 'chai'; +import { describe, it } from 'mocha'; +import { BanksClient, ProgramTestContext, start } from 'solana-bankrun'; +import { + createAMint, + deserializeAmmAccount, + deserializePoolAccount, + getCreateAmmInstructionData, + getCreatePoolInstructionData, + getDepositLiquidityInstructionData, + getWithdrawLiquidityInstructionData, + mintTo, +} from './utils'; + +const PROGRAM_ID = new PublicKey('z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35'); + +describe('Token Swap Program: Create and withdraw all liquidity', () => { + let context: ProgramTestContext; + let client: BanksClient; + let payer: Keypair; + const mint_a = Keypair.generate(); + const mint_b = Keypair.generate(); + const admin = Keypair.generate(); + const trader = Keypair.generate(); + const fee = 500; // 5% + + const id = Keypair.generate(); + const [ammPda] = PublicKey.findProgramAddressSync([id.publicKey.toBuffer()], PROGRAM_ID); + + const [poolPda] = PublicKey.findProgramAddressSync([ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer()], PROGRAM_ID); + + const [poolAuthorityPda] = PublicKey.findProgramAddressSync( + [ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer(), Buffer.from('authority')], + PROGRAM_ID, + ); + + const [mintLiquidityPda] = PublicKey.findProgramAddressSync( + [ammPda.toBuffer(), mint_a.publicKey.toBuffer(), mint_b.publicKey.toBuffer(), Buffer.from('liquidity')], + PROGRAM_ID, + ); + + const poolAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, poolAuthorityPda, true); + + const poolAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, poolAuthorityPda, true); + + let depositorAccountLp: PublicKey; + let depositorAccountA: PublicKey; + let depositorAccountB: PublicKey; + let traderAccountA: PublicKey; + let traderAccountB: PublicKey; + + const MINIMUM_LIQUIDITY = 100; + + const amountA = BigInt(4 * 10 ** 9); + const amountB = BigInt(1 * 10 ** 9); + const amountLp = BigInt(Math.sqrt(Number(amountA) * Number(amountB)) - MINIMUM_LIQUIDITY); + + before(async () => { + context = await start([{ name: 'token_swap_program', programId: PROGRAM_ID }], []); + client = context.banksClient; + payer = context.payer; + console.log(mint_a.publicKey.toBase58(), payer.publicKey.toBase58()); + await createAMint(context, payer, mint_a); + await createAMint(context, payer, mint_b); + + depositorAccountLp = getAssociatedTokenAddressSync(mintLiquidityPda, payer.publicKey, false); + + depositorAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, payer.publicKey, false); + + depositorAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, payer.publicKey, false); + + traderAccountA = getAssociatedTokenAddressSync(mint_a.publicKey, trader.publicKey, false); + + traderAccountB = getAssociatedTokenAddressSync(mint_b.publicKey, trader.publicKey, false); + + await mintTo(context, payer, payer.publicKey, mint_a.publicKey); + await mintTo(context, payer, payer.publicKey, mint_b.publicKey); + await mintTo(context, payer, trader.publicKey, mint_a.publicKey); + await mintTo(context, payer, trader.publicKey, mint_b.publicKey); + }); + + it('Should create a new amm successfully', async () => { + const tx = new Transaction(); + tx.add( + new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [ + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: admin.publicKey, isSigner: false, isWritable: false }, + { pubkey: ammPda, isSigner: false, isWritable: true }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + ], + data: getCreateAmmInstructionData(id.publicKey, fee), + }), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer); + + // process the transaction + await client.processTransaction(tx); + + const ammAccount = await client.getAccount(ammPda); + assert.isNotNull(ammAccount); + assert.equal(ammAccount?.owner.toBase58(), PROGRAM_ID.toBase58()); + const ammAccountData = deserializeAmmAccount(ammAccount.data); + + assert.equal(ammAccountData.id.toBase58(), id.publicKey.toBase58()); + assert.equal(ammAccountData.admin.toBase58(), admin.publicKey.toBase58()); + assert.equal(ammAccountData.fee, fee); + }); + + it('Should create a new pool successfully', async () => { + const tx = new Transaction(); + tx.add( + new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [ + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: ammPda, isSigner: false, isWritable: false }, + { pubkey: poolPda, isSigner: false, isWritable: true }, + { pubkey: poolAuthorityPda, isSigner: false, isWritable: false }, + { pubkey: mintLiquidityPda, isSigner: false, isWritable: true }, + { pubkey: mint_a.publicKey, isSigner: false, isWritable: false }, + { pubkey: mint_b.publicKey, isSigner: false, isWritable: false }, + { pubkey: poolAccountA, isSigner: false, isWritable: true }, + { pubkey: poolAccountB, isSigner: false, isWritable: true }, + { + pubkey: TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false, + }, + ], + data: getCreatePoolInstructionData(), + }), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer); + + // process the transaction + await client.processTransaction(tx); + + const poolPdaAccount = await client.getAccount(poolPda); + assert.isNotNull(poolPdaAccount); + assert.equal(poolPdaAccount?.owner.toBase58(), PROGRAM_ID.toBase58()); + + const data = deserializePoolAccount(poolPdaAccount.data); + assert.equal(data.amm.toBase58(), ammPda.toBase58()); + assert.equal(data.mintA.toBase58(), mint_a.publicKey.toBase58()); + assert.equal(data.mintB.toBase58(), mint_b.publicKey.toBase58()); + }); + + it('Should deposit liquidity successfully', async () => { + const tx = new Transaction(); + tx.add( + new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [ + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: poolPda, isSigner: false, isWritable: true }, + { pubkey: poolAuthorityPda, isSigner: false, isWritable: false }, + { pubkey: mintLiquidityPda, isSigner: false, isWritable: true }, + { pubkey: mint_a.publicKey, isSigner: false, isWritable: false }, + { pubkey: mint_b.publicKey, isSigner: false, isWritable: false }, + { pubkey: poolAccountA, isSigner: false, isWritable: true }, + { pubkey: poolAccountB, isSigner: false, isWritable: true }, + { pubkey: depositorAccountLp, isSigner: false, isWritable: true }, + { pubkey: depositorAccountA, isSigner: false, isWritable: true }, + { pubkey: depositorAccountB, isSigner: false, isWritable: true }, + { + pubkey: TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + ], + data: getDepositLiquidityInstructionData(amountA, amountB), + }), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer); + + // process the transaction + await client.processTransaction(tx); + + const rawDepositorAccountLp = await client.getAccount(depositorAccountLp); + assert.isNotNull(rawDepositorAccountLp); + const decodedDepositorAccountLp = AccountLayout.decode(rawDepositorAccountLp?.data); + assert.equal(decodedDepositorAccountLp.amount, amountLp); + + const expectedLpAmount = Math.sqrt(Number(amountA) * Number(amountB)) - MINIMUM_LIQUIDITY; + + const rawLiquidityMint = await client.getAccount(mintLiquidityPda); + assert.isNotNull(rawLiquidityMint); + const decodedLiquidityMint = MintLayout.decode(rawLiquidityMint.data); + assert.equal(decodedLiquidityMint.supply, BigInt(expectedLpAmount)); + + const rawPoolAccountA = await client.getAccount(poolAccountA); + assert.isNotNull(rawPoolAccountA); + const decodedPoolAccountA = AccountLayout.decode(rawPoolAccountA?.data); + assert.equal(decodedPoolAccountA.amount, amountA); + + const rawPoolAccountB = await client.getAccount(poolAccountB); + assert.isNotNull(rawPoolAccountB); + const decodedPoolAccountB = AccountLayout.decode(rawPoolAccountB?.data); + assert.equal(decodedPoolAccountB.amount, amountB); + }); + + it('Should withdraw all liquidity successfully', async () => { + const pre_rawLiquidityMint = await client.getAccount(mintLiquidityPda); + assert.isNotNull(pre_rawLiquidityMint); + const decodedPreLiquidityMint = MintLayout.decode(pre_rawLiquidityMint.data); + const expectedPostPoolAccountAAmount = BigInt( + Number(amountA) - (Number(amountLp) * Number(amountA)) / (Number(decodedPreLiquidityMint.supply) + MINIMUM_LIQUIDITY), + ); + + const expectedPostPoolAccountBAmount = BigInt( + Number(amountB) - (Number(amountLp) * Number(amountB)) / (Number(decodedPreLiquidityMint.supply) + MINIMUM_LIQUIDITY), + ); + + const tx = new Transaction(); + tx.add( + new TransactionInstruction({ + programId: PROGRAM_ID, + keys: [ + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: payer.publicKey, isSigner: true, isWritable: true }, + { pubkey: poolPda, isSigner: false, isWritable: true }, + { pubkey: poolAuthorityPda, isSigner: false, isWritable: false }, + { pubkey: mintLiquidityPda, isSigner: false, isWritable: true }, + { pubkey: mint_a.publicKey, isSigner: false, isWritable: false }, + { pubkey: mint_b.publicKey, isSigner: false, isWritable: false }, + { pubkey: poolAccountA, isSigner: false, isWritable: true }, + { pubkey: poolAccountB, isSigner: false, isWritable: true }, + { pubkey: depositorAccountLp, isSigner: false, isWritable: true }, + { pubkey: depositorAccountA, isSigner: false, isWritable: true }, + { pubkey: depositorAccountB, isSigner: false, isWritable: true }, + { + pubkey: TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false, + }, + { + pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, + isSigner: false, + isWritable: false, + }, + ], + data: getWithdrawLiquidityInstructionData(amountLp), + }), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer); + + // process the transaction + await client.processTransaction(tx); + + const rawDepositorAccountLp = await client.getAccount(depositorAccountLp); + assert.isNotNull(rawDepositorAccountLp); + const decodedDepositorAccountLp = AccountLayout.decode(rawDepositorAccountLp?.data); + assert.equal(decodedDepositorAccountLp.amount, BigInt(0)); + + const rawLiquidityMint = await client.getAccount(mintLiquidityPda); + assert.isNotNull(rawLiquidityMint); + const decodedLiquidityMint = MintLayout.decode(rawLiquidityMint.data); + assert.equal(decodedLiquidityMint.supply, BigInt(0)); + + const rawPoolAccountA = await client.getAccount(poolAccountA); + assert.isNotNull(rawPoolAccountA); + const decodedPoolAccountA = AccountLayout.decode(rawPoolAccountA?.data); + assert.equal(decodedPoolAccountA.amount, expectedPostPoolAccountAAmount); + + const rawPoolAccountB = await client.getAccount(poolAccountB); + assert.isNotNull(rawPoolAccountB); + const decodedPoolAccountB = AccountLayout.decode(rawPoolAccountB?.data); + assert.equal(decodedPoolAccountB.amount, expectedPostPoolAccountBAmount); + }); +}); diff --git a/tokens/token-swap/steel/tests/utils.ts b/tokens/token-swap/steel/tests/utils.ts new file mode 100644 index 000000000..372184c8d --- /dev/null +++ b/tokens/token-swap/steel/tests/utils.ts @@ -0,0 +1,149 @@ +import { + MINT_SIZE, + TOKEN_PROGRAM_ID, + createAssociatedTokenAccountInstruction, + createInitializeMint2Instruction, + createMintToInstruction, + getAssociatedTokenAddressSync, +} from '@solana/spl-token'; +import { Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction } from '@solana/web3.js'; +import * as borsh from 'borsh'; +import { ProgramTestContext } from 'solana-bankrun'; + +export const instructionDiscriminators = { + CreateAmm: Buffer.from([0]), + CreatePool: Buffer.from([1]), + DepositLiquidity: Buffer.from([2]), + WithdrawLiquidity: Buffer.from([3]), + Swap: Buffer.from([4]), +}; +export const getCreatePoolInstructionData = () => { + return Buffer.concat([instructionDiscriminators.CreatePool]); +}; + +export const encodeBigint = (value: bigint) => { + const buffer = Buffer.alloc(8); + buffer.writeBigUInt64LE(value); + return Uint8Array.from(buffer); +}; + +export const getDepositLiquidityInstructionData = (amountA: bigint, amountB: bigint) => { + return Buffer.concat([instructionDiscriminators.DepositLiquidity, encodeBigint(amountA), encodeBigint(amountB)]); +}; + +export const getWithdrawLiquidityInstructionData = (amountLp: bigint) => { + return Buffer.concat([instructionDiscriminators.WithdrawLiquidity, encodeBigint(amountLp)]); +}; + +export const getSwapInstructionData = (swapA: boolean, inputAmount: bigint, minimunAmountOut: bigint) => { + return Buffer.concat([instructionDiscriminators.Swap, Buffer.from([swapA ? 1 : 0]), encodeBigint(inputAmount), encodeBigint(minimunAmountOut)]); +}; + +export const getCreateAmmInstructionData = (id: PublicKey, fee: number) => { + const buffer = Buffer.alloc(2); + buffer.writeUint16LE(fee, 0); + return Buffer.concat([instructionDiscriminators.CreateAmm, id.toBuffer(), Buffer.from(buffer)]); +}; + +export const createAMint = async (context: ProgramTestContext, payer: Keypair, mint: Keypair) => { + const tx = new Transaction(); + tx.add( + SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + newAccountPubkey: mint.publicKey, + // the `space` required for a token mint is accessible in the `@solana/spl-token` sdk + space: MINT_SIZE, + // store enough lamports needed for our `space` to be rent exempt + lamports: Number((await context.banksClient.getRent()).minimumBalance(BigInt(MINT_SIZE))), + // tokens are owned by the "token program" + programId: TOKEN_PROGRAM_ID, + }), + createInitializeMint2Instruction(mint.publicKey, 9, payer.publicKey, payer.publicKey), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer, mint); + + // process the transaction + await context.banksClient.processTransaction(tx); +}; + +export const mintTo = async (context: ProgramTestContext, payer: Keypair, owner: PublicKey, mint: PublicKey) => { + const tokenAccount = getAssociatedTokenAddressSync(mint, owner, false); + const tx = new Transaction(); + tx.add( + createAssociatedTokenAccountInstruction(payer.publicKey, tokenAccount, owner, mint), + createMintToInstruction(mint, tokenAccount, payer.publicKey, 1_000 * LAMPORTS_PER_SOL), + ); + tx.recentBlockhash = context.lastBlockhash; + tx.sign(payer); + + // process the transaction + await context.banksClient.processTransaction(tx); + return tokenAccount; +}; + +// Define AmmAccount type +export type AmmAccount = { + id: PublicKey; + admin: PublicKey; + fee: number; +}; + +// Define DataAccountRaw type for deserialization +export type AmmAccountRaw = { + id: Uint8Array; + admin: Uint8Array; + fee: number; +}; + +// Define the schema for the account data +export const ammAccountSchema: borsh.Schema = { + struct: { + discriminator: 'u64', + id: { array: { type: 'u8', len: 32 } }, + admin: { array: { type: 'u8', len: 32 } }, + fee: 'u16', + }, +}; + +export const deserializeAmmAccount = (data: Uint8Array): AmmAccount => { + const account = borsh.deserialize(ammAccountSchema, data) as AmmAccountRaw; + return { + id: new PublicKey(account.id), + admin: new PublicKey(account.admin), + fee: account.fee, + }; +}; + +// Define AmmAccount type +export type PoolAccount = { + amm: PublicKey; + mintA: PublicKey; + mintB: PublicKey; +}; + +// Define DataAccountRaw type for deserialization +export type PoolAccountRaw = { + amm: Uint8Array; + mint_a: Uint8Array; + mint_b: Uint8Array; +}; + +// Define the schema for the account data +export const poolAccountSchema: borsh.Schema = { + struct: { + discriminator: 'u64', + amm: { array: { type: 'u8', len: 32 } }, + mint_a: { array: { type: 'u8', len: 32 } }, + mint_b: { array: { type: 'u8', len: 32 } }, + }, +}; + +export const deserializePoolAccount = (data: Uint8Array): PoolAccount => { + const account = borsh.deserialize(poolAccountSchema, data) as PoolAccountRaw; + return { + amm: new PublicKey(account.amm), + mintA: new PublicKey(account.mint_a), + mintB: new PublicKey(account.mint_b), + }; +}; diff --git a/tokens/token-swap/steel/tsconfig.json b/tokens/token-swap/steel/tsconfig.json new file mode 100644 index 000000000..8c20b2236 --- /dev/null +++ b/tokens/token-swap/steel/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai", "node"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +}