Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jup-sdk/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions jup-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "futarchy-amm-jup-sdk"
description = "SDK for interacting with Futarchy AMM, built for Jupiter"
license = "BUSL-1.1"
version = "0.1.2"
version = "0.1.3"
edition = "2024"

[dependencies]
Expand All @@ -16,5 +16,3 @@ ahash = "0.8.12"
solana-client = "2.2.1"
solana-commitment-config = "2.2.1"
solana-sdk = "2.3.1"


104 changes: 34 additions & 70 deletions jup-sdk/src/futarchy_amm.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anchor_lang::prelude::{
borsh, AccountMeta, AnchorDeserialize, AnchorSerialize, InitSpace, Pubkey,
AccountMeta, AnchorDeserialize, AnchorSerialize, InitSpace, Pubkey, borsh,
};
use anyhow::{anyhow, bail, Result};
use anyhow::{Result, anyhow, bail};

use crate::FutarchyAmmError;
use crate::{
FutarchyAmmError,
lamport::{Lamport, ToLamport},
};

// use crate::{FutarchyError, LP_TAKER_FEE_BPS, MAX_BPS, PROTOCOL_TAKER_FEE_BPS};
pub const LP_TAKER_FEE_BPS: u16 = 25;
Expand Down Expand Up @@ -185,66 +188,44 @@ impl Pool {
}

