From 7322e575d705cd931e342865810ca4d0b5fbc592 Mon Sep 17 00:00:00 2001
From: Matt Acciai <matt@skip.money>
Date: Mon, 19 Aug 2024 14:30:37 -0400
Subject: [PATCH] add pryzm swap adapter

---
 contracts/adapters/swap/pryzm/Cargo.toml      |  35 +
 contracts/adapters/swap/pryzm/README.md       | 333 ++++++++
 .../swap/pryzm/src/bin/pryzm-schema.rs        |  10 +
 contracts/adapters/swap/pryzm/src/consts.rs   |   8 +
 contracts/adapters/swap/pryzm/src/contract.rs | 308 +++++++
 contracts/adapters/swap/pryzm/src/error.rs    |  50 ++
 .../adapters/swap/pryzm/src/execution.rs      | 224 +++++
 contracts/adapters/swap/pryzm/src/lib.rs      |   7 +
 contracts/adapters/swap/pryzm/src/reply_id.rs |   5 +
 contracts/adapters/swap/pryzm/src/simulate.rs | 363 +++++++++
 contracts/adapters/swap/pryzm/src/state.rs    |  13 +
 .../adapters/swap/pryzm/tests/mock/mod.rs     |  45 +
 .../swap/pryzm/tests/test_execute_reply.rs    | 274 +++++++
 .../swap/pryzm/tests/test_execute_swap.rs     | 419 ++++++++++
 .../tests/test_execute_transfer_funds_back.rs | 169 ++++
 .../swap/pryzm/tests/test_execution.rs        | 435 ++++++++++
 .../swap/pryzm/tests/test_simulate.rs         | 769 ++++++++++++++++++
 17 files changed, 3467 insertions(+)
 create mode 100644 contracts/adapters/swap/pryzm/Cargo.toml
 create mode 100644 contracts/adapters/swap/pryzm/README.md
 create mode 100644 contracts/adapters/swap/pryzm/src/bin/pryzm-schema.rs
 create mode 100644 contracts/adapters/swap/pryzm/src/consts.rs
 create mode 100644 contracts/adapters/swap/pryzm/src/contract.rs
 create mode 100644 contracts/adapters/swap/pryzm/src/error.rs
 create mode 100644 contracts/adapters/swap/pryzm/src/execution.rs
 create mode 100644 contracts/adapters/swap/pryzm/src/lib.rs
 create mode 100644 contracts/adapters/swap/pryzm/src/reply_id.rs
 create mode 100644 contracts/adapters/swap/pryzm/src/simulate.rs
 create mode 100644 contracts/adapters/swap/pryzm/src/state.rs
 create mode 100644 contracts/adapters/swap/pryzm/tests/mock/mod.rs
 create mode 100644 contracts/adapters/swap/pryzm/tests/test_execute_reply.rs
 create mode 100644 contracts/adapters/swap/pryzm/tests/test_execute_swap.rs
 create mode 100644 contracts/adapters/swap/pryzm/tests/test_execute_transfer_funds_back.rs
 create mode 100644 contracts/adapters/swap/pryzm/tests/test_execution.rs
 create mode 100644 contracts/adapters/swap/pryzm/tests/test_simulate.rs