pub fn swap(&mut self, input_amount: u64, swap_type: SwapType) -> Result<u64> {
let input_amount_after_protocol_fee = (input_amount as u128)
.checked_mul((MAX_BPS - PROTOCOL_TAKER_FEE_BPS) as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?
.checked_div(MAX_BPS as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?
as u64;
let input_amount_after_protocol_fee = (input_amount.lamports()
* (MAX_BPS - PROTOCOL_TAKER_FEE_BPS).lamports()
/ MAX_BPS.lamports())?;

let k = self.k();

let (input_reserve, output_reserve) = match swap_type {
SwapType::Buy => (self.quote_reserves, self.base_reserves),
SwapType::Sell => (self.base_reserves, self.quote_reserves),
};
let (input_reserve, output_reserve) = (input_reserve.lamports(), output_reserve.lamports());

// airlifted from uniswap v1:
// https://github.com/Uniswap/v1-contracts/blob/c10c08d81d6114f694baa8bd32f555a40f6264da/contracts/uniswap_exchange.vy#L106-L111

if input_reserve == 0 || output_reserve == 0 {
if input_reserve == Lamport::ZERO || output_reserve == Lamport::ZERO {
bail!(FutarchyAmmError::InvalidReserves);
}

let input_amount_after_lp_fee = (input_amount_after_protocol_fee as u128)
.checked_mul((MAX_BPS - LP_TAKER_FEE_BPS) as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
let input_amount_after_lp_fee =
(input_amount_after_protocol_fee * (MAX_BPS - LP_TAKER_FEE_BPS).lamports())?;

let numerator = input_amount_after_lp_fee
.checked_mul(output_reserve as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
let numerator = (input_amount_after_lp_fee * output_reserve)?;

let denominator = (input_reserve as u128)
.checked_mul(MAX_BPS as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?
.checked_add(input_amount_after_lp_fee as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
let denominator = (input_reserve * MAX_BPS.lamports() + input_amount_after_lp_fee)?;

let output_amount = (numerator
.checked_div(denominator)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?)
as u64;
let output_amount = (numerator / denominator)?;

match swap_type {
SwapType::Buy => {
self.quote_reserves = self
.quote_reserves
.checked_add(input_amount_after_protocol_fee)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
self.base_reserves = self
.base_reserves
.checked_sub(output_amount)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
self.quote_reserves =
(self.quote_reserves.lamports() + input_amount_after_protocol_fee)?.val();
self.base_reserves = (self.base_reserves.lamports() - output_amount)?.val();
}
SwapType::Sell => {
self.base_reserves = self
.base_reserves
.checked_add(input_amount_after_protocol_fee)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
self.quote_reserves = self
.quote_reserves
.checked_sub(output_amount)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
self.base_reserves =
(self.base_reserves.lamports() + input_amount_after_protocol_fee)?.val();
self.quote_reserves = (self.quote_reserves.lamports() - output_amount)?.val();
}
}

Expand All @@ -254,57 +235,40 @@ impl Pool {
bail!(FutarchyAmmError::AmmInvariantViolated);
}

Ok(output_amount)
Ok(output_amount.val())
}

pub fn feeless_swap(&mut self, input_amount: u64, swap_type: SwapType) -> Result<u64> {
let k = self.k();
let input_amount = input_amount.lamports();

let (input_reserve, output_reserve) = match swap_type {
SwapType::Buy => (self.quote_reserves, self.base_reserves),
SwapType::Sell => (self.base_reserves, self.quote_reserves),
};
let (input_reserve, output_reserve) = (input_reserve.lamports(), output_reserve.lamports());

// airlifted from uniswap v1:
// https://github.com/Uniswap/v1-contracts/blob/c10c08d81d6114f694baa8bd32f555a40f6264da/contracts/uniswap_exchange.vy#L106-L111

if input_reserve == 0 || output_reserve == 0 {
if input_reserve == Lamport::ZERO || output_reserve == Lamport::ZERO {
bail!(FutarchyAmmError::InvalidReserves);
}

let numerator = (input_amount as u128)
.checked_mul(output_reserve as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
let numerator = (input_amount * output_reserve)?;

let denominator = (input_reserve as u128)
.checked_add(input_amount as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
let denominator = (input_reserve + input_amount)?;

let output_amount = (numerator
.checked_div(denominator)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?)
as u64;
let output_amount = (numerator / denominator)?;

match swap_type {
SwapType::Buy => {
self.quote_reserves = self
.quote_reserves
.checked_add(input_amount)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
self.base_reserves = self
.base_reserves
.checked_sub(output_amount)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
self.quote_reserves = (self.quote_reserves.lamports() + input_amount)?.val();
self.base_reserves = (self.base_reserves.lamports() - output_amount)?.val();
}
SwapType::Sell => {
self.base_reserves = self
.base_reserves
.checked_add(input_amount)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
self.quote_reserves = self
.quote_reserves
.checked_sub(output_amount)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?;
self.base_reserves = (self.base_reserves.lamports() + input_amount)?.val();
self.quote_reserves = (self.quote_reserves.lamports() - output_amount)?.val();
}
}

Expand All @@ -314,7 +278,7 @@ impl Pool {
bail!(FutarchyAmmError::AmmInvariantViolated);
}

Ok(output_amount)
Ok(output_amount.val())
}

pub fn simulate_swap(&self, input_amount: u64, swap_type: SwapType) -> Result<u64> {
Expand Down
114 changes: 114 additions & 0 deletions jup-sdk/src/lamport.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use crate::FutarchyAmmError;
use anyhow::anyhow;
use std::ops::{Add, Div, Mul, Sub};

/// A wrapper for lamport values that will bail on
/// any overflows.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Lamport(u128);

impl Lamport {
pub const ZERO: Self = Lamport(0);

pub const fn new(lamports: u64) -> Self {
Lamport(lamports as _)
}

pub const fn val(self) -> u64 {
self.0 as _
}
}

impl Add for Lamport {
type Output = anyhow::Result<Self>;
fn add(self, rhs: Self) -> Self::Output {
self.0
.checked_add(rhs.0)
.map(|v| Self(v))
.ok_or(anyhow!(FutarchyAmmError::MathOverflow))
}
}

impl Sub for Lamport {
type Output = anyhow::Result<Self>;
fn sub(self, rhs: Self) -> Self::Output {
self.0
.checked_sub(rhs.0)
.map(|v| Self(v))
.ok_or(anyhow!(FutarchyAmmError::MathOverflow))
}
}

impl Div for Lamport {
type Output = anyhow::Result<Self>;
fn div(self, rhs: Self) -> Self::Output {
self.0
.checked_div(rhs.0)
.map(|v| Self(v))
.ok_or(anyhow!(FutarchyAmmError::MathOverflow))
}
}

impl Mul for Lamport {
type Output = anyhow::Result<Self>;
fn mul(self, rhs: Self) -> Self::Output {
self.0
.checked_mul(rhs.0)
.map(|v| Self(v))
.ok_or(anyhow!(FutarchyAmmError::MathOverflow))
}
}

impl Div<anyhow::Result<Self>> for Lamport {
type Output = anyhow::Result<Self>;
fn div(self, rhs: anyhow::Result<Self>) -> Self::Output {
rhs.and_then(|rhs| {
self.0
.checked_div(rhs.0)
.map(|v| Self(v))
.ok_or(anyhow!(FutarchyAmmError::MathOverflow))
})
}
}

impl Mul<anyhow::Result<Self>> for Lamport {
type Output = anyhow::Result<Self>;
fn mul(self, rhs: anyhow::Result<Self>) -> Self::Output {
rhs.and_then(|rhs| {
self.0
.checked_mul(rhs.0)
.map(|v| Self(v))
.ok_or(anyhow!(FutarchyAmmError::MathOverflow))
})
}
}

impl Div<Lamport> for anyhow::Result<Lamport> {
type Output = Self;
fn div(self, rhs: Lamport) -> Self::Output {
self.and_then(|v| v / rhs)
}
}

impl Add<Lamport> for anyhow::Result<Lamport> {
type Output = Self;
fn add(self, rhs: Lamport) -> Self::Output {
self.and_then(|v| v + rhs)
}
}

pub trait ToLamport {
fn lamports(self) -> Lamport;
}

impl ToLamport for u64 {
fn lamports(self) -> Lamport {
Lamport::new(self)
}
}

impl ToLamport for u16 {
fn lamports(self) -> Lamport {
Lamport::new(self as _)
}
}
28 changes: 14 additions & 14 deletions jup-sdk/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
use anchor_lang::prelude::{AnchorDeserialize, Pubkey, Space};
pub mod futarchy_amm;
pub mod lamport;

pub use futarchy_amm::{FutarchyAmm, MAX_BPS, TAKER_FEE_BPS};

use anyhow::{Context, Result, anyhow, bail};
use crate::{
futarchy_amm::{FutarchyAmmSwap, SwapType},
lamport::ToLamport,
};
use anchor_lang::prelude::{AnchorDeserialize, Pubkey, Space};
use anyhow::{Context, Result, bail};
use jupiter_amm_interface::{
AccountMap, Amm, AmmContext, AmmProgramIdToLabel, KeyedAccount, Quote, Swap,
SwapAndAccountMetas, SwapMode, SwapParams,
};

pub mod futarchy_amm;

pub use futarchy_amm::{FutarchyAmm, MAX_BPS, TAKER_FEE_BPS};
use rust_decimal::Decimal;

use crate::futarchy_amm::{FutarchyAmmSwap, SwapType};

pub const FUTARCHY_PROGRAM_ID: Pubkey =
Pubkey::from_str_const("FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq");
pub const SPL_TOKEN_PROGRAM_ID: Pubkey =
Expand Down Expand Up @@ -41,6 +43,8 @@ impl std::fmt::Display for FutarchyAmmError {
}
}

impl std::error::Error for FutarchyAmmError {}

#[derive(Debug, Clone)]
pub struct FutarchyAmmClient {
pub dao_address: Pubkey,
Expand Down Expand Up @@ -176,12 +180,8 @@ impl Amm for FutarchyAmmClient {
let fee_pct = Decimal::new(TAKER_FEE_BPS as i64, 2);

// this isn't exact because of compounding, but should be close enough
let fee_amount = (quote_params.amount as u128)
.checked_mul(TAKER_FEE_BPS as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?
.checked_div(MAX_BPS as u128)
.ok_or_else(|| anyhow!(FutarchyAmmError::MathOverflow))?
as u64;
let fee_amount =
(quote_params.amount.lamports() * TAKER_FEE_BPS.lamports() / MAX_BPS.lamports())?.val();

Ok(Quote {
in_amount: quote_params.amount,
Expand Down
Loading