diff --git a/contracts/adapters/swap/pryzm/Cargo.toml b/contracts/adapters/swap/pryzm/Cargo.toml
new file mode 100644
index 00000000..13aa581c
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name          = "skip-go-swap-adapter-pryzm"
+version       = { workspace = true }
+rust-version  = { workspace = true }
+authors       = { workspace = true }
+edition       = { workspace = true }
+license       = { workspace = true }
+homepage      = { workspace = true }
+repository    = { workspace = true }
+documentation = { workspace = true }
+keywords      = { workspace = true }
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[features]
+# for more explicit tests, cargo test --features=backtraces
+backtraces = ["cosmwasm-std/backtraces"]
+# use library feature to disable all instantiate/execute/query exports
+library = []
+
+[dependencies]
+cosmwasm-schema  = { workspace = true }
+cosmwasm-std     = { workspace = true }
+cw2              = { workspace = true }
+cw-storage-plus  = { workspace = true }
+cw-utils         = { workspace = true }
+pryzm-std        = { workspace = true }
+skip             = { workspace = true }
+thiserror        = { workspace = true }
+
+[dev-dependencies]
+test-case        = { workspace = true }
+mockall          = { workspace = true }
+serde            = { workspace = true }
\ No newline at end of file
diff --git a/contracts/adapters/swap/pryzm/README.md b/contracts/adapters/swap/pryzm/README.md
new file mode 100644
index 00000000..88a6e4ef
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/README.md
@@ -0,0 +1,333 @@
+# PRYZM Swap Adapter Contract
+
+The Pryzm swap adapter contract is responsible for:
+
+1. Taking the standardized entry point swap operations message format and converting it to the respective messages on
+   Pryzm.
+2. Swapping on Pryzm's [AMM](https://docs.pryzm.zone/core/amm) or liquid staking on
+   Pryzm's [ICStaking](https://docs.pryzm.zone/core/icstaking) module.
+3. Providing query methods that can be called by the entry point contract (generally, to any external actor) to simulate
+   multi-hop swaps that either specify an exact amount in (estimating how much would be received from the swap) or an
+   exact amount out (estimating how much is required to get the specified amount out).
+
+Note: Swap adapter contracts expect to be called by an entry point contract that provides basic validation and minimum
+amount out safety guarantees for the caller. There are no slippage guarantees provided by swap adapter contracts.
+
+WARNING: Do not send funds directly to the contract without calling one of its functions. Funds sent directly to the
+contract do not trigger any contract logic that performs validation / safety checks (as the Cosmos SDK handles direct
+fund transfers in the `Bank` module and not the `Wasm` module). There are no explicit recovery mechanisms for
+accidentally sent funds.
+
+## InstantiateMsg
+
+Instantiates a new Pryzm swap adapter contract.
+
+``` json
+{
+   "entry_point_contract_address": ""
+}
+```
+
+## ExecuteMsg
+
+### `swap`
+
+Swaps the coin sent using the operations provided.
+
+Note: The `pool` string field provided in the operations must have the following format:
+
+* For AMM swap, it must be "amm:" appended with a valid `u64` pool id, i.e: `amm:1`
+* For liquid staking, it must be "icstaking:" appended with a valid registered host chain id and the transfer channel,
+  i.e: `icstaking:uatom:channel-0`
+
+``` json
+{
+    "swap": {
+        "operations": [
+            {
+                "pool": "amm:1",
+                "denom_in": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4",
+                "denom_out": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"
+            },
+            {
+                "pool": "icstaking:uatom:channel-0",
+                "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                "denom_out": "c:uatom"
+            }
+        ]
+    }
+}
+```
+
+### `transfer_funds_back`
+
+Transfers all contract funds to the address provided, called by the swap adapter contract to send back the entry point
+contract the assets received from swapping.
+
+Note: This function can be called by anyone as the contract is assumed to have no balance before/after it's called by
+the entry point contract. Do not send funds directly to this contract without calling a function.
+
+``` json
+{
+    "transfer_funds_back": {
+        "swapper": "pryzm...",
+        "return_denom": "c:uatom"
+    }
+}
+```
+
+## QueryMsg
+
+### `simulate_swap_exact_asset_in`
+
+Returns the asset_out that would be received from swapping the `asset_in` specified in the call (swapped through
+the `swap_operatons` provided)
+
+Query:
+
+``` json
+{
+    "simulate_swap_exact_asset_in": {
+        "asset_in": {
+            "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+            "amount": "100"
+        },
+        "swap_operations": [
+            {
+                "pool": "amm:1",
+                "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                "denom_out": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4"
+            }
+        ]
+    }
+}
+```
+
+Response:
+
+``` json
+{
+    "denom": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4",
+    "amount": "100000"
+}
+```
+
+### `simulate_swap_exact_asset_out`
+
+Returns the asset_in required to receive the `asset_out` specified in the call (swapped through the `swap_operatons`
+provided)
+
+Query:
+
+``` json
+{
+    "simulate_swap_exact_asset_out": {
+        "asset_out": {
+            "denom": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4",
+            "amount": "100000"
+        },
+        "swap_operations": [
+            {
+                "pool": "amm:1",
+                "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                "denom_out": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4"
+            }
+        ]
+    }
+}
+```
+
+Response:
+
+``` json
+{
+    "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+    "amount": "100"
+}
+```
+
+### `simulate_swap_exact_asset_in_with_metadata`
+
+Similar to `simulate_swap_exact_asset_in`, but also includes swap spot price if requested.
+
+Query:
+
+``` json
+{
+    "simulate_swap_exact_asset_in_with_metadata": {
+        "asset_in": {
+            "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+            "amount": "100"
+        },
+        "swap_operations": [
+            {
+                "pool": "amm:1",
+                "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                "denom_out": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4"
+            }
+        ]
+    }
+}
+```
+
+Response:
+
+``` json
+{
+    "denom": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4",
+    "amount": "100000"
+}
+```
+
+### `simulate_swap_exact_asset_out_with_metadata`
+
+Similar to `simulate_swap_exact_asset_out`, but also includes swap spot price if requested.
+
+Query:
+
+``` json
+{
+    "simulate_swap_exact_asset_out_with_metadata": {
+        "asset_out": {
+            "denom": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4",
+            "amount": "100000"
+        },
+        "swap_operations": [
+            {
+                "pool": "amm:1",
+                "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                "denom_out": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4"
+            }
+        ]
+    }
+}
+```
+
+Response:
+
+``` json
+{
+    "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+    "amount": "100"
+}
+```
+
+### `simulate_smart_swap_exact_asset_in`
+
+Returns the asset_out that would be received from swapping an asset through multiple routes (the asset_in amount is 
+divided into multiple parts and each part is swapped using a different route)
+
+Query:
+
+``` json
+{
+    "simulate_swap_exact_asset_in": {
+        "asset_in": {
+            "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+            "amount": "100"
+        },
+        "routes": [
+            {
+                "offer_asset": {
+                    "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                    "amount": "70"
+                },
+                "swap_operations": [
+                    {
+                        "pool": "amm:1",
+                        "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                        "denom_out": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4"
+                    }
+                ]       
+            },
+            {
+                "offer_asset": {
+                    "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                    "amount": "30"
+                },
+                "swap_operations": [
+                    {
+                        "pool": "amm:1",
+                        "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                        "denom_out": "c:uatom"
+                    },
+                    {
+                        "pool": "amm:5",
+                        "denom_in": "c:uatom",
+                        "denom_out": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4"
+                    }
+                ]       
+            }
+        ]
+    }
+}
+```
+
+Response:
+
+``` json
+{
+    "denom": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4",
+    "amount": "100000"
+}
+```
+
+### `simulate_smart_swap_exact_asset_in_with_metadata`
+
+Similar to `simulate_smart_swap_exact_asset_in`, but also return the swap weighted spot price if requested.
+
+Query:
+
+``` json
+{
+    "simulate_smart_swap_exact_asset_in_with_metadata": {
+        "asset_in": {
+            "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+            "amount": "100"
+        },
+        "routes": [
+            {
+                "offer_asset": {
+                    "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                    "amount": "70"
+                },
+                "swap_operations": [
+                    {
+                        "pool": "amm:1",
+                        "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                        "denom_out": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4"
+                    }
+                ]       
+            },
+            {
+                "offer_asset": {
+                    "denom": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                    "amount": "30"
+                },
+                "swap_operations": [
+                    {
+                        "pool": "amm:3",
+                        "denom_in": "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
+                        "denom_out": "c:uatom"
+                    },
+                    {
+                        "pool": "amm:5",
+                        "denom_in": "c:uatom",
+                        "denom_out": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4"
+                    }
+                ]       
+            }
+        ]
+    }
+}
+```
+
+Response:
+
+``` json
+{
+    "denom": "ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4",
+    "amount": "100000",
+    "spot_price": "1000"
+}
+```
\ No newline at end of file
diff --git a/contracts/adapters/swap/pryzm/src/bin/pryzm-schema.rs b/contracts/adapters/swap/pryzm/src/bin/pryzm-schema.rs
new file mode 100644
index 00000000..4f4733f0
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/bin/pryzm-schema.rs
@@ -0,0 +1,10 @@
+use cosmwasm_schema::write_api;
+use skip::swap::{ExecuteMsg, InstantiateMsg, QueryMsg};
+
+fn main() {
+    write_api! {
+        instantiate: InstantiateMsg,
+        execute: ExecuteMsg,
+        query: QueryMsg
+    }
+}
diff --git a/contracts/adapters/swap/pryzm/src/consts.rs b/contracts/adapters/swap/pryzm/src/consts.rs
new file mode 100644
index 00000000..d27149db
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/consts.rs
@@ -0,0 +1,8 @@
+// The pool prefix that identifies the swap operation as an AMM pool swap
+pub const AMM_POOL_PREFIX: &str = "amm:";
+
+// The pool prefix that identifies the swap operation as a liquid staking operation on Pryzm's icstaking module
+pub const ICSTAKING_POOL_PREFIX: &str = "icstaking:";
+
+// The prefix of the icstaking module's cAsset denominations
+pub const C_ASSET_PREFIX: &str = "c:";
diff --git a/contracts/adapters/swap/pryzm/src/contract.rs b/contracts/adapters/swap/pryzm/src/contract.rs
new file mode 100644
index 00000000..27c04b60
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/contract.rs
@@ -0,0 +1,308 @@
+use std::collections::VecDeque;
+
+use cosmwasm_std::{
+    entry_point, to_json_binary, Addr, Binary, Coin, Deps, DepsMut, Env, MessageInfo, Reply,
+    Response, SubMsg, SubMsgResponse, SubMsgResult, WasmMsg,
+};
+use cw2::set_contract_version;
+use cw_utils::one_coin;
+use pryzm_std::types::pryzm::{amm::v1::MsgBatchSwapResponse, icstaking::v1::MsgStakeResponse};
+
+use skip::swap::{
+    execute_transfer_funds_back, get_ask_denom_for_routes, ExecuteMsg, InstantiateMsg, MigrateMsg,
+    QueryMsg, SwapOperation,
+};
+
+use crate::{
+    error::{ContractError, ContractResult},
+    execution::{extract_execution_steps, parse_coin, SwapExecutionStep},
+    reply_id,
+    simulate::{
+        simulate_smart_swap_exact_asset_in, simulate_smart_swap_exact_asset_in_with_metadata,
+        simulate_swap_exact_asset_in, simulate_swap_exact_asset_in_with_metadata,
+        simulate_swap_exact_asset_out, simulate_swap_exact_asset_out_with_metadata,
+    },
+    state::{ENTRY_POINT_CONTRACT_ADDRESS, IN_PROGRESS_SWAP_OPERATIONS, IN_PROGRESS_SWAP_SENDER},
+};
+
+///////////////
+/// MIGRATE ///
+///////////////
+
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> ContractResult<Response> {
+    unimplemented!()
+}
+
+///////////////////
+/// INSTANTIATE ///
+///////////////////
+
+// Contract name and version used for migration.
+const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
+const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
+
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn instantiate(
+    deps: DepsMut,
+    _env: Env,
+    _info: MessageInfo,
+    msg: InstantiateMsg,
+) -> ContractResult<Response> {
+    // Set contract version
+    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
+
+    // Validate entry point contract address
+    let checked_entry_point_contract_address =
+        deps.api.addr_validate(&msg.entry_point_contract_address)?;
+
+    // Store the entry point contract address
+    ENTRY_POINT_CONTRACT_ADDRESS.save(deps.storage, &checked_entry_point_contract_address)?;
+
+    Ok(Response::new()
+        .add_attribute("action", "instantiate")
+        .add_attribute(
+            "entry_point_contract_address",
+            checked_entry_point_contract_address.to_string(),
+        ))
+}
+
+///////////////
+/// EXECUTE ///
+///////////////
+
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn execute(
+    deps: DepsMut,
+    env: Env,
+    info: MessageInfo,
+    msg: ExecuteMsg,
+) -> ContractResult<Response> {
+    match msg {
+        ExecuteMsg::Swap { operations } => execute_swap(deps, env, info, operations),
+        ExecuteMsg::TransferFundsBack {
+            swapper,
+            return_denom,
+        } => Ok(execute_transfer_funds_back(
+            deps,
+            env,
+            info,
+            swapper,
+            return_denom,
+        )?),
+        _ => {
+            unimplemented!()
+        }
+    }
+}
+
+// Executes a swap with the given swap operations and then transfers the funds back to the caller
+fn execute_swap(
+    deps: DepsMut,
+    env: Env,
+    info: MessageInfo,
+    operations: Vec<SwapOperation>,
+) -> ContractResult<Response> {
+    // Get entry point contract address from storage
+    let entry_point_contract_address = ENTRY_POINT_CONTRACT_ADDRESS.load(deps.storage)?;
+
+    // Enforce the caller is the entry point contract
+    if info.sender != entry_point_contract_address {
+        return Err(ContractError::Unauthorized);
+    }
+
+    // Get coin in from the message info, error if there is not exactly one coin sent
+    let coin_in = one_coin(&info)?;
+
+    // Extract the execution steps from the provided swap operations
+    let execution_steps = extract_execution_steps(operations)?;
+
+    // Execute the swap
+    execute_steps(deps, env, info.sender, coin_in, execution_steps)
+}
+
+// Executes the swap of the provided coin using the provided execution steps for the swapper
+fn execute_steps(
+    deps: DepsMut,
+    env: Env,
+    swapper: Addr,
+    coin_in: Coin,
+    execution_steps: VecDeque<SwapExecutionStep>,
+) -> ContractResult<Response> {
+    // return error if execution_steps is empty
+    if execution_steps.is_empty() {
+        return Err(ContractError::SwapOperationsEmpty);
+    }
+
+    // convert the first execution step to the appropriate cosmos message
+    let first_step = execution_steps.front().unwrap();
+    let msg = first_step
+        .clone()
+        .to_cosmos_msg(env.contract.address.to_string(), coin_in)?;
+
+    // If there is only one execution step, create the transfer funds back message since the swap is done is a single step
+    if execution_steps.len() == 1 {
+        // Create the transfer funds back message
+        let return_denom = first_step.clone().get_return_denom()?;
+        let transfer_funds_back_msg = WasmMsg::Execute {
+            contract_addr: env.contract.address.to_string(),
+            msg: to_json_binary(&ExecuteMsg::TransferFundsBack {
+                swapper,
+                return_denom,
+            })?,
+            funds: vec![],
+        };
+
+        return Ok(Response::new()
+            .add_message(msg.clone())
+            .add_message(transfer_funds_back_msg)
+            .add_attribute("action", "dispatch_swap_and_transfer_back"));
+    }
+
+    // if there are more than one step, create sub message for the first step
+    let sub_msg = match first_step {
+        SwapExecutionStep::Swap { .. } => {
+            SubMsg::reply_on_success(msg.clone(), reply_id::BATCH_SWAP_REPLY_ID)
+        }
+        SwapExecutionStep::Stake { .. } => {
+            SubMsg::reply_on_success(msg.clone(), reply_id::STAKE_REPLY_ID)
+        }
+    };
+
+    // store the steps and the swapper to continue after the current step is executed in the reply entrypoint
+    IN_PROGRESS_SWAP_OPERATIONS.save(deps.storage, &execution_steps)?;
+    IN_PROGRESS_SWAP_SENDER.save(deps.storage, &swapper)?;
+
+    Ok(Response::new()
+        .add_submessage(sub_msg)
+        .add_attribute("action", "dispatch_swap_step"))
+}
+
+/////////////
+/// REPLY ///
+/////////////
+
+// Handles the reply from the swap step execution messages
+#[entry_point]
+pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> ContractResult<Response> {
+    // Get the sub message result from the reply
+    let SubMsgResult::Ok(SubMsgResponse { data: Some(b), .. }) = reply.result else {
+        return Err(ContractError::InvalidState {
+            msg: "could not get sub message response from reply result".to_string(),
+        });
+    };
+
+    // handle the reply and use the output of the swap as the coin_in for the next swap steps
+    let coin_in: Coin;
+    match reply.id {
+        reply_id::BATCH_SWAP_REPLY_ID => {
+            // Parse the batch swap response from the sub message
+            let resp: MsgBatchSwapResponse = b.try_into().map_err(ContractError::Std).unwrap();
+            if resp.amounts_out.len() != 1 {
+                return Err(ContractError::InvalidMsgResponse {
+                    msg: "unexpected amounts out length is batch swap response".to_string(),
+                });
+            }
+            coin_in = parse_coin(resp.amounts_out.first().unwrap())
+        }
+        reply_id::STAKE_REPLY_ID => {
+            // Parse the stake response from the sub message
+            let resp: MsgStakeResponse = b.try_into().map_err(ContractError::Std).unwrap();
+            if let Some(c_amount) = resp.c_amount {
+                coin_in = parse_coin(&c_amount)
+            } else {
+                return Err(ContractError::InvalidMsgResponse {
+                    msg: "expected valid c_amount in stake response, received None".to_string(),
+                });
+            }
+        }
+        _ => {
+            return Err(ContractError::InvalidState {
+                msg: format!("unexpected reply id {}", reply.id),
+            });
+        }
+    }
+
+    // load the swap execution steps from the store
+    let mut in_progress_exec_steps = IN_PROGRESS_SWAP_OPERATIONS.load(deps.storage)?;
+    IN_PROGRESS_SWAP_OPERATIONS.remove(deps.storage);
+
+    // load the swapper address from the store
+    let swapper = IN_PROGRESS_SWAP_SENDER.load(deps.storage)?;
+    IN_PROGRESS_SWAP_SENDER.remove(deps.storage);
+
+    // remove the first step (which is already executed)
+    in_progress_exec_steps.pop_front();
+
+    // continue the swap execution with the next steps
+    execute_steps(deps, env, swapper, coin_in, in_progress_exec_steps)
+}
+
+/////////////
+/// QUERY ///
+/////////////
+
+#[cfg_attr(not(feature = "library"), entry_point)]
+pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResult<Binary> {
+    match msg {
+        QueryMsg::SimulateSwapExactAssetIn {
+            asset_in,
+            swap_operations,
+        } => to_json_binary(&simulate_swap_exact_asset_in(
+            deps,
+            asset_in,
+            swap_operations,
+        )?),
+        QueryMsg::SimulateSwapExactAssetOut {
+            asset_out,
+            swap_operations,
+        } => to_json_binary(&simulate_swap_exact_asset_out(
+            deps,
+            asset_out,
+            swap_operations,
+        )?),
+        QueryMsg::SimulateSwapExactAssetInWithMetadata {
+            asset_in,
+            swap_operations,
+            include_spot_price,
+        } => to_json_binary(&simulate_swap_exact_asset_in_with_metadata(
+            deps,
+            asset_in,
+            swap_operations,
+            include_spot_price,
+        )?),
+        QueryMsg::SimulateSwapExactAssetOutWithMetadata {
+            asset_out,
+            swap_operations,
+            include_spot_price,
+        } => to_json_binary(&simulate_swap_exact_asset_out_with_metadata(
+            deps,
+            asset_out,
+            swap_operations,
+            include_spot_price,
+        )?),
+        QueryMsg::SimulateSmartSwapExactAssetIn { routes, .. } => {
+            let ask_denom = get_ask_denom_for_routes(&routes)?;
+
+            to_json_binary(&simulate_smart_swap_exact_asset_in(
+                deps, ask_denom, routes,
+            )?)
+        }
+        QueryMsg::SimulateSmartSwapExactAssetInWithMetadata {
+            routes,
+            asset_in,
+            include_spot_price,
+        } => {
+            let ask_denom = get_ask_denom_for_routes(&routes)?;
+
+            to_json_binary(&simulate_smart_swap_exact_asset_in_with_metadata(
+                deps,
+                asset_in,
+                ask_denom,
+                routes,
+                include_spot_price,
+            )?)
+        }
+    }
+    .map_err(From::from)
+}
diff --git a/contracts/adapters/swap/pryzm/src/error.rs b/contracts/adapters/swap/pryzm/src/error.rs
new file mode 100644
index 00000000..70d68992
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/error.rs
@@ -0,0 +1,50 @@
+use cosmwasm_std::StdError;
+use skip::error::SkipError;
+use thiserror::Error;
+
+pub type ContractResult<T> = Result<T, ContractError>;
+
+#[derive(Error, Debug, PartialEq)]
+pub enum ContractError {
+    #[error(transparent)]
+    Std(#[from] StdError),
+
+    #[error(transparent)]
+    Skip(#[from] SkipError),
+
+    #[error(transparent)]
+    Payment(#[from] cw_utils::PaymentError),
+
+    #[error(transparent)]
+    Overflow(#[from] cosmwasm_std::OverflowError),
+
+    #[error(transparent)]
+    CheckedFromRatioError(#[from] cosmwasm_std::CheckedFromRatioError),
+
+    #[error("Unauthorized")]
+    Unauthorized,
+
+    #[error("provided pool string is not a valid swap route: {msg:?}")]
+    InvalidPool { msg: String },
+
+    #[error("swap_operations cannot be empty")]
+    SwapOperationsEmpty,
+
+    #[error("coin_in denom must match the first swap operation's denom in")]
+    CoinInDenomMismatch,
+
+    #[error("coin_out denom must match the last swap operation's denom out")]
+    CoinOutDenomMismatch,
+
+    #[error("Asset Must Be Native, Pryzm Does Not Support CW20 Tokens")]
+    AssetNotNative,
+
+    #[error("Unexpected message response received: {msg:?}")]
+    InvalidMsgResponse { msg: String },
+
+    #[error("Unexpected query response received: {msg:?}")]
+    InvalidQueryResponse { msg: String },
+
+    #[error("InvalidState: {msg:?}")]
+    InvalidState { msg: String },
+}
diff --git a/contracts/adapters/swap/pryzm/src/execution.rs b/contracts/adapters/swap/pryzm/src/execution.rs
new file mode 100644
index 00000000..26f84aff
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/execution.rs
@@ -0,0 +1,224 @@
+use crate::consts;
+use crate::error::ContractError;
+use cosmwasm_schema::cw_serde;
+use cosmwasm_std::{coin, Coin, CosmosMsg, Uint128};
+use pryzm_std::types::cosmos::base::v1beta1::Coin as CosmosCoin;
+use pryzm_std::types::pryzm::{
+    amm::v1::{MsgBatchSwap, SwapStep, SwapType},
+    icstaking::v1::MsgStake,
+};
+use skip::swap::SwapOperation;
+use std::collections::VecDeque;
+
+// SwapExecutionStep is an enum that represents the different types of swap operations that can be executed
+#[cw_serde]
+pub enum SwapExecutionStep {
+    // Swap represents a batch swap operation on the AMM module
+    Swap {
+        swap_steps: Vec<SwapStep>, // the batch swap steps
+    },
+    // Stake represents a liquid staking operation on Pryzm's icstaking module
+    Stake {
+        host_chain_id: String,    // the host chain id for staking
+        transfer_channel: String, // the transfer channel of the tokens
+    },
+}
+
+impl SwapExecutionStep {
+    // Converts the step to the appropriate Pryzm message
+    pub fn to_cosmos_msg(
+        &self,
+        address: String,
+        coin_in: Coin,
+    ) -> Result<CosmosMsg, ContractError> {
+        match self {
+            SwapExecutionStep::Swap { swap_steps } => {
+                create_amm_swap_msg(swap_steps, address, coin_in)
+            }
+            SwapExecutionStep::Stake {
+                host_chain_id,
+                transfer_channel,
+            } => create_icstaking_stake_msg(
+                host_chain_id.clone(),
+                transfer_channel.clone(),
+                address,
+                coin_in,
+            ),
+        }
+    }
+
+    // Returns the output denom of the step
+    pub fn get_return_denom(&self) -> Result<String, ContractError> {
+        return match self {
+            SwapExecutionStep::Swap { swap_steps } => {
+                // take the last step token_out as the return denom
+                let token_out = match swap_steps.last() {
+                    Some(last_op) => last_op.token_out.clone(),
+                    None => return Err(ContractError::SwapOperationsEmpty),
+                };
+                Ok(token_out)
+            }
+            SwapExecutionStep::Stake { host_chain_id, .. } => {
+                // calculate the cAsset denom by prefixing "c:" to the host chain id
+                Ok(format!("{}{}", consts::C_ASSET_PREFIX, host_chain_id))
+            }
+        };
+    }
+}
+
+// Iterates over the swap operations and aggregates the operations into execution steps
+pub fn extract_execution_steps(
+    operations: Vec<SwapOperation>,
+) -> Result<VecDeque<SwapExecutionStep>, ContractError> {
+    // Return error if swap operations is empty
+    if operations.is_empty() {
+        return Err(ContractError::SwapOperationsEmpty);
+    }
+
+    // Create a vector to push the steps into
+    let mut execution_steps: VecDeque<SwapExecutionStep> = VecDeque::new();
+
+    // Create a vector to keep consecutive AMM operations in order to batch them into a single step
+    let mut amm_swap_steps: Vec<SwapStep> = Vec::new();
+
+    // Iterate over the swap operations
+    let swap_operations_iter = operations.iter();
+    for swap_op in swap_operations_iter {
+        if swap_op.pool.starts_with(consts::ICSTAKING_POOL_PREFIX) {
+            // Validate that the icstaking operation is converting an asset to a cAsset,
+            // not a cAsset to an asset which is not supported
+            if swap_op.denom_in.starts_with(consts::C_ASSET_PREFIX)
+                || !swap_op.denom_out.starts_with(consts::C_ASSET_PREFIX)
+            {
+                return Err(ContractError::InvalidPool {
+                    msg: format!(
+                        "icstaking swap operation can only convert an asset to cAsset: cannot convert {} to {}",
+                        swap_op.denom_in, swap_op.denom_out
+                    )
+                });
+            }
+
+            // If there are AMM swap steps from before, aggregate and push them into the execution steps
+            if !amm_swap_steps.is_empty() {
+                execution_steps.push_back(SwapExecutionStep::Swap {
+                    swap_steps: amm_swap_steps,
+                });
+                amm_swap_steps = Vec::new();
+            }
+
+            // split and validate the pool string
+            let split: Vec<&str> = swap_op.pool.split(':').collect();
+            if split.len() != 3 {
+                return Err(ContractError::InvalidPool {
+                    msg: format!(
+                        "icstaking pool string must be in the format \"icstaking:<host_chain_id>:<transfer_channel>\": {}",
+                        swap_op.pool
+                    )
+                });
+            }
+
+            // Push the staking operation into the execution steps
+            execution_steps.push_back(SwapExecutionStep::Stake {
+                host_chain_id: split.get(1).unwrap().to_string(),
+                transfer_channel: split.get(2).unwrap().to_string(),
+            });
+        } else if swap_op.pool.starts_with(consts::AMM_POOL_PREFIX) {
+            // replace the pool prefix and parse the pool id
+            let pool_id = swap_op.pool.replace(consts::AMM_POOL_PREFIX, "");
+            if let Ok(pool) = pool_id.parse() {
+                // Add the operation to the amm swap steps
+                amm_swap_steps.push(SwapStep {
+                    pool_id: pool,
+                    token_in: swap_op.denom_in.clone(),
+                    token_out: swap_op.denom_out.clone(),
+                    amount: None,
+                });
+            } else {
+                return Err(ContractError::InvalidPool {
+                    msg: format!("invalid amm pool id {}", pool_id),
+                });
+            }
+        } else {
+            return Err(ContractError::InvalidPool {
+                msg: format!(
+                    "pool must be started with \"amm\" or \"icstaking\": {}",
+                    swap_op.pool
+                ),
+            });
+        }
+    }
+
+    // If there is any AMM swap steps left, push them into the execution steps
+    if !amm_swap_steps.is_empty() {
+        execution_steps.push_back(SwapExecutionStep::Swap {
+            swap_steps: amm_swap_steps,
+        });
+    }
+
+    Ok(execution_steps)
+}
+
+// create Pryzm MsgBatchSwap using the provided swap steps
+pub fn create_amm_swap_msg(
+    swap_steps: &[SwapStep],
+    address: String,
+    coin_in: Coin,
+) -> Result<CosmosMsg, ContractError> {
+    // take the last step token_out as the return denom
+    let token_out = match swap_steps.last() {
+        Some(last_op) => last_op.token_out.clone(),
+        None => return Err(ContractError::SwapOperationsEmpty),
+    };
+
+    // set the amount_in on the first swap step
+    let mut steps = swap_steps.to_vec();
+    if let Some(first_step) = steps.get_mut(0) {
+        first_step.amount = coin_in.amount.to_string().into();
+    }
+
+    // construct the message
+    let swap_msg: CosmosMsg = MsgBatchSwap {
+        creator: address,
+        swap_type: SwapType::GivenIn.into(),
+        max_amounts_in: vec![format_coin(coin_in)],
+        min_amounts_out: vec![CosmosCoin {
+            amount: "1".to_string(),
+            denom: token_out.to_string(),
+        }],
+        steps,
+    }
+    .into();
+
+    Ok(swap_msg)
+}
+
+// create Pryzm MsgStake using the provided host chain and transfer channel
+fn create_icstaking_stake_msg(
+    host_chain_id: String,
+    transfer_channel: String,
+    address: String,
+    coin_in: Coin,
+) -> Result<CosmosMsg, ContractError> {
+    // Construct the message
+    let msg: CosmosMsg = MsgStake {
+        creator: address,
+        host_chain: host_chain_id,
+        transfer_channel,
+        amount: coin_in.amount.into(),
+    }
+    .into();
+
+    Ok(msg)
+}
+
+pub fn parse_coin(c: &CosmosCoin) -> Coin {
+    let p: Uint128 = c.amount.parse().unwrap();
+    coin(p.u128(), &c.denom)
+}
+
+pub fn format_coin(c: Coin) -> CosmosCoin {
+    CosmosCoin {
+        amount: c.amount.to_string(),
+        denom: c.denom,
+    }
+}
diff --git a/contracts/adapters/swap/pryzm/src/lib.rs b/contracts/adapters/swap/pryzm/src/lib.rs
new file mode 100644
index 00000000..4c71d8e7
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/lib.rs
@@ -0,0 +1,7 @@
+pub mod consts;
+pub mod contract;
+pub mod error;
+pub mod execution;
+pub mod reply_id;
+pub mod simulate;
+pub mod state;
diff --git a/contracts/adapters/swap/pryzm/src/reply_id.rs b/contracts/adapters/swap/pryzm/src/reply_id.rs
new file mode 100644
index 00000000..f3877f15
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/reply_id.rs
@@ -0,0 +1,5 @@
+// The reply id for handling amm module's batch swap message
+pub const BATCH_SWAP_REPLY_ID: u64 = 1;
+
+// The reply id for handling icstaking module's staking message
+pub const STAKE_REPLY_ID: u64 = 2;
diff --git a/contracts/adapters/swap/pryzm/src/simulate.rs b/contracts/adapters/swap/pryzm/src/simulate.rs
new file mode 100644
index 00000000..c896a342
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/simulate.rs
@@ -0,0 +1,363 @@
+use std::str::FromStr;
+
+use cosmwasm_std::{Decimal, Deps, Uint128};
+use pryzm_std::types::pryzm::amm::v1::{
+    AmmQuerier, QuerySimulateBatchSwapResponse, QuerySpotPriceResponse, SwapType,
+};
+use pryzm_std::types::pryzm::icstaking::v1::{IcstakingQuerier, QuerySimulateStakeResponse};
+
+use skip::asset::Asset;
+use skip::swap::{
+    Route, SimulateSmartSwapExactAssetInResponse, SimulateSwapExactAssetInResponse,
+    SimulateSwapExactAssetOutResponse, SwapOperation,
+};
+
+use crate::error::{ContractError, ContractResult};
+use crate::execution::{extract_execution_steps, parse_coin, SwapExecutionStep};
+
+// Simulates a swap given the exact amount in
+pub fn simulate_swap_exact_asset_in(
+    deps: Deps,
+    asset_in: Asset,
+    swap_operations: Vec<SwapOperation>,
+) -> ContractResult<Asset> {
+    // Error if swap operations is empty
+    let Some(first_op) = swap_operations.first() else {
+        return Err(ContractError::SwapOperationsEmpty);
+    };
+
+    // Get coin in from asset in, error if asset in is not a
+    // native coin because Pryzm does not support CW20 tokens.
+    let coin_in = match asset_in {
+        Asset::Native(coin) => coin,
+        _ => return Err(ContractError::AssetNotNative),
+    };
+
+    // Ensure coin_in's denom is the same as the first swap operation's denom in
+    if coin_in.denom != first_op.denom_in {
+        return Err(ContractError::CoinInDenomMismatch);
+    }
+
+    // instantiate module queriers
+    let amm_querier = &AmmQuerier::new(&deps.querier);
+    let icstaking_querier = &IcstakingQuerier::new(&deps.querier);
+
+    // Extract the execution steps from the provided swap operations
+    let execution_steps = extract_execution_steps(swap_operations)?;
+
+    // Iterate over steps and simulate the step given the output of the last step
+    // The first step uses the coin_in as input
+    let mut step_amount = coin_in;
+    for step in execution_steps {
+        match step {
+            SwapExecutionStep::Swap { swap_steps } => {
+                // Set the amount on the first step of the batch swap
+                let mut vec = swap_steps.clone();
+                if let Some(first_step) = vec.first_mut() {
+                    first_step.amount = step_amount.amount.to_string().into();
+                }
+                // execute the simulation query on the amm module
+                let res: QuerySimulateBatchSwapResponse =
+                    amm_querier.simulate_batch_swap(SwapType::GivenIn.into(), vec)?;
+                if res.amounts_out.len() != 1 {
+                    return Err(ContractError::InvalidQueryResponse {
+                        msg: "unexpected amounts out length in batch swap simulation".to_string(),
+                    });
+                }
+                // set the output of the simulation as the input for the next step
+                step_amount = parse_coin(res.amounts_out.first().unwrap());
+            }
+            SwapExecutionStep::Stake {
+                host_chain_id,
+                transfer_channel,
+            } => {
+                // execute the simulation query on the icstaking module
+                let res: QuerySimulateStakeResponse = icstaking_querier.simulate_stake(
+                    host_chain_id,
+                    transfer_channel,
+                    step_amount.amount.to_string().into(),
+                    None,
+                )?;
+                if let Some(amount_out) = res.amount_out {
+                    // set the output of the simulation as the input for the next step
+                    step_amount = parse_coin(&amount_out);
+                } else {
+                    return Err(ContractError::InvalidQueryResponse {
+                        msg: "unexpected amount_out in liquid staking simulation".to_string(),
+                    });
+                }
+            }
+        }
+    }
+
+    // return the last step output as the result of the simulation
+    Ok(Asset::from(step_amount))
+}
+
+// Simulates a swap given the exact amount in, include spot price if requested
+pub fn simulate_swap_exact_asset_in_with_metadata(
+    deps: Deps,
+    asset_in: Asset,
+    swap_operations: Vec<SwapOperation>,
+    include_spot_price: bool,
+) -> ContractResult<SimulateSwapExactAssetInResponse> {
+    // simulate the swap
+    let mut response = SimulateSwapExactAssetInResponse {
+        asset_out: simulate_swap_exact_asset_in(deps, asset_in, swap_operations.clone())?,
+        spot_price: None,
+    };
+
+    // calculate and include spot price if requested
+    if include_spot_price {
+        response.spot_price = Some(calculate_spot_price(deps, swap_operations)?)
+    }
+
+    Ok(response)
+}
+
+// Simulates a swap given exact amount out
+pub fn simulate_swap_exact_asset_out(
+    deps: Deps,
+    asset_out: Asset,
+    swap_operations: Vec<SwapOperation>,
+) -> ContractResult<Asset> {
+    // Error if swap operations is empty
+    let Some(last_op) = swap_operations.last() else {
+        return Err(ContractError::SwapOperationsEmpty);
+    };
+
+    // Get coin out from asset out, error if asset out is not a
+    // native coin because Pryzm does not support CW20 tokens.
+    let coin_out = match asset_out {
+        Asset::Native(coin) => coin,
+        _ => return Err(ContractError::AssetNotNative),
+    };
+
+    // Ensure coin_out's denom is the same as the last swap operation's denom out
+    if coin_out.denom != last_op.denom_out {
+        return Err(ContractError::CoinOutDenomMismatch);
+    }
+
+    // instantiate module queriers
+    let amm_querier = &AmmQuerier::new(&deps.querier);
+    let icstaking_querier = &IcstakingQuerier::new(&deps.querier);
+
+    // Iterate over steps starting from the last step and simulate the step given the result of the
+    // last step. The first step uses the coin_out as input
+    let mut step_amount = coin_out;
+    let execution_steps = extract_execution_steps(swap_operations)?;
+    let reverse_iter = execution_steps.into_iter().rev();
+    for step in reverse_iter {
+        match step {
+            SwapExecutionStep::Swap { swap_steps } => {
+                // make the swap steps reversed and set the amount on the first step
+                let mut vec = swap_steps.clone();
+                vec.reverse();
+                if let Some(first_step) = vec.first_mut() {
+                    first_step.amount = step_amount.amount.to_string().into();
+                }
+                // execute the simulation query on the amm module
+                let res: QuerySimulateBatchSwapResponse =
+                    amm_querier.simulate_batch_swap(SwapType::GivenOut.into(), vec)?;
+                // set the output of the simulation as the input for the next step
+                if res.amounts_in.len() != 1 {
+                    return Err(ContractError::InvalidQueryResponse {
+                        msg: "unexpected amounts in length in batch swap simulation".to_string(),
+                    });
+                }
+                step_amount = parse_coin(res.amounts_in.first().unwrap());
+            }
+            SwapExecutionStep::Stake {
+                host_chain_id,
+                transfer_channel,
+            } => {
+                // execute the simulation query on the icstaking module
+                let res: QuerySimulateStakeResponse = icstaking_querier.simulate_stake(
+                    host_chain_id,
+                    transfer_channel,
+                    None,
+                    step_amount.amount.to_string().into(),
+                )?;
+                if let Some(amount_in) = res.amount_in {
+                    // set the output of the simulation as the input for the next step
+                    step_amount = parse_coin(&amount_in);
+                } else {
+                    return Err(ContractError::InvalidQueryResponse {
+                        msg: "unexpected amount_in in liquid staking simulation".to_string(),
+                    });
+                }
+            }
+        }
+    }
+
+    // return the last step output as the result of the simulation
+    Ok(Asset::from(step_amount))
+}
+
+// Simulates a swap given exact amount out, include spot price if requested
+pub fn simulate_swap_exact_asset_out_with_metadata(
+    deps: Deps,
+    asset_out: Asset,
+    swap_operations: Vec<SwapOperation>,
+    include_spot_price: bool,
+) -> ContractResult<SimulateSwapExactAssetOutResponse> {
+    // simulate the swap
+    let mut response = SimulateSwapExactAssetOutResponse {
+        asset_in: simulate_swap_exact_asset_out(deps, asset_out, swap_operations.clone())?,
+        spot_price: None,
+    };
+
+    // calculate and include spot price if requested
+    if include_spot_price {
+        response.spot_price = Some(calculate_spot_price(deps, swap_operations)?)
+    }
+
+    Ok(response)
+}
+
+// Simulates a smart swap given the exact amount in
+pub fn simulate_smart_swap_exact_asset_in(
+    deps: Deps,
+    ask_denom: String,
+    routes: Vec<Route>,
+) -> ContractResult<Asset> {
+    // initialize the total output with zero value
+    let mut asset_out = Asset::new(deps.api, &ask_denom, Uint128::zero());
+
+    // Iterate over routes and simulate the swap for each route
+    for route in &routes {
+        let route_asset_out = simulate_swap_exact_asset_in(
+            deps,
+            route.offer_asset.clone(),
+            route.operations.clone(),
+        )?;
+
+        // add the output of swap using this route to the total output
+        asset_out.add(route_asset_out.amount())?;
+    }
+
+    Ok(asset_out)
+}
+
+// Simulates a smart swap given the exact amount in, and includes spot price if requested
+pub fn simulate_smart_swap_exact_asset_in_with_metadata(
+    deps: Deps,
+    asset_in: Asset,
+    ask_denom: String,
+    routes: Vec<Route>,
+    include_spot_price: bool,
+) -> ContractResult<SimulateSmartSwapExactAssetInResponse> {
+    // simulate the swap
+    let asset_out = simulate_smart_swap_exact_asset_in(deps, ask_denom, routes.clone())?;
+
+    // instantiate the response
+    let mut response = SimulateSmartSwapExactAssetInResponse {
+        asset_out,
+        spot_price: None,
+    };
+
+    // calculate and include weighted spot price if requested
+    if include_spot_price {
+        response.spot_price = Some(calculate_weighted_spot_price(deps, asset_in, routes)?)
+    }
+
+    Ok(response)
+}
+
+// Calculate the spot price for the swap
+fn calculate_spot_price(
+    deps: Deps,
+    swap_operations: Vec<SwapOperation>,
+) -> ContractResult<Decimal> {
+    // Extract the execution steps from the provided swap operations
+    let execution_steps = extract_execution_steps(swap_operations)?;
+
+    // instantiate module queriers
+    let amm_querier = &AmmQuerier::new(&deps.querier);
+    let icstaking_querier = &IcstakingQuerier::new(&deps.querier);
+
+    // iterate over execution steps, calculate spot price for each step and multiply all spot prices
+    let spot_price = execution_steps.into_iter().try_fold(
+        Decimal::one(),
+        |curr_spot_price, step| -> ContractResult<Decimal> {
+            let step_spot_price = match step {
+                SwapExecutionStep::Swap { swap_steps } => swap_steps.into_iter().try_fold(
+                    Decimal::one(),
+                    |curr_spot_price, step| -> ContractResult<Decimal> {
+                        // spot price for a Swap step can be queried from amm module
+                        let spot_price_res: QuerySpotPriceResponse = amm_querier.spot_price(
+                            step.pool_id,
+                            step.token_in,
+                            step.token_out,
+                            false,
+                        )?;
+                        // parse the result and multiply the spot price with the current value
+                        if let Ok(spot_price) = Decimal::from_str(&spot_price_res.spot_price) {
+                            Ok(curr_spot_price.checked_mul(spot_price)?)
+                        } else {
+                            Err(ContractError::InvalidQueryResponse {
+                                msg: "invalid spot price in amm spot price query".to_string(),
+                            })
+                        }
+                    },
+                ),
+                SwapExecutionStep::Stake {
+                    host_chain_id,
+                    transfer_channel,
+                } => {
+                    // calculate spot price for liquid staking, by simulating stake for an amount
+                    let amount = Decimal::from_str("1000000000000000000")?; // 1e18
+                    let res: QuerySimulateStakeResponse = icstaking_querier.simulate_stake(
+                        host_chain_id,
+                        transfer_channel,
+                        amount.to_string().into(),
+                        None,
+                    )?;
+                    // calculate the spot price by dividing the output of staking by the input amount
+                    if let Some(amount_out) = res.amount_out {
+                        if let Ok(output) = Decimal::from_str(&amount_out.amount) {
+                            Ok(output.checked_div(amount)?)
+                        } else {
+                            Err(ContractError::InvalidQueryResponse {
+                                msg: "invalid amount for amount_out coin in staking simulation response".to_string(),
+                            })
+                        }
+                    } else {
+                        return Err(ContractError::InvalidQueryResponse {
+                            msg: "unexpected amount_out in liquid staking simulation".to_string(),
+                        });
+                    }
+                }
+            };
+
+            Ok(curr_spot_price.checked_mul(step_spot_price?)?)
+        },
+    )?;
+
+    Ok(spot_price)
+}
+
+// Calculate weighted spot price for a set of routes
+fn calculate_weighted_spot_price(
+    deps: Deps,
+    asset_in: Asset,
+    routes: Vec<Route>,
+) -> ContractResult<Decimal> {
+    // iterate over the routes, calculate each route spot price, and multiply them based on the
+    // weight of that route
+    let spot_price = routes.into_iter().try_fold(
+        Decimal::zero(),
+        |curr_spot_price, route| -> ContractResult<Decimal> {
+            // calculate route's spot price
+            let route_spot_price = calculate_spot_price(deps, route.operations)?;
+
+            // calculate the weight of the route, which is equal to the ratio of amount swapped on
+            // the route to the total amount being swapped
+            let weight = Decimal::from_ratio(route.offer_asset.amount(), asset_in.amount());
+
+            Ok(curr_spot_price + (route_spot_price * weight))
+        },
+    )?;
+
+    Ok(spot_price)
+}
diff --git a/contracts/adapters/swap/pryzm/src/state.rs b/contracts/adapters/swap/pryzm/src/state.rs
new file mode 100644
index 00000000..cffa0ee8
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/src/state.rs
@@ -0,0 +1,13 @@
+use crate::execution::SwapExecutionStep;
+use cosmwasm_std::Addr;
+use cw_storage_plus::Item;
+use std::collections::VecDeque;
+
+pub const ENTRY_POINT_CONTRACT_ADDRESS: Item<Addr> = Item::new("entry_point_contract_address");
+
+// stores the list of operations of the in progress swap, used by the reply entrypoint
+pub const IN_PROGRESS_SWAP_OPERATIONS: Item<VecDeque<SwapExecutionStep>> =
+    Item::new("in_progress_swap_operations");
+
+// stores the address of the swapper for the in progress swap, used by the reply entrypoint
+pub const IN_PROGRESS_SWAP_SENDER: Item<Addr> = Item::new("in_progress_swap_sender");
diff --git a/contracts/adapters/swap/pryzm/tests/mock/mod.rs b/contracts/adapters/swap/pryzm/tests/mock/mod.rs
new file mode 100644
index 00000000..d11d0c48
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/tests/mock/mod.rs
@@ -0,0 +1,45 @@
+use cosmwasm_std::{
+    to_json_binary, to_json_vec, ContractResult, Querier as CWStdQuerier, QuerierResult,
+    SystemResult,
+};
+use cosmwasm_std::{Empty, QueryRequest};
+use mockall::predicate::*;
+use mockall::*;
+use serde::Serialize;
+
+mock! {
+    StdQuerier {}
+    impl CWStdQuerier for StdQuerier {
+        fn raw_query(&self, bin_request: &[u8]) -> QuerierResult;
+    }
+}
+
+pub struct MockQuerier {
+    inner_mock: MockStdQuerier,
+}
+
+impl CWStdQuerier for MockQuerier {
+    fn raw_query(&self, bin_request: &[u8]) -> QuerierResult {
+        self.inner_mock.raw_query(bin_request)
+    }
+}
+
+impl MockQuerier {
+    pub fn new() -> Self {
+        Self {
+            inner_mock: MockStdQuerier::new(),
+        }
+    }
+
+    pub fn mock_query<T>(&mut self, request: QueryRequest<Empty>, response: &T)
+    where
+        T: Serialize + ?Sized,
+    {
+        self.inner_mock
+            .expect_raw_query()
+            .with(eq(to_json_vec(&request).unwrap()))
+            .return_const(SystemResult::Ok(ContractResult::Ok(
+                to_json_binary(response).unwrap(),
+            )));
+    }
+}
diff --git a/contracts/adapters/swap/pryzm/tests/test_execute_reply.rs b/contracts/adapters/swap/pryzm/tests/test_execute_reply.rs
new file mode 100644
index 00000000..e9299cc3
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/tests/test_execute_reply.rs
@@ -0,0 +1,274 @@
+use std::collections::VecDeque;
+
+#[allow(unused_imports)]
+use cosmwasm_std::{
+    testing::{mock_dependencies, mock_env, mock_info},
+    to_json_binary, Addr, Binary, Coin,
+    ReplyOn::Never,
+    ReplyOn::Success,
+    SubMsg, WasmMsg,
+};
+use cosmwasm_std::{Reply, SubMsgResponse, SubMsgResult};
+#[allow(unused_imports)]
+use pryzm_std::types::{
+    cosmos::base::v1beta1::Coin as CosmosCoin,
+    pryzm::amm::v1::{MsgBatchSwap, MsgBatchSwapResponse, SwapStep, SwapType},
+    pryzm::icstaking::v1::{MsgStake, MsgStakeResponse},
+};
+use test_case::test_case;
+
+#[allow(unused_imports)]
+use skip::swap::{ExecuteMsg, SwapOperation};
+use skip_go_swap_adapter_pryzm::execution::SwapExecutionStep;
+use skip_go_swap_adapter_pryzm::state::{IN_PROGRESS_SWAP_OPERATIONS, IN_PROGRESS_SWAP_SENDER};
+#[allow(unused_imports)]
+use skip_go_swap_adapter_pryzm::{
+    contract, error::ContractResult, reply_id::BATCH_SWAP_REPLY_ID, reply_id::STAKE_REPLY_ID,
+    state::ENTRY_POINT_CONTRACT_ADDRESS,
+};
+
+/*
+Test Cases:
+
+Expect Success
+    - One-Step Left
+    - Many Steps Left
+
+ */
+
+// Define test parameters
+struct Params {
+    swapper: String,
+    reply_id: u64,
+    swap_steps: Vec<SwapExecutionStep>,
+    response: Binary,
+    expected_messages: Vec<SubMsg>,
+    expected_error_string: String,
+    expected_stored_swapper: String,
+    expected_stored_steps: Vec<SwapExecutionStep>,
+}
+
+// Test execute_swap
+#[test_case(
+    Params {
+        swapper: "entry_point".to_string(),
+        reply_id: STAKE_REPLY_ID,
+        swap_steps: vec![
+            SwapExecutionStep::Stake {
+                host_chain_id: "uatom".to_string(),
+                transfer_channel: "channel-0".to_string()
+            },
+            SwapExecutionStep::Swap {
+                swap_steps: vec![
+                    SwapStep {
+                        pool_id: 4,
+                        token_in: "c:uatom".to_string(),
+                        token_out: "p:uatom:30Sep2024".to_string(),
+                        amount: None,
+                    },
+                ],
+            },
+        ],
+        response: MsgStakeResponse {
+            c_amount: Some(CosmosCoin {amount: "1800".to_string(), denom: "c:uatom".to_string()}),
+            fee: None,
+        }.into(),
+        expected_messages: vec![
+            SubMsg {
+                id: 0,
+                msg: MsgBatchSwap {
+                    creator: "swap_contract_address".to_string(),
+                    swap_type: SwapType::GivenIn.into(),
+                    max_amounts_in: vec![CosmosCoin{amount: "1800".to_string(), denom: "c:uatom".to_string()}],
+                    min_amounts_out: vec![CosmosCoin{amount: "1".to_string(), denom: "p:uatom:30Sep2024".to_string()}],
+                    steps: vec![
+                        SwapStep {
+                            pool_id: 4,
+                            token_in: "c:uatom".to_string(),
+                            token_out: "p:uatom:30Sep2024".to_string(),
+                            amount: Some("1800".to_string()),
+                        }
+                    ],
+                }.into(),
+                gas_limit: None,
+                reply_on: Never,
+            },
+            SubMsg {
+                id: 0,
+                msg: WasmMsg::Execute {
+                    contract_addr: "swap_contract_address".to_string(),
+                    msg: to_json_binary(&ExecuteMsg::TransferFundsBack {
+                        return_denom: "p:uatom:30Sep2024".to_string(),
+                        swapper: Addr::unchecked("entry_point"),
+                    })?,
+                    funds: vec![],
+                }
+                .into(),
+                gas_limit: None,
+                reply_on: Never,
+            },
+        ],
+        expected_error_string: "".to_string(),
+        expected_stored_swapper: "".to_string(),
+        expected_stored_steps: vec![],
+    };
+"One Step Left")]
+#[test_case(
+    Params {
+        swapper: "entry_point".to_string(),
+        reply_id: BATCH_SWAP_REPLY_ID,
+        swap_steps: vec![
+            SwapExecutionStep::Swap {
+                swap_steps: vec![
+                    SwapStep {
+                        pool_id: 1,
+                        token_in: "ibc/uosmo".to_string(),
+                        token_out: "ibc/uusdc".to_string(),
+                        amount: Some("2000".to_string()),
+                    },
+                    SwapStep {
+                        pool_id: 2,
+                        token_in: "ibc/uusdc".to_string(),
+                        token_out: "ibc/uatom".to_string(),
+                        amount: None,
+                    },
+                ],
+            },
+            SwapExecutionStep::Stake {
+                host_chain_id: "uatom".to_string(),
+                transfer_channel: "channel-0".to_string()
+            },
+            SwapExecutionStep::Swap {
+                swap_steps: vec![
+                    SwapStep {
+                        pool_id: 1,
+                        token_in: "c:uatom".to_string(),
+                        token_out: "lp:0:uatomlpt".to_string(),
+                        amount: None,
+                    }
+                ],
+            },
+        ],
+        response: MsgBatchSwapResponse {
+            amounts_out: vec![CosmosCoin {amount: "100".to_string(), denom: "ibc/uatom".to_string()}],
+            amounts_in: vec![],
+            join_exit_protocol_fee: vec![],
+            swap_fee: vec![],
+            swap_protocol_fee: vec![],
+        }.into(),
+        expected_messages: vec![
+            SubMsg {
+                id: STAKE_REPLY_ID,
+                msg: MsgStake {
+                    creator: "swap_contract_address".to_string(),
+                    host_chain: "uatom".to_string(),
+                    transfer_channel: "channel-0".to_string(),
+                    amount: "100".to_string(),
+                }
+                .into(),
+                gas_limit: None,
+                reply_on: Success,
+            },
+        ],
+        expected_error_string: "".to_string(),
+        expected_stored_swapper: "entry_point".to_string(),
+        expected_stored_steps: vec![
+            SwapExecutionStep::Stake {
+                host_chain_id: "uatom".to_string(),
+                transfer_channel: "channel-0".to_string()
+            },
+            SwapExecutionStep::Swap {
+                swap_steps: vec![
+                    SwapStep {
+                        pool_id: 1,
+                        token_in: "c:uatom".to_string(),
+                        token_out: "lp:0:uatomlpt".to_string(),
+                        amount: None,
+                    }
+                ],
+            },
+        ],
+    };
+"Multi Step Left")]
+fn test_execute_reply(params: Params) -> ContractResult<()> {
+    // Create mock dependencies
+    let mut deps = mock_dependencies();
+
+    // Create mock env
+    let mut env = mock_env();
+    env.contract.address = Addr::unchecked("swap_contract_address");
+
+    // Fill the storage
+    ENTRY_POINT_CONTRACT_ADDRESS.save(deps.as_mut().storage, &Addr::unchecked("entry_point"))?;
+    IN_PROGRESS_SWAP_OPERATIONS.save(deps.as_mut().storage, &VecDeque::from(params.swap_steps))?;
+    IN_PROGRESS_SWAP_SENDER.save(
+        deps.as_mut().storage,
+        &Addr::unchecked(params.swapper.as_str()),
+    )?;
+
+    // Call execute_swap with the given test parameters
+    let res = contract::reply(
+        deps.as_mut(),
+        env,
+        Reply {
+            id: params.reply_id,
+            result: SubMsgResult::Ok(SubMsgResponse {
+                data: Some(params.response),
+                events: vec![],
+            }),
+        },
+    );
+
+    // Assert the behavior is correct
+    match res {
+        Ok(res) => {
+            // Assert the test did not expect an error
+            assert!(
+                params.expected_error_string.is_empty(),
+                "expected test to error with {:?}, but it succeeded",
+                params.expected_error_string
+            );
+
+            // Assert the messages are correct
+            assert_eq!(res.messages, params.expected_messages);
+
+            if !params.expected_stored_steps.is_empty() {
+                // Assert the stored steps are correct
+                let stored_steps = IN_PROGRESS_SWAP_OPERATIONS.load(deps.as_ref().storage)?;
+                assert_eq!(stored_steps, VecDeque::from(params.expected_stored_steps));
+            } else {
+                // Assert no steps are stored
+                assert!(IN_PROGRESS_SWAP_OPERATIONS
+                    .load(deps.as_ref().storage)
+                    .is_err());
+            }
+
+            if !params.expected_stored_swapper.is_empty() {
+                // Assert the stored swapper is correct
+                let stored_swapper = IN_PROGRESS_SWAP_SENDER.load(deps.as_ref().storage)?;
+                assert_eq!(
+                    stored_swapper,
+                    Addr::unchecked(params.expected_stored_swapper.as_str())
+                );
+            } else {
+                // Assert no address is stored
+                assert!(IN_PROGRESS_SWAP_SENDER.load(deps.as_ref().storage).is_err());
+            }
+        }
+        Err(err) => {
+            // Assert the test expected an error
+            assert!(
+                !params.expected_error_string.is_empty(),
+                "expected test to succeed, but it errored with {:?}",
+                err
+            );
+
+            // Assert the error is correct
+            assert!(err
+                .to_string()
+                .contains(params.expected_error_string.as_str()));
+        }
+    }
+
+    Ok(())
+}
diff --git a/contracts/adapters/swap/pryzm/tests/test_execute_swap.rs b/contracts/adapters/swap/pryzm/tests/test_execute_swap.rs
new file mode 100644
index 00000000..1f44d996
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/tests/test_execute_swap.rs
@@ -0,0 +1,419 @@
+#[allow(unused_imports)]
+use cosmwasm_std::{
+    testing::{mock_dependencies, mock_env, mock_info},
+    to_json_binary, Addr, Coin,
+    ReplyOn::Never,
+    ReplyOn::Success,
+    SubMsg, WasmMsg,
+};
+#[allow(unused_imports)]
+use pryzm_std::types::{
+    cosmos::base::v1beta1::Coin as CosmosCoin,
+    pryzm::amm::v1::{MsgBatchSwap, SwapStep, SwapType},
+    pryzm::icstaking::v1::MsgStake,
+};
+use std::collections::VecDeque;
+use test_case::test_case;
+
+#[allow(unused_imports)]
+use skip::swap::{ExecuteMsg, SwapOperation};
+
+use skip_go_swap_adapter_pryzm::execution::SwapExecutionStep;
+use skip_go_swap_adapter_pryzm::state::{IN_PROGRESS_SWAP_OPERATIONS, IN_PROGRESS_SWAP_SENDER};
+#[allow(unused_imports)]
+use skip_go_swap_adapter_pryzm::{
+    contract, error::ContractResult, reply_id::BATCH_SWAP_REPLY_ID, reply_id::STAKE_REPLY_ID,
+    state::ENTRY_POINT_CONTRACT_ADDRESS,
+};
+/*
+Test Cases:
+
+Expect Success
+    - One Swap Operation
+    - Multiple Swap Operations
+    - No Swap Operations (This is prevented in the entry point contract)
+
+Expect Error
+    - Unauthorized Caller (Only the stored entry point contract can call this function)
+    - No Coin Sent
+    - More Than One Coin Sent
+    - Invalid Pool ID Conversion For Swap Operations
+
+ */
+
+// Define test parameters
+struct Params {
+    caller: String,
+    info_funds: Vec<Coin>,
+    swap_operations: Vec<SwapOperation>,
+    expected_messages: Vec<SubMsg>,
+    expected_error_string: String,
+    expected_stored_swapper: String,
+    expected_stored_steps: Vec<SwapExecutionStep>,
+}
+
+// Test execute_swap
+#[test_case(
+    Params {
+        caller: "entry_point".to_string(),
+        info_funds: vec![Coin::new(100, "pr")],
+        swap_operations: vec![
+            SwapOperation {
+                pool: "amm:1".to_string(),
+                denom_in: "pr".to_string(),
+                denom_out: "ibc/uusdc".to_string(),
+                interface: None,
+            }
+        ],
+        expected_messages: vec![
+            SubMsg {
+                id: 0,
+                msg: MsgBatchSwap {
+                    creator: "swap_contract_address".to_string(),
+                    swap_type: SwapType::GivenIn.into(),
+                    max_amounts_in: vec![CosmosCoin {amount: "100".to_string(), denom: "pr".to_string()}],
+                    min_amounts_out: vec![CosmosCoin {amount: "1".to_string(), denom: "ibc/uusdc".to_string()}],
+                    steps: vec![
+                        SwapStep {
+                            pool_id: 1,
+                            token_in: "pr".to_string(),
+                            token_out: "ibc/uusdc".to_string(),
+                            amount: Some("100".to_string()),
+                        }
+                    ]
+                }
+                .into(),
+                gas_limit: None,
+                reply_on: Never,
+            },
+            SubMsg {
+                id: 0,
+                msg: WasmMsg::Execute {
+                    contract_addr: "swap_contract_address".to_string(),
+                    msg: to_json_binary(&ExecuteMsg::TransferFundsBack {
+                        return_denom: "ibc/uusdc".to_string(),
+                        swapper: Addr::unchecked("entry_point"),
+                    })?,
+                    funds: vec![],
+                }
+                .into(),
+                gas_limit: None,
+                reply_on: Never,
+            },
+        ],
+        expected_error_string: "".to_string(),
+        expected_stored_swapper: "".to_string(),
+        expected_stored_steps: vec![],
+    };
+"One Swap Operation")]
+#[test_case(
+    Params {
+        caller: "entry_point".to_string(),
+        info_funds: vec![Coin::new(2000, "pr")],
+        swap_operations: vec![
+            SwapOperation {
+                pool: "amm:1".to_string(),
+                denom_in: "pr".to_string(),
+                denom_out: "ibc/uusdc".to_string(),
+                interface: None,
+            },
+            SwapOperation {
+                pool: "amm:2".to_string(),
+                denom_in: "ibc/uusdc".to_string(),
+                denom_out: "ibc/uatom".to_string(),
+                interface: None,
+            },
+            SwapOperation {
+                pool: "icstaking:uatom:channel-0".to_string(),
+                denom_in: "ibc/uatom".to_string(),
+                denom_out: "c:uatom".to_string(),
+                interface: None,
+            },
+            SwapOperation {
+                pool: "amm:3".to_string(),
+                denom_in: "c:uatom".to_string(),
+                denom_out: "p:uatom:30Sep2024".to_string(),
+                interface: None,
+            }
+        ],
+        expected_messages: vec![
+            SubMsg {
+                id: BATCH_SWAP_REPLY_ID,
+                msg: MsgBatchSwap {
+                    creator: "swap_contract_address".to_string(),
+                    swap_type: SwapType::GivenIn.into(),
+                    max_amounts_in: vec![CosmosCoin {
+                        amount: "2000".to_string(),
+                        denom: "pr".to_string(),
+                    }],
+                    min_amounts_out: vec![CosmosCoin {
+                        amount: "1".to_string(),
+                        denom: "ibc/uatom".to_string(),
+                    }],
+                    steps: vec![
+                        SwapStep {
+                            pool_id: 1,
+                            token_in: "pr".to_string(),
+                            token_out: "ibc/uusdc".to_string(),
+                            amount: Some("2000".to_string()),
+                        },
+                        SwapStep {
+                            pool_id: 2,
+                            token_in: "ibc/uusdc".to_string(),
+                            token_out: "ibc/uatom".to_string(),
+                            amount: None,
+                        },
+                    ],
+                }
+                .into(),
+                gas_limit: None,
+                reply_on: Success,
+            },
+        ],
+        expected_error_string: "".to_string(),
+        expected_stored_swapper: "entry_point".to_string(),
+        expected_stored_steps: vec![
+            SwapExecutionStep::Swap {
+                swap_steps: vec![
+                    SwapStep {
+                        pool_id: 1,
+                        token_in: "pr".to_string(),
+                        token_out: "ibc/uusdc".to_string(),
+                        amount: None,
+                    },
+                    SwapStep {
+                        pool_id: 2,
+                        token_in: "ibc/uusdc".to_string(),
+                        token_out: "ibc/uatom".to_string(),
+                        amount: None,
+                    },
+                ],
+            },
+            SwapExecutionStep::Stake {
+                host_chain_id: "uatom".to_string(),
+                transfer_channel: "channel-0".to_string()
+            },
+            SwapExecutionStep::Swap {
+                swap_steps: vec![
+                    SwapStep {
+                        pool_id: 3,
+                        token_in: "c:uatom".to_string(),
+                        token_out: "p:uatom:30Sep2024".to_string(),
+                        amount: None,
+                    },
+                ],
+            },
+        ],
+    };
+"Multiple Swap Operations")]
+#[test_case(
+    Params {
+        caller: "entry_point".to_string(),
+        info_funds: vec![Coin::new(2000, "pr")],
+        swap_operations: vec![
+            SwapOperation {
+                pool: "icstaking:uatom:channel-0".to_string(),
+                denom_in: "pr".to_string(),
+                denom_out: "c:pr".to_string(),
+                interface: None,
+            },
+            SwapOperation {
+                pool: "amm:4".to_string(),
+                denom_in: "c:pr".to_string(),
+                denom_out: "p:pr:30Sep2024".to_string(),
+                interface: None,
+            }
+        ],
+        expected_messages: vec![
+            SubMsg {
+                id: STAKE_REPLY_ID,
+                msg: MsgStake {
+                    creator: "swap_contract_address".to_string(),
+                    host_chain: "uatom".to_string(),
+                    transfer_channel: "channel-0".to_string(),
+                    amount: "2000".to_string(),
+                }
+                .into(),
+                gas_limit: None,
+                reply_on: Success,
+            },
+        ],
+        expected_error_string: "".to_string(),
+        expected_stored_swapper: "entry_point".to_string(),
+        expected_stored_steps: vec![
+            SwapExecutionStep::Stake {
+                host_chain_id: "uatom".to_string(),
+                transfer_channel: "channel-0".to_string()
+            },
+            SwapExecutionStep::Swap {
+                swap_steps: vec![
+                    SwapStep {
+                        pool_id: 4,
+                        token_in: "c:pr".to_string(),
+                        token_out: "p:pr:30Sep2024".to_string(),
+                        amount: None,
+                    },
+                ],
+            },
+        ],
+    };
+"First Step Liquid Staking")]
+#[test_case(
+    Params {
+        caller: "entry_point".to_string(),
+        info_funds: vec![Coin::new(100, "pr")],
+        swap_operations: vec![],
+        expected_messages: vec![],
+        expected_error_string: "swap_operations cannot be empty".to_string(),
+        expected_stored_swapper: "".to_string(),
+        expected_stored_steps: vec![],
+    };
+"No Swap Operations")]
+#[test_case(
+    Params {
+        caller: "entry_point".to_string(),
+        info_funds: vec![],
+        swap_operations: vec![
+            SwapOperation {
+                pool: "pool_1".to_string(),
+                denom_in: "pr".to_string(),
+                denom_out: "uatom".to_string(),
+                interface: None,
+            }
+        ],
+        expected_messages: vec![],
+        expected_error_string: "No funds sent".to_string(),
+        expected_stored_swapper: "".to_string(),
+        expected_stored_steps: vec![],
+    };
+    "No Coin Sent - Expect Error")]
+#[test_case(
+    Params {
+        caller: "entry_point".to_string(),
+        info_funds: vec![
+            Coin::new(100, "pr"),
+            Coin::new(100, "uatom"),
+        ],
+        swap_operations: vec![
+            SwapOperation {
+                pool: "pool_1".to_string(),
+                denom_in: "pr".to_string(),
+                denom_out: "uatom".to_string(),
+                interface: None,
+            }
+        ],
+        expected_messages: vec![],
+        expected_error_string: "Sent more than one denomination".to_string(),
+        expected_stored_swapper: "".to_string(),
+        expected_stored_steps: vec![],
+    };
+    "More Than One Coin Sent - Expect Error")]
+#[test_case(
+    Params {
+        caller: "entry_point".to_string(),
+        info_funds: vec![Coin::new(100, "pr")],
+        swap_operations: vec![
+            SwapOperation {
+                pool: "1".to_string(),
+                denom_in: "pr".to_string(),
+                denom_out: "ibc/uusdc".to_string(),
+                interface: None,
+            }
+        ],
+        expected_messages: vec![],
+        expected_error_string: "provided pool string is not a valid swap route".to_string(),
+        expected_stored_swapper: "".to_string(),
+        expected_stored_steps: vec![],
+    };
+    "Invalid Pool For Swap Operations - Expect Error")]
+#[test_case(
+    Params {
+        caller: "random".to_string(),
+        info_funds: vec![Coin::new(100, "pr")],
+        swap_operations: vec![],
+        expected_messages: vec![],
+        expected_error_string: "Unauthorized".to_string(),
+        expected_stored_swapper: "".to_string(),
+        expected_stored_steps: vec![],
+    };
+    "Unauthorized Caller - Expect Error")]
+fn test_execute_swap(params: Params) -> ContractResult<()> {
+    // Create mock dependencies
+    let mut deps = mock_dependencies();
+
+    // Create mock env
+    let mut env = mock_env();
+    env.contract.address = Addr::unchecked("swap_contract_address");
+
+    // Convert info funds vector into a slice of Coin objects
+    let info_funds: &[Coin] = &params.info_funds;
+
+    // Create mock info with entry point contract address
+    let info = mock_info(&params.caller, info_funds);
+
+    // Store the entry point contract address
+    ENTRY_POINT_CONTRACT_ADDRESS.save(deps.as_mut().storage, &Addr::unchecked("entry_point"))?;
+
+    // Call execute_swap with the given test parameters
+    let res = contract::execute(
+        deps.as_mut(),
+        env,
+        info,
+        ExecuteMsg::Swap {
+            operations: params.swap_operations.clone(),
+        },
+    );
+
+    // Assert the behavior is correct
+    match res {
+        Ok(res) => {
+            // Assert the test did not expect an error
+            assert!(
+                params.expected_error_string.is_empty(),
+                "expected test to error with {:?}, but it succeeded",
+                params.expected_error_string
+            );
+
+            // Assert the messages are correct
+            assert_eq!(res.messages, params.expected_messages);
+
+            if !params.expected_stored_steps.is_empty() {
+                // Assert the stored steps are correct
+                let stored_steps = IN_PROGRESS_SWAP_OPERATIONS.load(deps.as_ref().storage)?;
+                assert_eq!(stored_steps, VecDeque::from(params.expected_stored_steps));
+            } else {
+                // Assert no steps are stored
+                assert!(IN_PROGRESS_SWAP_OPERATIONS
+                    .load(deps.as_ref().storage)
+                    .is_err());
+            }
+
+            if !params.expected_stored_swapper.is_empty() {
+                // Assert the stored swapper is correct
+                let stored_swapper = IN_PROGRESS_SWAP_SENDER.load(deps.as_ref().storage)?;
+                assert_eq!(
+                    stored_swapper,
+                    Addr::unchecked(params.expected_stored_swapper.as_str())
+                );
+            } else {
+                // Assert no address is stored
+                assert!(IN_PROGRESS_SWAP_SENDER.load(deps.as_ref().storage).is_err());
+            }
+        }
+        Err(err) => {
+            // Assert the test expected an error
+            assert!(
+                !params.expected_error_string.is_empty(),
+                "expected test to succeed, but it errored with {:?}",
+                err
+            );
+
+            // Assert the error is correct
+            assert!(err
+                .to_string()
+                .contains(params.expected_error_string.as_str()));
+        }
+    }
+
+    Ok(())
+}
diff --git a/contracts/adapters/swap/pryzm/tests/test_execute_transfer_funds_back.rs b/contracts/adapters/swap/pryzm/tests/test_execute_transfer_funds_back.rs
new file mode 100644
index 00000000..18350527
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/tests/test_execute_transfer_funds_back.rs
@@ -0,0 +1,169 @@
+use cosmwasm_std::{
+    testing::{mock_dependencies_with_balances, mock_env, mock_info},
+    Addr, BankMsg, Coin,
+    ReplyOn::Never,
+    SubMsg,
+};
+use test_case::test_case;
+
+use skip::{error::SkipError, swap::ExecuteMsg};
+use skip_go_swap_adapter_pryzm::contract;
+use skip_go_swap_adapter_pryzm::error::{ContractError, ContractResult};
+
+/*
+Test Cases:
+
+Expect Success
+    - One Coin Balance
+    - Multiple Coin Balance
+    - No Coin Balance (This will fail at the bank module if attempted)
+
+Expect Error
+    - Unauthorized Caller (Only contract itself can call this function)
+ */
+
+// Define test parameters
+struct Params {
+    caller: String,
+    contract_balance: Vec<Coin>,
+    return_denom: String,
+    expected_messages: Vec<SubMsg>,
+    expected_error: Option<ContractError>,
+}
+
+// Test execute_transfer_funds_back
+#[test_case(
+    Params {
+        caller: "swap_contract_address".to_string(),
+        contract_balance: vec![Coin::new(100, "pr")],
+        return_denom: "pr".to_string(),
+        expected_messages: vec![
+            SubMsg {
+                id: 0,
+                msg: BankMsg::Send {
+                    to_address: "swapper".to_string(),
+                    amount: vec![Coin::new(100, "pr")],
+                }.into(),
+                gas_limit: None,
+                reply_on: Never,
+            },
+        ],
+        expected_error: None,
+    };
+    "Transfers One Coin Balance")]
+#[test_case(
+    Params {
+        caller: "swap_contract_address".to_string(),
+        contract_balance: vec![
+            Coin::new(100, "pr"),
+            Coin::new(100, "uatom"),
+        ],
+        return_denom: "pr".to_string(),
+        expected_messages: vec![
+            SubMsg {
+                id: 0,
+                msg: BankMsg::Send {
+                    to_address: "swapper".to_string(),
+                    amount: vec![
+                        Coin::new(100, "pr"),
+                        Coin::new(100, "uatom")
+                    ],
+                }.into(),
+                gas_limit: None,
+                reply_on: Never,
+            },
+        ],
+        expected_error: None,
+    };
+    "Transfers Multiple Coin Balance")]
+#[test_case(
+    Params {
+        caller: "swap_contract_address".to_string(),
+        contract_balance: vec![],
+        return_denom: "pr".to_string(),
+        expected_messages: vec![
+            SubMsg {
+                id: 0,
+                msg: BankMsg::Send {
+                    to_address: "swapper".to_string(),
+                    amount: vec![],
+                }.into(),
+                gas_limit: None,
+                reply_on: Never,
+            },
+        ],
+        expected_error: None,
+    };
+    "Transfers No Coin Balance")]
+#[test_case(
+    Params {
+        caller: "random".to_string(),
+        contract_balance: vec![],
+        return_denom: "pr".to_string(),
+        expected_messages: vec![
+            SubMsg {
+                id: 0,
+                msg: BankMsg::Send {
+                    to_address: "swapper".to_string(),
+                    amount: vec![],
+                }.into(),
+                gas_limit: None,
+                reply_on: Never,
+            },
+        ],
+        expected_error: Some(ContractError::Skip(SkipError::Unauthorized)),
+    };
+    "Unauthorized Caller")]
+fn test_execute_transfer_funds_back(params: Params) -> ContractResult<()> {
+    // Convert params contract balance to a slice
+    let contract_balance: &[Coin] = &params.contract_balance;
+
+    // Create mock dependencies
+    let mut deps = mock_dependencies_with_balances(&[("swap_contract_address", contract_balance)]);
+
+    // Create mock env
+    let mut env = mock_env();
+    env.contract.address = Addr::unchecked("swap_contract_address");
+
+    // Create mock info
+    let info = mock_info(&params.caller, &[]);
+
+    // Call execute_swap with the given test parameters
+    let res = contract::execute(
+        deps.as_mut(),
+        env,
+        info,
+        ExecuteMsg::TransferFundsBack {
+            return_denom: params.return_denom,
+            swapper: Addr::unchecked("swapper"),
+        },
+    );
+
+    // Assert the behavior is correct
+    match res {
+        Ok(res) => {
+            // Assert the test did not expect an error
+            assert!(
+                params.expected_error.is_none(),
+                "expected test to error with {:?}, but it succeeded",
+                params.expected_error
+            );
+
+            // Assert the messages are correct
+            assert_eq!(res.messages, params.expected_messages);
+        }
+        Err(err) => {
+            // Assert the test expected an error
+            assert!(
+                params.expected_error.is_some(),
+                "expected test to succeed, but it errored with {:?}",
+                err
+            );
+
+            // Assert the error is correct
+            assert_eq!(err, params.expected_error.unwrap());
+        }
+    }
+
+    Ok(())
+}
diff --git a/contracts/adapters/swap/pryzm/tests/test_execution.rs b/contracts/adapters/swap/pryzm/tests/test_execution.rs
new file mode 100644
index 00000000..c51cdb1f
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/tests/test_execution.rs
@@ -0,0 +1,435 @@
+use cosmwasm_std::{coin, CosmosMsg};
+use pryzm_std::types::cosmos::base::v1beta1::Coin as CosmosCoin;
+use pryzm_std::types::pryzm::{
+    amm::v1::{MsgBatchSwap, SwapStep, SwapType},
+    icstaking::v1::MsgStake,
+};
+
+use skip::swap::SwapOperation;
+use skip_go_swap_adapter_pryzm::error::ContractError;
+use skip_go_swap_adapter_pryzm::execution::{extract_execution_steps, SwapExecutionStep};
+
+#[test]
+fn test_execution_step_return_denom() {
+    // empty swap steps
+    let step = SwapExecutionStep::Swap { swap_steps: vec![] };
+    assert!(step.get_return_denom().is_err());
+
+    // single step Swap type step
+    let step = SwapExecutionStep::Swap {
+        swap_steps: vec![SwapStep {
+            pool_id: 1,
+            token_in: "a".to_string(),
+            token_out: "b".to_string(),
+            amount: Some("1000".to_string()),
+        }],
+    };
+    assert!(step.get_return_denom().is_ok());
+    assert_eq!("b", step.get_return_denom().unwrap());
+
+    // multistep Swap type step
+    let step = SwapExecutionStep::Swap {
+        swap_steps: vec![
+            SwapStep {
+                pool_id: 1,
+                token_in: "a".to_string(),
+                token_out: "b".to_string(),
+                amount: Some("1000".to_string()),
+            },
+            SwapStep {
+                pool_id: 3,
+                token_in: "b".to_string(),
+                token_out: "c".to_string(),
+                amount: None,
+            },
+            SwapStep {
+                pool_id: 2,
+                token_in: "c".to_string(),
+                token_out: "d".to_string(),
+                amount: None,
+            },
+        ],
+    };
+    assert!(step.get_return_denom().is_ok());
+    assert_eq!("d", step.get_return_denom().unwrap());
+
+    // stake step
+    let step = SwapExecutionStep::Stake {
+        host_chain_id: "uatom".to_string(),
+        transfer_channel: "channel-0".to_string(),
+    };
+    assert!(step.get_return_denom().is_ok());
+    assert_eq!("c:uatom", step.get_return_denom().unwrap());
+}
+
+#[test]
+fn test_execution_step_cosmos_msg() {
+    let address = "address";
+
+    // empty swap steps
+    let step = SwapExecutionStep::Swap { swap_steps: vec![] };
+    assert!(step
+        .to_cosmos_msg(address.to_string(), coin(1000, "a"))
+        .is_err());
+
+    // single step Swap type step
+    let step = SwapExecutionStep::Swap {
+        swap_steps: vec![SwapStep {
+            pool_id: 1,
+            token_in: "a".to_string(),
+            token_out: "b".to_string(),
+            amount: None,
+        }],
+    };
+    let result = step.to_cosmos_msg(address.to_string(), coin(1000, "a"));
+    assert!(result.is_ok());
+    assert_eq!(
+        <MsgBatchSwap as Into<CosmosMsg>>::into(MsgBatchSwap {
+            creator: address.to_string(),
+            swap_type: SwapType::GivenIn.into(),
+            max_amounts_in: vec![CosmosCoin {
+                amount: "1000".to_string(),
+                denom: "a".to_string()
+            }],
+            min_amounts_out: vec![CosmosCoin {
+                amount: "1".to_string(),
+                denom: "b".to_string()
+            }],
+            steps: vec![SwapStep {
+                pool_id: 1,
+                token_in: "a".to_string(),
+                token_out: "b".to_string(),
+                amount: "1000".to_string().into()
+            }],
+        }),
+        result.unwrap()
+    );
+
+    // multistep Swap type step
+    let step = SwapExecutionStep::Swap {
+        swap_steps: vec![
+            SwapStep {
+                pool_id: 1,
+                token_in: "a".to_string(),
+                token_out: "b".to_string(),
+                amount: None,
+            },
+            SwapStep {
+                pool_id: 3,
+                token_in: "b".to_string(),
+                token_out: "c".to_string(),
+                amount: None,
+            },
+            SwapStep {
+                pool_id: 2,
+                token_in: "c".to_string(),
+                token_out: "d".to_string(),
+                amount: None,
+            },
+        ],
+    };
+    let result = step.to_cosmos_msg(address.to_string(), coin(1000, "a"));
+    assert!(result.is_ok());
+    assert_eq!(
+        <MsgBatchSwap as Into<CosmosMsg>>::into(MsgBatchSwap {
+            creator: address.to_string(),
+            swap_type: SwapType::GivenIn.into(),
+            max_amounts_in: vec![CosmosCoin {
+                amount: "1000".to_string(),
+                denom: "a".to_string()
+            }],
+            min_amounts_out: vec![CosmosCoin {
+                amount: "1".to_string(),
+                denom: "d".to_string()
+            }],
+            steps: vec![
+                SwapStep {
+                    pool_id: 1,
+                    token_in: "a".to_string(),
+                    token_out: "b".to_string(),
+                    amount: "1000".to_string().into()
+                },
+                SwapStep {
+                    pool_id: 3,
+                    token_in: "b".to_string(),
+                    token_out: "c".to_string(),
+                    amount: None
+                },
+                SwapStep {
+                    pool_id: 2,
+                    token_in: "c".to_string(),
+                    token_out: "d".to_string(),
+                    amount: None
+                }
+            ],
+        }),
+        result.unwrap()
+    );
+
+    // stake step
+    let step = SwapExecutionStep::Stake {
+        host_chain_id: "uatom".to_string(),
+        transfer_channel: "channel-0".to_string(),
+    };
+    let result = step.to_cosmos_msg(address.to_string(), coin(1000, "uatom"));
+    assert!(result.is_ok());
+    assert_eq!(
+        <MsgStake as Into<CosmosMsg>>::into(MsgStake {
+            creator: address.to_string(),
+            host_chain: "uatom".to_string(),
+            transfer_channel: "channel-0".to_string(),
+            amount: "1000".into(),
+        }),
+        result.unwrap()
+    );
+}
+
+#[test]
+fn test_extract_execution_step() {
+    // empty swap operations
+    let result = extract_execution_steps(vec![]);
+    assert!(result.is_err());
+
+    // single amm swap step
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "amm:1".to_string(),
+        denom_in: "a".to_string(),
+        denom_out: "b".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_ok());
+    let vec = result.unwrap();
+    assert_eq!(1, vec.len());
+    assert_eq!(
+        SwapExecutionStep::Swap {
+            swap_steps: vec![SwapStep {
+                pool_id: 1,
+                token_in: "a".to_string(),
+                token_out: "b".to_string(),
+                amount: None
+            }]
+        },
+        vec.front().unwrap().clone()
+    );
+
+    // multi step amm swap
+    let result = extract_execution_steps(vec![
+        SwapOperation {
+            pool: "amm:1".to_string(),
+            denom_in: "a".to_string(),
+            denom_out: "b".to_string(),
+            interface: None,
+        },
+        SwapOperation {
+            pool: "amm:3".to_string(),
+            denom_in: "b".to_string(),
+            denom_out: "c".to_string(),
+            interface: None,
+        },
+        SwapOperation {
+            pool: "amm:2".to_string(),
+            denom_in: "c".to_string(),
+            denom_out: "d".to_string(),
+            interface: None,
+        },
+    ]);
+    assert!(result.is_ok());
+    let vec = result.unwrap();
+    assert_eq!(1, vec.len());
+    assert_eq!(
+        SwapExecutionStep::Swap {
+            swap_steps: vec![
+                SwapStep {
+                    pool_id: 1,
+                    token_in: "a".to_string(),
+                    token_out: "b".to_string(),
+                    amount: None
+                },
+                SwapStep {
+                    pool_id: 3,
+                    token_in: "b".to_string(),
+                    token_out: "c".to_string(),
+                    amount: None
+                },
+                SwapStep {
+                    pool_id: 2,
+                    token_in: "c".to_string(),
+                    token_out: "d".to_string(),
+                    amount: None
+                }
+            ]
+        },
+        vec.front().unwrap().clone()
+    );
+
+    // single staking step
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "icstaking:uatom:channel-0".to_string(),
+        denom_in: "uatom".to_string(),
+        denom_out: "c:uatom".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_ok());
+    let vec = result.unwrap();
+    assert_eq!(1, vec.len());
+    assert_eq!(
+        SwapExecutionStep::Stake {
+            host_chain_id: "uatom".to_string(),
+            transfer_channel: "channel-0".to_string(),
+        },
+        vec.front().unwrap().clone()
+    );
+
+    // multiple steps including amm and icstaking
+    let result = extract_execution_steps(vec![
+        SwapOperation {
+            pool: "amm:7".to_string(),
+            denom_in: "uusdc".to_string(),
+            denom_out: "uauuu".to_string(),
+            interface: None,
+        },
+        SwapOperation {
+            pool: "amm:12".to_string(),
+            denom_in: "uauuu".to_string(),
+            denom_out: "uatom".to_string(),
+            interface: None,
+        },
+        SwapOperation {
+            pool: "icstaking:uatom:channel-0".to_string(),
+            denom_in: "uatom".to_string(),
+            denom_out: "c:uatom".to_string(),
+            interface: None,
+        },
+        SwapOperation {
+            pool: "amm:1".to_string(),
+            denom_in: "c:uatom".to_string(),
+            denom_out: "y:uatom:30Sep2024".to_string(),
+            interface: None,
+        },
+    ]);
+    assert!(result.is_ok());
+    let mut vec = result.unwrap();
+    assert_eq!(3, vec.len());
+    assert_eq!(
+        SwapExecutionStep::Swap {
+            swap_steps: vec![
+                SwapStep {
+                    pool_id: 7,
+                    token_in: "uusdc".to_string(),
+                    token_out: "uauuu".to_string(),
+                    amount: None
+                },
+                SwapStep {
+                    pool_id: 12,
+                    token_in: "uauuu".to_string(),
+                    token_out: "uatom".to_string(),
+                    amount: None
+                },
+            ]
+        },
+        vec.pop_front().unwrap().clone()
+    );
+    assert_eq!(
+        SwapExecutionStep::Stake {
+            host_chain_id: "uatom".to_string(),
+            transfer_channel: "channel-0".to_string(),
+        },
+        vec.pop_front().unwrap().clone()
+    );
+    assert_eq!(
+        SwapExecutionStep::Swap {
+            swap_steps: vec![SwapStep {
+                pool_id: 1,
+                token_in: "c:uatom".to_string(),
+                token_out: "y:uatom:30Sep2024".to_string(),
+                amount: None
+            },]
+        },
+        vec.pop_front().unwrap().clone()
+    );
+
+    // invalid pools
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "amm:invalid".to_string(),
+        denom_in: "a".to_string(),
+        denom_out: "b".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_err());
+    assert!(matches!(
+        result.err().unwrap(),
+        ContractError::InvalidPool { .. }
+    ));
+
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "invalid:1".to_string(),
+        denom_in: "a".to_string(),
+        denom_out: "b".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_err());
+    assert!(matches!(
+        result.err().unwrap(),
+        ContractError::InvalidPool { .. }
+    ));
+
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "icstaking:1".to_string(),
+        denom_in: "uatom".to_string(),
+        denom_out: "c:uatom".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_err());
+    assert!(matches!(
+        result.err().unwrap(),
+        ContractError::InvalidPool { .. }
+    ));
+
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "icstaking:uatom:channel-0".to_string(),
+        denom_in: "c:uatom".to_string(),
+        denom_out: "uatom".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_err());
+    assert!(matches!(
+        result.err().unwrap(),
+        ContractError::InvalidPool { .. }
+    ));
+
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "icstaking:uatom:channel-0".to_string(),
+        denom_in: "c:uatom".to_string(),
+        denom_out: "c:uosmo".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_err());
+    assert!(matches!(
+        result.err().unwrap(),
+        ContractError::InvalidPool { .. }
+    ));
+
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "icstaking:uatom:channel-0".to_string(),
+        denom_in: "uatom".to_string(),
+        denom_out: "uosmo".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_err());
+    assert!(matches!(
+        result.err().unwrap(),
+        ContractError::InvalidPool { .. }
+    ));
+
+    let result = extract_execution_steps(vec![SwapOperation {
+        pool: "icstaking:uatom:channel-0:some".to_string(),
+        denom_in: "uatom".to_string(),
+        denom_out: "c:uatom".to_string(),
+        interface: None,
+    }]);
+    assert!(result.is_err());
+    assert!(matches!(
+        result.err().unwrap(),
+        ContractError::InvalidPool { .. }
+    ));
+}
diff --git a/contracts/adapters/swap/pryzm/tests/test_simulate.rs b/contracts/adapters/swap/pryzm/tests/test_simulate.rs
new file mode 100644
index 00000000..824ee916
--- /dev/null
+++ b/contracts/adapters/swap/pryzm/tests/test_simulate.rs
@@ -0,0 +1,769 @@
+use std::marker::PhantomData;
+use std::str::FromStr;
+
+use cosmwasm_std::testing::{mock_env, MockApi, MockStorage};
+use cosmwasm_std::{coin, from_json, Decimal, OwnedDeps, StdResult};
+use pryzm_std::types::cosmos::base::v1beta1::Coin as CosmosCoin;
+use pryzm_std::types::pryzm::amm::v1::{
+    QuerySimulateBatchSwapRequest, QuerySimulateBatchSwapResponse, QuerySpotPriceRequest,
+    QuerySpotPriceResponse, SwapStep, SwapType,
+};
+use pryzm_std::types::pryzm::icstaking::v1::{
+    QuerySimulateStakeRequest, QuerySimulateStakeResponse,
+};
+
+use skip::asset::Asset;
+use skip::error::SkipError;
+use skip::swap::{
+    QueryMsg, Route, SimulateSwapExactAssetInResponse, SimulateSwapExactAssetOutResponse,
+    SwapOperation,
+};
+use skip_go_swap_adapter_pryzm::contract;
+use skip_go_swap_adapter_pryzm::error::ContractError;
+
+use crate::mock::MockQuerier;
+
+mod mock;
+
+#[test]
+fn test_simulate_exact_asset_in() {
+    let mut deps = OwnedDeps {
+        storage: MockStorage::default(),
+        api: MockApi::default(),
+        querier: setup_mocks(),
+        custom_query_type: PhantomData,
+    };
+
+    // empty swap operations
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetIn {
+            asset_in: Asset::Native(coin(1000, "ibc/uatom")),
+            swap_operations: vec![],
+        },
+    );
+    assert!(res.is_err());
+    assert!(matches!(
+        res.err().unwrap(),
+        ContractError::SwapOperationsEmpty
+    ));
+
+    // invalid asset provided
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetIn {
+            asset_in: Asset::Native(coin(1000, "ibc/uosmo")),
+            swap_operations: vec![SwapOperation {
+                pool: "icstaking:uatom:channel-0".to_string(),
+                denom_in: "ibc/uatom".to_string(),
+                denom_out: "c:uatom".to_string(),
+                interface: None,
+            }],
+        },
+    );
+    assert!(res.is_err());
+    assert!(matches!(
+        res.err().unwrap(),
+        ContractError::CoinInDenomMismatch
+    ));
+
+    // valid stake operation
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetIn {
+            asset_in: Asset::Native(coin(1000, "ibc/uatom")),
+            swap_operations: vec![SwapOperation {
+                pool: "icstaking:uatom:channel-0".to_string(),
+                denom_in: "ibc/uatom".to_string(),
+                denom_out: "c:uatom".to_string(),
+                interface: None,
+            }],
+        },
+    );
+    assert!(res.is_ok());
+    let output: StdResult<Asset> = from_json(res.unwrap());
+    assert!(output.is_ok());
+    let token_out = output.unwrap();
+    assert_eq!("c:uatom", token_out.denom());
+    assert_eq!(950, token_out.amount().u128());
+
+    // valid multi step swap
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetIn {
+            asset_in: Asset::Native(coin(2000, "ibc/uosmo")),
+            swap_operations: vec![
+                SwapOperation {
+                    pool: "amm:1".to_string(),
+                    denom_in: "ibc/uosmo".to_string(),
+                    denom_out: "ibc/uusdc".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "amm:2".to_string(),
+                    denom_in: "ibc/uusdc".to_string(),
+                    denom_out: "ibc/uatom".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "icstaking:uatom:channel-0".to_string(),
+                    denom_in: "ibc/uatom".to_string(),
+                    denom_out: "c:uatom".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "amm:3".to_string(),
+                    denom_in: "c:uatom".to_string(),
+                    denom_out: "y:uatom:30Sep2024".to_string(),
+                    interface: None,
+                },
+            ],
+        },
+    );
+    assert!(res.is_ok());
+    let output: StdResult<Asset> = from_json(res.unwrap());
+    assert!(output.is_ok());
+    let token_out = output.unwrap();
+    assert_eq!("y:uatom:30Sep2024", token_out.denom());
+    assert_eq!(1200, token_out.amount().u128());
+
+    // get with spot price
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetInWithMetadata {
+            asset_in: Asset::Native(coin(2000, "ibc/uosmo")),
+            swap_operations: vec![
+                SwapOperation {
+                    pool: "amm:1".to_string(),
+                    denom_in: "ibc/uosmo".to_string(),
+                    denom_out: "ibc/uusdc".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "amm:2".to_string(),
+                    denom_in: "ibc/uusdc".to_string(),
+                    denom_out: "ibc/uatom".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "icstaking:uatom:channel-0".to_string(),
+                    denom_in: "ibc/uatom".to_string(),
+                    denom_out: "c:uatom".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "amm:3".to_string(),
+                    denom_in: "c:uatom".to_string(),
+                    denom_out: "y:uatom:30Sep2024".to_string(),
+                    interface: None,
+                },
+            ],
+            include_spot_price: true,
+        },
+    );
+    assert!(res.is_ok());
+    let output: StdResult<SimulateSwapExactAssetInResponse> = from_json(res.unwrap());
+    assert!(output.is_ok());
+    let response = output.unwrap();
+    let token_out = response.asset_out;
+    assert_eq!("y:uatom:30Sep2024", token_out.denom());
+    assert_eq!(1200, token_out.amount().u128());
+    assert_eq!(
+        Decimal::from_str("0.600000000125").unwrap(),
+        response.spot_price.unwrap()
+    );
+}
+
+#[test]
+fn test_simulate_exact_asset_out() {
+    let mut deps = OwnedDeps {
+        storage: MockStorage::default(),
+        api: MockApi::default(),
+        querier: setup_mocks(),
+        custom_query_type: PhantomData,
+    };
+
+    // empty swap operations
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetOut {
+            asset_out: Asset::Native(coin(1200, "y:uatom:30Sep2024")),
+            swap_operations: vec![],
+        },
+    );
+    assert!(res.is_err());
+    assert!(matches!(
+        res.err().unwrap(),
+        ContractError::SwapOperationsEmpty
+    ));
+
+    // invalid asset provided
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetOut {
+            asset_out: Asset::Native(coin(1000, "c:uosmo")),
+            swap_operations: vec![SwapOperation {
+                pool: "icstaking:uatom:channel-0".to_string(),
+                denom_in: "ibc/uatom".to_string(),
+                denom_out: "c:uatom".to_string(),
+                interface: None,
+            }],
+        },
+    );
+    assert!(res.is_err());
+    assert!(matches!(
+        res.err().unwrap(),
+        ContractError::CoinOutDenomMismatch
+    ));
+
+    // valid stake operation
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetOut {
+            asset_out: Asset::Native(coin(950, "c:uatom")),
+            swap_operations: vec![SwapOperation {
+                pool: "icstaking:uatom:channel-0".to_string(),
+                denom_in: "ibc/uatom".to_string(),
+                denom_out: "c:uatom".to_string(),
+                interface: None,
+            }],
+        },
+    );
+    assert!(res.is_ok());
+    let output: StdResult<Asset> = from_json(res.unwrap());
+    assert!(output.is_ok());
+    let token_in = output.unwrap();
+    assert_eq!("ibc/uatom", token_in.denom());
+    assert_eq!(1000, token_in.amount().u128());
+
+    // valid multi step swap
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetOut {
+            asset_out: Asset::Native(coin(1200, "y:uatom:30Sep2024")),
+            swap_operations: vec![
+                SwapOperation {
+                    pool: "amm:1".to_string(),
+                    denom_in: "ibc/uosmo".to_string(),
+                    denom_out: "ibc/uusdc".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "amm:2".to_string(),
+                    denom_in: "ibc/uusdc".to_string(),
+                    denom_out: "ibc/uatom".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "icstaking:uatom:channel-0".to_string(),
+                    denom_in: "ibc/uatom".to_string(),
+                    denom_out: "c:uatom".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "amm:3".to_string(),
+                    denom_in: "c:uatom".to_string(),
+                    denom_out: "y:uatom:30Sep2024".to_string(),
+                    interface: None,
+                },
+            ],
+        },
+    );
+    assert!(res.is_ok());
+    let output: StdResult<Asset> = from_json(res.unwrap());
+    assert!(output.is_ok());
+    let token_in = output.unwrap();
+    assert_eq!("ibc/uosmo", token_in.denom());
+    assert_eq!(2000, token_in.amount().u128());
+
+    // get with spot price
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSwapExactAssetOutWithMetadata {
+            asset_out: Asset::Native(coin(1200, "y:uatom:30Sep2024")),
+            swap_operations: vec![
+                SwapOperation {
+                    pool: "amm:1".to_string(),
+                    denom_in: "ibc/uosmo".to_string(),
+                    denom_out: "ibc/uusdc".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "amm:2".to_string(),
+                    denom_in: "ibc/uusdc".to_string(),
+                    denom_out: "ibc/uatom".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "icstaking:uatom:channel-0".to_string(),
+                    denom_in: "ibc/uatom".to_string(),
+                    denom_out: "c:uatom".to_string(),
+                    interface: None,
+                },
+                SwapOperation {
+                    pool: "amm:3".to_string(),
+                    denom_in: "c:uatom".to_string(),
+                    denom_out: "y:uatom:30Sep2024".to_string(),
+                    interface: None,
+                },
+            ],
+            include_spot_price: true,
+        },
+    );
+    assert!(res.is_ok());
+    let output: StdResult<SimulateSwapExactAssetOutResponse> = from_json(res.unwrap());
+    assert!(output.is_ok());
+    let response = output.unwrap();
+    let token_in = response.asset_in;
+    assert_eq!("ibc/uosmo", token_in.denom());
+    assert_eq!(2000, token_in.amount().u128());
+    assert_eq!(
+        Decimal::from_str("0.600000000125").unwrap(),
+        response.spot_price.unwrap()
+    );
+}
+
+#[test]
+fn test_simulate_smart_swap() {
+    let mut deps = OwnedDeps {
+        storage: MockStorage::default(),
+        api: MockApi::default(),
+        querier: setup_mocks(),
+        custom_query_type: PhantomData,
+    };
+
+    // empty routes
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSmartSwapExactAssetIn {
+            asset_in: Asset::Native(coin(1000, "ibc/uatom")),
+            routes: vec![],
+        },
+    );
+    assert!(res.is_err());
+    assert!(matches!(
+        res.err().unwrap(),
+        ContractError::Skip(SkipError::RoutesEmpty)
+    ));
+
+    // valid multi step swap
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSmartSwapExactAssetIn {
+            asset_in: Asset::Native(coin(5000, "ibc/uosmo")),
+            routes: vec![
+                Route {
+                    offer_asset: Asset::Native(coin(2000, "ibc/uosmo")),
+                    operations: vec![
+                        SwapOperation {
+                            pool: "amm:1".to_string(),
+                            denom_in: "ibc/uosmo".to_string(),
+                            denom_out: "ibc/uusdc".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:2".to_string(),
+                            denom_in: "ibc/uusdc".to_string(),
+                            denom_out: "ibc/uatom".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "icstaking:uatom:channel-0".to_string(),
+                            denom_in: "ibc/uatom".to_string(),
+                            denom_out: "c:uatom".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:3".to_string(),
+                            denom_in: "c:uatom".to_string(),
+                            denom_out: "y:uatom:30Sep2024".to_string(),
+                            interface: None,
+                        },
+                    ],
+                },
+                Route {
+                    offer_asset: Asset::Native(coin(3000, "ibc/uosmo")),
+                    operations: vec![
+                        SwapOperation {
+                            pool: "amm:1".to_string(),
+                            denom_in: "ibc/uosmo".to_string(),
+                            denom_out: "ibc/uusdc".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:2".to_string(),
+                            denom_in: "ibc/uusdc".to_string(),
+                            denom_out: "ibc/uatom".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:4".to_string(),
+                            denom_in: "ibc/uatom".to_string(),
+                            denom_out: "c:uatom".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:3".to_string(),
+                            denom_in: "c:uatom".to_string(),
+                            denom_out: "y:uatom:30Sep2024".to_string(),
+                            interface: None,
+                        },
+                    ],
+                },
+            ],
+        },
+    );
+    assert!(res.is_ok());
+    let output: StdResult<Asset> = from_json(res.unwrap());
+    assert!(output.is_ok());
+    let token_out = output.unwrap();
+    assert_eq!("y:uatom:30Sep2024", token_out.denom());
+    assert_eq!(2905, token_out.amount().u128());
+
+    // get with spot price
+    let res = contract::query(
+        deps.as_mut().as_ref(),
+        mock_env(),
+        QueryMsg::SimulateSmartSwapExactAssetInWithMetadata {
+            asset_in: Asset::Native(coin(5000, "ibc/uosmo")),
+            routes: vec![
+                Route {
+                    offer_asset: Asset::Native(coin(2000, "ibc/uosmo")),
+                    operations: vec![
+                        SwapOperation {
+                            pool: "amm:1".to_string(),
+                            denom_in: "ibc/uosmo".to_string(),
+                            denom_out: "ibc/uusdc".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:2".to_string(),
+                            denom_in: "ibc/uusdc".to_string(),
+                            denom_out: "ibc/uatom".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "icstaking:uatom:channel-0".to_string(),
+                            denom_in: "ibc/uatom".to_string(),
+                            denom_out: "c:uatom".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:3".to_string(),
+                            denom_in: "c:uatom".to_string(),
+                            denom_out: "y:uatom:30Sep2024".to_string(),
+                            interface: None,
+                        },
+                    ],
+                },
+                Route {
+                    offer_asset: Asset::Native(coin(3000, "ibc/uosmo")),
+                    operations: vec![
+                        SwapOperation {
+                            pool: "amm:1".to_string(),
+                            denom_in: "ibc/uosmo".to_string(),
+                            denom_out: "ibc/uusdc".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:2".to_string(),
+                            denom_in: "ibc/uusdc".to_string(),
+                            denom_out: "ibc/uatom".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:4".to_string(),
+                            denom_in: "ibc/uatom".to_string(),
+                            denom_out: "c:uatom".to_string(),
+                            interface: None,
+                        },
+                        SwapOperation {
+                            pool: "amm:3".to_string(),
+                            denom_in: "c:uatom".to_string(),
+                            denom_out: "y:uatom:30Sep2024".to_string(),
+                            interface: None,
+                        },
+                    ],
+                },
+            ],
+            include_spot_price: true,
+        },
+    );
+    assert!(res.is_ok());
+    let output: StdResult<SimulateSwapExactAssetInResponse> = from_json(res.unwrap());
+    assert!(output.is_ok());
+    let response = output.unwrap();
+    let token_out = response.asset_out;
+    assert_eq!("y:uatom:30Sep2024", token_out.denom());
+    assert_eq!(2905, token_out.amount().u128());
+
+    // weighted spot price:
+    // 2000 with 0.600000000125
+    // 3000 with 0.56842105275
+    assert_eq!(
+        Decimal::from_str("0.5810526317").unwrap(),
+        response.spot_price.unwrap()
+    );
+}
+
+fn setup_mocks() -> MockQuerier {
+    let mut querier = MockQuerier::new();
+
+    mock_stake_given_in(&mut querier, "uatom", "channel-0", "1000", "950");
+    mock_stake_given_out(&mut querier, "uatom", "channel-0", "950", "1000");
+    mock_stake_given_in(
+        &mut querier,
+        "uatom",
+        "channel-0",
+        "1000000000000000000",
+        "950000000000000000",
+    );
+
+    mock_batch_swap_given_in(
+        &mut querier,
+        vec![
+            SwapStep {
+                pool_id: 1,
+                token_in: "ibc/uosmo".to_string(),
+                token_out: "ibc/uusdc".to_string(),
+                amount: Some("2000".to_string()),
+            },
+            SwapStep {
+                pool_id: 2,
+                token_in: "ibc/uusdc".to_string(),
+                token_out: "ibc/uatom".to_string(),
+                amount: None,
+            },
+        ],
+        "1000",
+        "ibc/uatom",
+    );
+    mock_batch_swap_given_out(
+        &mut querier,
+        vec![
+            SwapStep {
+                pool_id: 2,
+                token_in: "ibc/uusdc".to_string(),
+                token_out: "ibc/uatom".to_string(),
+                amount: Some("1000".to_string()),
+            },
+            SwapStep {
+                pool_id: 1,
+                token_in: "ibc/uosmo".to_string(),
+                token_out: "ibc/uusdc".to_string(),
+                amount: None,
+            },
+        ],
+        "2000",
+        "ibc/uosmo",
+    );
+
+    mock_batch_swap_given_in(
+        &mut querier,
+        vec![SwapStep {
+            pool_id: 3,
+            token_in: "c:uatom".to_string(),
+            token_out: "y:uatom:30Sep2024".to_string(),
+            amount: Some("950".to_string()),
+        }],
+        "1200",
+        "y:uatom:30Sep2024",
+    );
+    mock_batch_swap_given_out(
+        &mut querier,
+        vec![SwapStep {
+            pool_id: 3,
+            token_in: "c:uatom".to_string(),
+            token_out: "y:uatom:30Sep2024".to_string(),
+            amount: Some("1200".to_string()),
+        }],
+        "950",
+        "c:uatom",
+    );
+
+    mock_batch_swap_given_in(
+        &mut querier,
+        vec![
+            SwapStep {
+                pool_id: 1,
+                token_in: "ibc/uosmo".to_string(),
+                token_out: "ibc/uusdc".to_string(),
+                amount: Some("3000".to_string()),
+            },
+            SwapStep {
+                pool_id: 2,
+                token_in: "ibc/uusdc".to_string(),
+                token_out: "ibc/uatom".to_string(),
+                amount: None,
+            },
+            SwapStep {
+                pool_id: 4,
+                token_in: "ibc/uatom".to_string(),
+                token_out: "c:uatom".to_string(),
+                amount: None,
+            },
+            SwapStep {
+                pool_id: 3,
+                token_in: "c:uatom".to_string(),
+                token_out: "y:uatom:30Sep2024".to_string(),
+                amount: None,
+            },
+        ],
+        "1705",
+        "ibc/y:uatom:30Sep2024",
+    );
+
+    mock_spot_price(&mut querier, 1, "ibc/uosmo", "ibc/uusdc", "0.25");
+    mock_spot_price(&mut querier, 2, "ibc/uusdc", "ibc/uatom", "2");
+    mock_spot_price(
+        &mut querier,
+        3,
+        "c:uatom",
+        "y:uatom:30Sep2024",
+        "1.263157895",
+    );
+    mock_spot_price(&mut querier, 4, "ibc/uatom", "c:uatom", "0.9");
+
+    querier
+}
+
+fn mock_stake_given_in(
+    querier: &mut MockQuerier,
+    host_chain: &str,
+    channel: &str,
+    amount_in: &str,
+    amount_out: &str,
+) {
+    querier.mock_query(
+        QuerySimulateStakeRequest {
+            host_chain: host_chain.to_string(),
+            transfer_channel: channel.to_string(),
+            amount_in: Some(amount_in.to_string()),
+            amount_out: None,
+        }
+        .into(),
+        &QuerySimulateStakeResponse {
+            amount_in: None,
+            amount_out: Some(CosmosCoin {
+                amount: amount_out.to_string(),
+                denom: format!("c:{}", host_chain),
+            }),
+            fee_amount: Some(CosmosCoin {
+                amount: "0".to_string(),
+                denom: format!("ibc/{}", host_chain),
+            }),
+        },
+    );
+}
+
+fn mock_stake_given_out(
+    querier: &mut MockQuerier,
+    host_chain: &str,
+    channel: &str,
+    amount_out: &str,
+    amount_in: &str,
+) {
+    querier.mock_query(
+        QuerySimulateStakeRequest {
+            host_chain: host_chain.to_string(),
+            transfer_channel: channel.to_string(),
+            amount_in: None,
+            amount_out: Some(amount_out.to_string()),
+        }
+        .into(),
+        &QuerySimulateStakeResponse {
+            amount_in: Some(CosmosCoin {
+                amount: amount_in.to_string(),
+                denom: format!("ibc/{}", host_chain),
+            }),
+            amount_out: None,
+            fee_amount: Some(CosmosCoin {
+                amount: "0".to_string(),
+                denom: format!("ibc/{}", host_chain),
+            }),
+        },
+    );
+}
+
+fn mock_batch_swap_given_in(
+    querier: &mut MockQuerier,
+    swap_steps: Vec<SwapStep>,
+    out_amount: &str,
+    out_denom: &str,
+) {
+    querier.mock_query(
+        QuerySimulateBatchSwapRequest {
+            swap_type: SwapType::GivenIn.into(),
+            steps: swap_steps,
+        }
+        .into(),
+        &QuerySimulateBatchSwapResponse {
+            amounts_out: vec![CosmosCoin {
+                amount: out_amount.to_string(),
+                denom: out_denom.to_string(),
+            }],
+            amounts_in: vec![],
+            swap_fee: vec![],
+            join_exit_protocol_fee: vec![],
+            swap_protocol_fee: vec![],
+        },
+    );
+}
+
+fn mock_batch_swap_given_out(
+    querier: &mut MockQuerier,
+    swap_steps: Vec<SwapStep>,
+    in_amount: &str,
+    in_denom: &str,
+) {
+    querier.mock_query(
+        QuerySimulateBatchSwapRequest {
+            swap_type: SwapType::GivenOut.into(),
+            steps: swap_steps,
+        }
+        .into(),
+        &QuerySimulateBatchSwapResponse {
+            amounts_in: vec![CosmosCoin {
+                amount: in_amount.to_string(),
+                denom: in_denom.to_string(),
+            }],
+            amounts_out: vec![],
+            swap_fee: vec![],
+            join_exit_protocol_fee: vec![],
+            swap_protocol_fee: vec![],
+        },
+    );
+}
+
+fn mock_spot_price(
+    querier: &mut MockQuerier,
+    pool_id: u64,
+    token_in: &str,
+    token_out: &str,
+    spot_price: &str,
+) {
+    querier.mock_query(
+        QuerySpotPriceRequest {
+            pool_id,
+            token_in: token_in.to_string(),
+            token_out: token_out.to_string(),
+            apply_fee: false,
+        }
+        .into(),
+        &QuerySpotPriceResponse {
+            spot_price: spot_price.to_string(),
+        },
+    );
+}