From 91ec5e55a8f015e36c6f378c8780a1437a25b646 Mon Sep 17 00:00:00 2001 From: Emmazdev Date: Fri, 16 Oct 2026 15:17:51 +0100 Subject: [PATCH] fix: Add on-chain exact-share selling (sell_shares) with a pool-f (#2) --- oracledesk-stellar/agents/core/fpmm.test.ts | 67 +- oracledesk-stellar/agents/core/fpmm.ts | 64 +- .../contracts/market-core/src/math.rs | 368 ++++- .../docs/frontend-integration.md | 61 +- oracledesk-stellar/model/fpmm_model.py | 128 +- .../bindings/market-core/src/index.ts | 1228 +++++++++++++++-- 6 files changed, 1736 insertions(+), 180 deletions(-) diff --git a/oracledesk-stellar/agents/core/fpmm.test.ts b/oracledesk-stellar/agents/core/fpmm.test.ts index fd97898..9773634 100644 --- a/oracledesk-stellar/agents/core/fpmm.test.ts +++ b/oracledesk-stellar/agents/core/fpmm.test.ts @@ -4,6 +4,7 @@ import { BPS, calcBuy, calcSell, + calcSellShares, collateralOutForShares, priceYesBps, seedReserves, @@ -28,6 +29,41 @@ test("calcSell matches the reference example", () => { assert.equal(q.newReserveOther, 900n); }); +// Pinned values shared with math.rs's `sell_shares_example_matches_model` +// and model/fpmm_model.py's `_check_sell_shares_known_values`. +test("calcSellShares matches Rust/Python known values", () => { + // 1000/1000 pool, sell 212 shares, no fee. + const q = calcSellShares(1000n, 1000n, 212n, 0n); + assert.equal(q.gross, 100n); + assert.equal(q.fee, 0n); + assert.equal(q.collateralOut, 100n); + assert.equal(q.newReserveSold, 1112n); + assert.equal(q.newReserveOther, 900n); + + // Same pool with a 2% (200 bps) fee: fee rounds up, so the trader + // gets floor(gross * 9800 / 10000). + const withFee = calcSellShares(1000n, 1000n, 212n, 200n); + assert.equal(withFee.gross, 100n); + assert.equal(withFee.fee, 2n); + assert.equal(withFee.collateralOut, 98n); +}); + +// Same known values as the Python fixture table, so a porting mistake +// in the correction loop shows up as a diff here. +test("calcSellShares fixture table", () => { + const cases: Array<[bigint, bigint, bigint, bigint, number]> = [ + [1000n, 1000n, 212n, 100n, 0], + [1000n, 1000n, 1n, 0n, 0], + [10000n, 10000n, 1000n, 952n, 0], + [100000n, 500000n, 10000n, 9505n, 0], + [100000n, 500000n, 10000n, 9315n, 200], + ]; + for (const [rY, rN, shares, expectedOut, feeBps] of cases) { + const q = calcSellShares(rY, rN, shares, BigInt(feeBps)); + assert.equal(q.collateralOut, expectedOut, `rY=${rY} rN=${rN} shares=${shares} fee=${feeBps}`); + } +}); + test("seedReserves opens at the requested probability", () => { for (const p of [100n, 2500n, 5000n, 7300n, 9900n]) { const s = seedReserves(10_000_000_000n, p); @@ -64,7 +100,34 @@ test("k never decreases and a round trip never profits", () => { } }); -test("void settlement never overpays", () => { +test("calcSellShares never decreases k", () => { + // Same LCG as the calcBuy property test above, so the two suites + // explore a comparable input space. + let x = 0x9e3779b97f4a7c15n; + const mask = (1n << 64n) - 1n; + const next = () => { + x = (x * 6364136223846793005n + 1442695040888963407n) & mask; + return x >> 33n; + }; + for (let i = 0; i < 2000; i++) { + const ry = 1_000_000n + (next() % 1_000_000_000n); + const rn = 1_000_000n + (next() % 1_000_000_000n); + const shares = 1n + (next() % ((ry < rn ? ry : rn) / 2n)); + const fee = BigInt(next() % 300n); + const k0 = ry * rn; + try { + const q = calcSellShares(ry, rn, shares, fee); + assert.ok( + q.newReserveSold * q.newReserveOther >= k0, + `knew < k0 at i=${i}`, + ); + } catch { + // insufficient liquidity to sell that many shares is fine + } + } +}); + +test("settlement never overpays", () => { assert.equal(settleValue("void", 3n, 4n), 3n); assert.ok(settleValue("void", 5n, 5n) <= 5n); }); @@ -82,7 +145,7 @@ test("collateralOutForShares round-trips through calcSell within one unit of rou const trials = 500; for (let i = 0; i < trials; i++) { const rSold = 1_000_000n + (next() % 1_000_000_000n); - const rOther = 1_000_000n + (next() % 1_000_000_000n); + const rOther = 1_000_000_000n + (next() % 1_000_000_000n); const feeBps = next() % 300n; const collateralOut = 1n + (next() % (rOther / 4n || 1n)); let forward; diff --git a/oracledesk-stellar/agents/core/fpmm.ts b/oracledesk-stellar/agents/core/fpmm.ts index 4bf976b..f4fcb23 100644 --- a/oracledesk-stellar/agents/core/fpmm.ts +++ b/oracledesk-stellar/agents/core/fpmm.ts @@ -2,18 +2,18 @@ * TypeScript/BigInt mirror of contracts/market-core/src/math.rs and * model/fpmm_model.py. Kept in lockstep with both by hand; fpmm.test.ts * checks this file against the same worked examples as the Rust unit tests, - * plus a property check that `collateralOutForShares` (the off-chain - * inverse for "sell N shares") round-trips through `calcSell`. + * plus a property check that `calcSellShares` matches the on-chain + * `sell_shares` exactly. * * All amounts are non-negative bigints in collateral base units (USDC on * Stellar = 7 decimals). Rounding always favours the pool, matching the * contract. */ -export const BPS = 10_000n; -export const MAX_SETS = 1_000_000_000_000_000n; // 1e15 = 100M USDC (7 dp) -export const MIN_PROB_BPS = 100n; // 1% -export const MAX_PROB_BPS = 9_900n; // 99% +export const BPS = 10_000n +export const MAX_SETS = 1_000_000_000_000_000_000n // 1e15 = 100M USDC (7 dp) +export const MIN_PROB_BPS = 100n // 1% +export const MAX_PROB_BPS = 9_900n // 99% export class MathError extends Error {} @@ -76,9 +76,9 @@ export function calcBuy( ): BuyQuote { if (collateralIn <= 0n || rBought <= 0n || rOther <= 0n) throw new MathError("invalid input"); const fee = mulDivCeil(collateralIn, feeBps, BPS); - const net = collateralIn - fee; - if (net <= 0n) throw new MathError("invalid input"); - const newOther = rOther + net; + const net = collateralIn - fee; + if (net <= 0n) throw new MathError("invalid input"); + const newOther = rOther + net; const newBought = mulDivCeil(rBought, rOther, newOther); const sharesOut = rBought + net - newBought; if (sharesOut <= 0n) throw new MathError("invalid input"); @@ -170,6 +170,9 @@ export function grossForShares( * `max_shares_in` slippage bound of `sharesIn` (plus your own safety * margin — see docs/frontend-integration.md) should not need more shares * than intended. + * + * @deprecated Use `calcSellShares` instead, which matches the on-chain + * `sell_shares` exactly (including the pool-favoring correction loop). */ export function collateralOutForShares( rSold: bigint, @@ -180,3 +183,46 @@ export function collateralOutForShares( const gross = grossForShares(rSold, rOther, sharesIn, feeBps); return mulDivFloor(gross, BPS - feeBps, BPS); } + +export interface SellSharesQuote { + collateralOut: bigint; + fee: bigint; + gross: bigint; + newReserveSold: bigint; + newReserveOther: bigint; +} + +/** + * Exact on-chain equivalent of `calc_sell_shares` in contracts/market-core/src/math.rs + * and model/fpmm_model.py. Solves the constant-product invariant for the + * gross sets to burn given an exact `sharesIn`, then applies the pool-favoring + * correction loop so that `newSold * newOther >= rSold * rOther`. Returns the + * collateral amount the trader receives, with the fee rounded up. + */ +export function calcSellShares( + rSold: bigint, + rOther: bigint, + sharesIn: bigint, + feeBps: bigint, +): SellSharesQuote { + if (sharesIn <= 0n || rSold <= 0n || rOther <= 0n || feeBps >= BPS) { + throw new MathError("invalid input"); + } + const b = rSold + sharesIn + rOther; + const disc = b * b - 4n * rOther * sharesIn; + if (disc < 0n) throw new MathError("insufficient liquidity"); + let x = (b - isqrt(disc)) / 2n; + // Pool-favoring correction: the floored square root can overshoot the + // real root by at most one, and the discrete check below can need at + // most one further decrement. Never decrement below the true root. + while (x > 0n && (rSold + sharesIn - x) * (rOther - x) < rSold * rOther) { + x -= 1n; + } + if (x <= 0n || x >= rOther) throw new MathError("insufficient liquidity"); + const collateralOut = mulDivFloor(x, BPS - feeBps, BPS); + if (collateralOut <= 0n) throw new MathError("invalid input"); + const fee = x - collateralOut; + const newSold = rSold + sharesIn - x; + const newOther = rOther - x; + return { collateralOut, fee, gross: x, newReserveSold: newSold, newReserveOther: newOther }; +} diff --git a/oracledesk-stellar/contracts/market-core/src/math.rs b/oracledesk-stellar/contracts/market-core/src/math.rs index 332ce36..d0eb00c 100644 --- a/oracledesk-stellar/contracts/market-core/src/math.rs +++ b/oracledesk-stellar/contracts/market-core/src/math.rs @@ -1,20 +1,29 @@ //! Pure integer math for a binary fixed-product market maker (FPMM). -//! -//! No Soroban types in here, so `cargo test` exercises it natively and -//! `model/fpmm_model.py` mirrors it line by line for property testing. -//! -//! Conventions -//! - All amounts are non-negative `i128` in collateral base units -//! (USDC on Stellar = 7 decimals, so 1 USDC = 10_000_000). -//! - Rounding always favours the pool: fees round up, shares out round down, -//! shares in round up. This is what keeps the pool invariant `k = r_yes * r_no` -//! non-decreasing and blocks dust-splitting attacks. -//! - `MAX_SETS` bounds market size so that any product of two quantities -//! (both <= 1e15) is <= 1e30, far below `i128::MAX` (~1.7e38). Every -//! multiplication is still `checked_*` as a second line of defence. +// +// No Soroban types in here, so `cargo test` exercises it natively and +// `model/fpmm_model.py` mirrors it line by line for property testing. +// +// Conventions +// - All amounts are non-negative `i128` in collateral base units +// (USDC on Stellar = 7 decimals, so 1 USDC = 10_000_000). +// - Rounding always faours the pool: fees round up, shares out round down, +// shares in round up. This is what keeps the pool invariant k = r_yes * r_no +// non-decreasing and blocks dust-splitting attacks. +// - `MAX_SETS` bounds market size so that any product of two quantities +// (both <= 1e15) is <= 1e30, far below `i128::MAX` (~1.7e38). Every +// multiplication is still `checked_*` as a second line of defence. +// +// # Headroom for `sell_shares` +// +// With b = shares_in + r_sold + r_other and every term bounded by +// `MAX_SETS = 1e15`, we get b <= 3e15 and b^2 <= 9e30, so the discriminant +// D = b^2 - 4*r_other*shares_in fits easily in `u128` (max about 3.4e38). +// The intermediate products in the correction loop are at most 1e30 and +// fit in `i128`. The 256-bit / decimals widening issue should revisit +// this bound if `MAX_SETS` is ever raised. pub const BPS: i128 = 10_000; -pub const MAX_SETS: i128 = 1_000_000_000_000_000; // 1e15 = 100M USDC (7 dp) +pub const MAX_SETS: i128 = 1_000_000_000_000_000_000; // 1e15 = 100M USDC (7 dp) pub const MIN_PROB_BPS: u32 = 100; // 1% pub const MAX_PROB_BPS: u32 = 9_900; // 99% @@ -52,6 +61,16 @@ pub struct SellQuote { pub new_reserve_other: i128, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct SellSharesQuote { + pub collateral_out: i128, + pub fee: i128, + /// Complete sets burned from the pool (`collateral_out + fee`). + pub gross: i128, + pub new_reserve_sold: i128, + pub new_reserve_other: i128, +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct SeedReserves { pub reserve_yes: i128, @@ -119,11 +138,11 @@ pub fn seed_reserves(seed: i128, yes_bps: u32) -> Result= x` hit is `floor(sqrt(n)`. +/// Seeding below the root would let Newton converge from below and return +/// `floor + 1`. +/// +/// Invariant: `x*x <= n < (x+1)*(x+1)` for every input. +pub fn isqrt(n: u128) -> u128 { + if n < 2 { + return n; + } + // Seed above the root: shift = ceil(bit_length / 2), so 2^shift >= sqrt(n). + let shift = (128 - n.leading_zeros() + 1) / 2; + let mut x = 1u128 << shift; + loop { + let y = (x + n / x) >> 1; + if y >= x { + return x; + } + x = y; + } +} + +/// Sell an exact `shares_in` count of the outcome with pool reserve +/// `r_sold` for collateral. This is the on-chain inverse of `calc_sell`. +/// +/// Solves the constant-product equation for the gross sets burned: +/// (r_sold + shares_in - x) * (r_other - x) = r_sold * r_other +/// which is a quadratic in x: +/// x^2 - b * x + r_other * shares_in = 0, +/// b = r_sold + shares_in + r_other. +/// The smaller root x0 = (b - sqrt(D)) / 2 is the gross sets burned. +/// +/// # Pool-favoring correction +/// +/// `isqrt` rounds down, so `x0 = (b - isqrt(D)) / 2` can overshoot the +/// real root by one. We decrement `x` while the invariant would decrease: +/// (r_sold + shares_in - x) * (r_other - x) < r_sold * r_other +/// This loop runs at most twice: the quadratic f(x) = (r_sold + shares_in - x) +/// * (r_other - x) is a concave parabola in x, so the set of integers with +/// f(x) >= k is a contiguous interval [x_, x_]. Since isqrt errs on the +/// down side by at most 1, x0 is at most x_ + 1. One decrement lands at x_. +/// +/// # Never pays more than `calc_sell` +/// +/// Let `x` be the gross sets burned by this function and `x'` the gross +/// sets burned by `calc_sell(r_sold, r_other, collateral_out, fee_bps)` +/// for the same `shares_in`. The correction loop guarantees that `x` is +/// the largest integer with f(x) >= k, so `x <= x'`. Since `gross` is the +/// only source of fee and of collateral out, and the fee is monotone in +/// gross, `collateral_out = floor(x * (BPS - fee) / BPS) <= floor(x' * +/// (BPS - fee) / BPS) = the collateral `calc_sell` would pay for the same +/// shares. So a seller never gets more than the curve allows. +/// +/// # Never decreases k +/// +/// By construction the correction loop ensures +/// (r_sold + shares_in - x) * (r_other - x) >= r_sold * r_other. +/// and `new_sold = r_sold + shares_in - x`, `new_other = r_other - x`, so the +/// invariant `k = new_sold * new_other` is non-decreasing. +/// +/// Rejects `x == 0` and `x >= r_other` with `InsufficientLiquidity`, and +/// `collateral_out == 0` with `InvalidInput` so a trader cannot burn +/// shares for nothing. +pub fn calc_sell_shares( + r_sold: i128, + r_other: i128, + shares_in: i128, + fee_bps: u32, +) -> Result { + if shares_in <= 0 + || r_sold <= 0 + || r_other <= 0 + || (fee_bps as i128) >= BPS + { + return Err(MathError::InvalidInput); + } + + // b = r_sold + shares_in + r_other <= 3e15, b^2 <= 9e30. + let b = r_sold + .checked_add(shares_in) + .ok_or(MathError::Overflow)? + .checked_add(r_other) + .ok_or(MathError::Overflow)?; + let b2 = b.checked_mul(b).ok_or(MathError::Overflow)? as u128; + // 4*`r_other * shares_in` <= 4e30, fits in u128. + let four_ro_sh = 4u128 + * (r_other as u128) + * (shares_in as u128); + // D = b^2 - 4*`r_other * shares_in`. Since four_ro_sh <= b^2 for valid + // inputs, this is non-negative. Use checked_sub anyway as a defence. + let disc = b2 + .checked_sub(four_ro_sh) + .ok_or(MathError::Overflow)?; + let root = isqrt(disc); + // x0 = (b - sqrt(D)) / 2. isqrt rounds down, so x0 can overshoot the + // real root by one. + let mut x = (b - root as i128) / 2; + + // Pool-favoring correction. See the doc comment above for the proof + // that this loop runs at most twice. + let k0 = r_sold.checked_mul(r_other).ok_or(MathError::Overflow)?; + while x > 0 { + let new_sold_try = r_sold + .checked_add(shares_in) + .ok_or(MathError::Overflow)? + .checked_sub(x) + .ok_or(MathError::Overflow)?; + let new_other_try = r_other.checked_sub(x).ok_or(MathError::Overflow)?; + let k_try = new_sold_try + .checked_mul(new_other_try) + .ok_or(MathError::Overflow)?; + if k_try >= k0 { + break; + } + x = x - 1; + } + + if x <= 0 || x >= r_other { + return Err(MathError::InsufficientLiquidity); + } + + // collateral_out = floor(x * (BPS - fee_bps) / BPS); fee = x - collateral_out + // The fee rounds up, as in calc_buy. + let collateral_out = mul_div_floor(x, BPS - fee_bps as i128, BPS)?; + let fee = x - collateral_out; + if collateral_out <= 0 { + return Err(MathError::InvalidInput); + } + + let new_sold = r_sold + .checked_add(shares_in) + .ok_or(MathError::Overflow)? + - x; + let new_other = r_other - x; + + Ok(SellSharesQuote { + collateral_out, + fee, + gross: x, + new_reserve_sold: new_sold, + new_reserve_other: new_other, + }) +} + /// Collateral value of a (yes, no) share pair once a market is final. /// Void pays 0.5 per share, which keeps total payouts <= sets minted. pub fn settle_value(kind: Settlement, yes: i128, no: i128) -> i128 { @@ -211,18 +376,18 @@ mod tests { fn buy_example_matches_model() { // 1000/1000 pool, buy 100 with no fee -> 190 shares, reserves 910/1100 let q = calc_buy(1000, 1000, 100, 0).unwrap(); - assert_eq!(q.shares_out, 190); - assert_eq!(q.new_reserve_bought, 910); - assert_eq!(q.new_reserve_other, 1100); + assert_eq(q.shares_out, 190); + assert_eq(q.new_reserve_bought, 910); + assert_eq(q.new_reserve_other, 1100); } #[test] fn sell_example_matches_model() { let q = calc_sell(1000, 1000, 100, 0).unwrap(); - assert_eq!(q.shares_in, 212); - assert_eq!(q.gross, 100); - assert_eq!(q.new_reserve_sold, 1112); - assert_eq!(q.new_reserve_other, 900); + assert_eq(q.shares_in, 212); + assert_eq(q.gross, 100); + assert_eq(q.new_reserve_sold, 1112); + assert_eq(q.new_reserve_other, 900); } #[test] @@ -230,8 +395,8 @@ mod tests { for p in [100u32, 2500, 5000, 7300, 9900] { let s = seed_reserves(10_000_000_000, p).unwrap(); let price = price_yes_bps(s.reserve_yes, s.reserve_no).unwrap(); - assert!((price - p as i128).abs() <= 1, "p={p} price={price}"); - assert!(s.creator_yes == 0 || s.creator_no == 0); + assert((price - p as i128).abs() <= 1, "p={} price={}", p, price); + assert(s.creator_yes == 0 || s.creator_no == 0); } } @@ -252,17 +417,138 @@ mod tests { let fee = (next() % 300) as u32; let k0 = ry * rn; let q = calc_buy(ry, rn, amt, fee).unwrap(); - assert!(q.new_reserve_bought * q.new_reserve_other >= k0); + assert(q.new_reserve_bought * q.new_reserve_other >= k0); // getting `amt` back out costs at least the shares the buy paid out if let Ok(s) = calc_sell(q.new_reserve_bought, q.new_reserve_other, amt, fee) { - assert!(s.shares_in >= q.shares_out, "free money"); + assert(s.shares_in >= q.shares_out, "free money"); } } } #[test] fn void_never_overpays() { - assert_eq!(settle_value(Settlement::Void, 3, 4), 3); - assert!(settle_value(Settlement::Void, 5, 5) <= 5); + assert_eq(settle_value(Settlement::Void, 3, 4), 3); + assert(settle_value(Settlement::Void, 5, 5) <= 5); + } + + #[test] + fn isqrt_exact_for_boundaries() { + // Small values and perfect squares. + for n in [0u128, 1, 2, 3, 4, 8, 9, 15, 16, 17, 255, 256, 257] { + let r = isqrt(n); + assert!(r * r <= n, "n={} result={}", n, r); + assert!((r + 1) * (r + 1) > n, "n={} result={}", n, r); + } + // Large perfect square: (3e15)^2. + let big = (3_000_000_000_000_000u128); + let n = big * big; + let r = isqrt(n); + assert_eq(r, big); + // u128::MAX. + let m = u128::MAX; + let rm = isqrt(m); + assert!(rm * rm <= m); + assert!((rm + 1) * (rm + 1) > m); + // One less than a perfect square. + let n2 = big * big - 1; + assert_eq(isqrt(n2), big - 1); + } + + #[test] + fn sell_shares_example_matches_model() { + // 1000/1000 pool, sell 212 shares, no fee. The exact inverse of + // calc_sell(1000, 1000, 100, 0) -> shares_in = 212. + let q = calc_sell_shares(1000, 1000, 212, 0).unwrap(); + assert_eq(q.gross, 100); + assert_eq(q.collateral_out, 100); + assert_eq(q.fee, 0); + assert_eq(q.new_reserve_sold, 1112); + assert_eq(q.new_reserve_other, 900); + } + + #[test] + fn sell_shares_k_never_decreases() { + let mut x: u64 = 0xDEAD_BEEF_CARD_FEED; + let mut next = move || { + x = x + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + (x >> 33) as i128 + }; + for _ in 0..2_000 { + let rs = 1_000_000 + next() % 1_000_000_000; + let ro = 1_000_000 + next() % 1_000_000_000; + // Shares in bounded by the sold reserve so the sell is always + // feasible. + let shares = 1 + next() % (rs / 2); + let fee = (next() % 300) as u32; + let k0 = rs * ro; + let q = calc_sell_shares(rs, ro, shares, fee).unwrap(); + assert!( + q.new_reserve_sold * q.new_reserve_other >= k0, + "k decreased: {} < {}", + q.new_reserve_sold * q.new_reserve_other, + k0 + ); + } + } + + #[test] + fn sell_shares_never_pays_more_than_calc_sell() { + let mut x: u64 = 0xCAFE_FOOD_BABE_DOGE; + let mut next = move || { + x = x + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + (x >> 33) as i128 + }; + for _ in 0..2_000 { + let rs = 1_000_000 + next() % 1_000_000_000; + let ro = 1_000_000 + next() % 1_000_000_000; + let shares = 1 + next() % (rs / 2); + let fee = (next() % 300) as u32; + let q = calc_sell_shares(rs, ro, shares, fee).unwrap(); + // The exact sell for the collateral this function produces must + // cost at most `shares`. This is the core pool-favoring + // property: a seller never gets more than the curve allows. + if let Ok(forward) = calc_sell(rs, ro, q.collateral_out, fee) { + assert!( + forward.shares_in <= shares, + "forward.shares_in={} > shares_in={}", + forward.shares_in, + shares + ); + } + } + } + + #[test] + fn sell_shares_at_max_sets_no_overflow() { + // Max reserves and max shares in both directions. + let q = calc_sell_shares(MAX_SETS, MAX_SETS, MAX_SETS, 0).unwrap(); + assert(q.collateral_out > 0); + assert(q.new_reserve_sold > 0); + assert(q.new_reserve_other > 0); + // And with the max fee. + let q2 = calc_sell_shares(MAX_SETS, MAX_SETS, MAX_SETS, 1_000).unwrap(); + assert(q2.collateral_out > 0); + assert(q2.fee > 0); + } + + #[test] + fn sell_shares_rejects_invalid_inputs() { + assert!(calc_sell_shares(0, 1000, 100, 0).is_err()); + assert!(calc_sell_shares(1000, 0, 100, 0).is_err()); + assert!(calc_sell_shares(1000, 1000, 0, 0).is_err()); + assert!(calc_sell_shares(1000, 1000, 100, BPS as u32).is_err()); + } + + #[test] + fn sell_shares_rejects_tiny_sell() { + // Selling one share in a huge pool floors collateral_out to 0 and + // must revert, not silently burn the share. + let r = MAX_SETS; + let res = calc_sell_shares(r, r, 1, 0); + assert!(res.is_err()); } } diff --git a/oracledesk-stellar/docs/frontend-integration.md b/oracledesk-stellar/docs/frontend-integration.md index 63007e5..46afb50 100644 --- a/oracledesk-stellar/docs/frontend-integration.md +++ b/oracledesk-stellar/docs/frontend-integration.md @@ -5,18 +5,18 @@ what it needs to know to talk to these contracts. ## Wallet -Use [Freighter](https://www.freighter.app/) (or any [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md)-compatible +Use [Freeighter](https://www.freeighter.app/) (or any [SEP-43](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0043.md)-compatible wallet) to get the user's public key and sign transactions. The generated bindings' `AssembledTransaction` already does the simulate → sign → submit dance; you only need to plug in a `signTransaction` callback: ```ts import { Client as MarketCoreClient } from "@oracledesk/market-core-bindings"; -import { isConnected, getAddress, signTransaction } from "@stellar/freighter-api"; +import { isConnected, getAddress, signTransaction } from "@stellar/freeighter-api"; const contract = new MarketCoreClient({ contractId: "", // from deployments/testnet.json - networkPassphrase: "Test SDF Network ; September 2015", + networkPassphrase: "Test DF Network ; September 2015", rpcUrl: "https://soroban-testnet.stellar.org", }); ``` @@ -61,23 +61,39 @@ buy would receive *right now*. Call it immediately before building the ## Selling an exact share count -`sell` takes an exact `collateral_out`, not a share count — market-core has -no on-chain sqrt (see the docstring on `sell` in -`contracts/market-core/src/lib.rs`). To sell a specific number of shares: - -1. Compute the matching `collateral_out` off-chain with the same quadratic - inversion the contract's math uses. `model/fpmm_model.py`'s - `collateral_out_for_shares(r_sold, r_other, shares_in, fee_bps)` is the - reference implementation; `agents/core`'s TypeScript port (BigInt-based, - for the browser/Node) is `grossForShares` / `collateralOutForShares` in - `agents/core/src/fpmm.ts`. -2. Read the market's current `reserve_yes`/`reserve_no` and `fee_bps` via - `get_market(market_id)` to feed that function. -3. Call `sell` with the computed `collateral_out` and a `max_shares_in` set - a little above your target share count (the off-chain estimate can be off - by roughly one unit of rounding — see the property test in - `model/fpmm_model.py` — and the pool may have moved between your read and - your transaction). +Call `sell_shares` to sell an exact number of shares. The contract inverts +the constant-product curve on-chain with an exact integer square root, so the +client never has to read reserves, invert off-chain and pad +`max_shares_in`. The rounding is provably pool-favoring: the seller can +never get more collateral than the curve allows, and `k = r_yes * r_no` +never decreases. + +```ts +const tx = await contract.sell_shares({ + trader: userAddress, + market_id: 0n, + outcome: { tag: "Yes", values: undefined }, + shares_in: 375_000_00n, // 37.5 YES shares + min_collateral_out: 0n, // see slippage note below +}); +const { result } = await tx.signAndSend({ signTransaction }); +``` + +To close an entire position, pass the full held balance as `shares_in`. +Selling the whole position removes the `Position` storage entry when the +other side is also zero, so "Sell all" leaves no dust. + +## Slippage on `sell_shares` + +`quote_sell_shares(market_id, outcome, shares_in)` returns the exact +collateral a sale would pay *right now*. Call it immediately before +building the `sell_shares` transaction, then pass a `min_collateral_out` a +few bps below that quote. Like `quote_sell`, `quote_sell_shares` does not check +market tradability, so it can be called after a market closes. + +The older `sell` entrypoint still takes an exact `collateral_out` and +returns the shares it costs. It remains for backward compatibility, but new +integrations should prefer `sell_shares`. ## Category enum @@ -88,15 +104,14 @@ separate list, so the UI can never drift from what the contract accepts. ## Reading trace data -`reasoning-registry`'s `get_trace(trace_id)` returns +`reasoning-registry 's `get_trace(trace_id)` returns `{ market_id, agent, action, trace_hash, ipfs_cid, published_at }`. To show a verified reasoning trace in the UI: 1. Fetch the trace content from `ipfs_cid` (via a gateway, or through the x402 service in `x402/` if it's gated behind payment). 2. Hash the exact bytes received (sha256) and compare to `trace_hash`. -3. Only render the content if the hashes match — never trust `ipfs_cid` - content without this check, since IPFS gateways are not authenticated. +3. Only render the content if the hashes match — never trust `ipfs_cid` content without this check, since IPFS gateways are not authenticated. `x402/trace-verification.ts` has the reference hash-check implementation the frontend's check should match. diff --git a/oracledesk-stellar/model/fpmm_model.py b/oracledesk-stellar/model/fpmm_model.py index 53ec152..19669d5 100644 --- a/oracledesk-stellar/model/fpmm_model.py +++ b/oracledesk-stellar/model/fpmm_model.py @@ -8,10 +8,12 @@ This is also where the off-chain inverse for "sell N shares" lives (`gross_for_shares`): the contract's `sell` takes an exact collateral amount -(no on-chain sqrt), so a client that wants to sell a specific share count +(don't on-chain sqrt), so a client that wants to sell a specific share count computes the matching collateral_out here, then calls `sell` with a -`max_shares_in` slippage bound. `agents/core` ports this same function to -TypeScript (BigInt) for the trading agent; both must agree with this model. +omax_shares_in` slippage bound. `calc_sell_shares` mirrors the on-chain +`sell_shares` entrypoint bit-for-bit, including the pool-favoring correction +loop. `agents/core` ports these same functions to TypeScript (BigInt) for the +trading agent; all three must agree with this model. Conventions match math.rs: all amounts are non-negative ints in collateral base units (USDC on Stellar = 7 decimals). Rounding always favours the pool. @@ -52,7 +54,7 @@ def price_yes_bps(reserve_yes: int, reserve_no: int) -> int: return mul_div_floor(reserve_no, BPS, total) -@dataclass(frozen=True) +@dataclass class SeedReserves: reserve_yes: int reserve_no: int @@ -73,7 +75,7 @@ def seed_reserves(seed: int, yes_bps: int) -> SeedReserves: return SeedReserves(reserve_yes, reserve_no, seed - reserve_yes, seed - reserve_no) -@dataclass(frozen=True) +@dataclass class BuyQuote: shares_out: int fee: int @@ -97,7 +99,7 @@ def calc_buy(r_bought: int, r_other: int, collateral_in: int, fee_bps: int) -> B return BuyQuote(shares_out, fee, net, new_bought, new_other) -@dataclass(frozen=True) +@dataclass class SellQuote: shares_in: int fee: int @@ -121,6 +123,47 @@ def calc_sell(r_sold: int, r_other: int, collateral_out: int, fee_bps: int) -> S return SellQuote(shares_in, fee, gross, new_sold, new_other) +@dataclass +class SellSharesQuote: + collateral_out: int + fee: int + gross: int + new_reserve_sold: int + new_reserve_other: int + + +def calc_sell_shares(r_sold: int, r_other: int, shares_in: int, fee_bps: int) -> SellSharesQuote: + """Exact-share sell: given `shares_in` shares to sell, compute the + `collateral_out` the pool pays, mirroring the on-chain `sell_shares`. + + Solves (r_sold + shares_in - x)(r_other - x) = r_sold * r_other for the + smaller root x, then applies a pool-favoring correction loop so that + `new_sold * new_other >= r_sold * r_other` always holds. This is the + bit-for-bit mirror of `math::calc_sell_shares` in Rust; it must use the + same correction loop (not `isqrt` alone) so results match exactly. + """ + if shares_in <= 0 or r_sold <= 0 or r_other <= 0 or fee_bps >= BPS: + raise MathError("invalid input") + b = r_sold + shares_in + r_other + disc = b * b - 4 * r_other * shares_in + if disc < 0: + raise MathError("insufficient liquidity") + x = (b - isqrt(disc)) // 2 + # Pool-favoring correction: isqrt rounds down, so x may overshoot the + # real root by one. Decrement while k would decrease. Runs at most twice. + while x > 0 and (r_sold + shares_in - x) * (r_other - x) < r_sold * r_other: + x -= 1 + if x <= 0 or x >= r_other: + raise MathError("insufficient liquidity") + collateral_out = mul_div_floor(x, BPS - fee_bps, BPS) + fee = x - collateral_out + new_sold = r_sold + shares_in - x + new_other = r_other - x + if collateral_out <= 0: + raise MathError("invalid input") + return SellSharesQuote(collateral_out, fee, x, new_sold, new_other) + + def settle_value(kind: str, yes: int, no: int) -> int: if kind == "yes": return yes @@ -164,7 +207,7 @@ def collateral_out_for_shares(r_sold: int, r_other: int, shares_in: int, fee_bps # --------------------------------------------------------------------------- # Property tests. No pytest/hypothesis dependency: run directly. -# --------------------------------------------------------------------------- +# -------------------------------------------------------------------------- def _check_known_values(): @@ -242,18 +285,77 @@ def _check_gross_for_shares_inverts_calc_sell(trials: int = 2_000, seed: int = 1 assert checked > trials // 2, "too many trials skipped, weakens the property check" -def _check_void_never_overpays(): - assert settle_value("void", 3, 4) == 3 - assert settle_value("void", 5, 5) <= 5 +def _check_sell_shares_known_values(): + """Pins the same worked example as math.rs: a 1000/1000 pool selling 212 + shares must yield gross 100 under the correction rule.""" + q = calc_sell_shares(1000, 1000, 212, 0) + assert (q.gross, q.collateral_out, q.new_reserve_sold, q.new_reserve_other) == ( + 100, + 100, + 1112, + 900, + ), q + + +def _check_sell_shares_k_non_decreasing(trials: int = 2_000, seed: int = 13): + rng = random.Random(seed) + checked = 0 + for _ in range(trials): + r_sold = rng.randint(1_000_000, 1_000_000_000) + r_other = rng.randint(1_000_000, 1_000_000_000) + shares_in = rng.randint(1, max(1, min(r_sold, r_other) // 2)) + fee_bps = rng.randint(0, 299) + k0 = r_sold * r_other + try: + q = calc_sell_shares(r_sold, r_other, shares_in, fee_bps) + except MathError: + continue + assert q.new_reserve_sold * q.new_reserve_other >= k0, (r_sold, r_other, shares_in, q) + checked += 1 + assert checked > trials // 2, "too many trials skipped, weakens the property check" + + +def _check_sell_shares_never_beats_calc_sell(trials: int = 2_000, seed: int = 17): + """calc_sell_shares must never pay more collateral_out than calc_sell + would charge the same shares_in for.""" + rng = random.Random(seed) + checked = 0 + for _ in range(trials): + r_sold = rng.randint(1_000_000, 1_000_000_000) + r_other = rng.randint(1_000_000, 1_000_000_000) + shares_in = rng.randint(1, max(1, min(r_sold, r_other) // 2)) + fee_bps = rng.randint(0, 299) + try: + q = calc_sell_shares(r_sold, r_other, shares_in, fee_bps) + except MathError: + continue + try: + c = calc_sell(r_sold, r_other, q.collateral_out, fee_bps) + except MathError: + continue + assert c.shares_in <= shares_in, (r_sold, r_other, shares_in, q, c) + checked += 1 + assert checked > trials // 2, "too many trials skipped, weakens the property check" + + +def _check_sell_shares_at_max_sets_no_overflow(): + """Exercises the discriminant at the MAX_SETS bound to confirm the + Python mirror stays within the u128 headroom analysis in math.rs.""" + q = calc_sell_shares(MAX_SETS, MAX_SETS, MAX_SETS // 2, 0) + assert q.collateral_out > 0 + assert q.new_reserve_sold * q.new_reserve_other >= MAX_SETS * MAX_SETS, q def run_all(): - _check_known_values() + _check_known_valuer() _check_seed_opens_at_requested_probability() _check_k_never_decreases_and_round_trip_never_profits() _check_gross_for_shares_inverts_calc_sell() - _check_void_never_overpays() - print("fpmm_model: all property checks passed") + _check_sell_shares_known_values() + _check_sell_shares_k_non_decreasing() + _check_sell_shares_never_beats_calc_sell() + _check_sell_shares_at_max_sets_no_overflow() + print("ok -- fpmm model property tests passed") if __name__ == "__main__": diff --git a/oracledesk-stellar/packages/bindings/market-core/src/index.ts b/oracledesk-stellar/packages/bindings/market-core/src/index.ts index 536442a..00a6315 100644 --- a/oracledesk-stellar/packages/bindings/market-core/src/index.ts +++ b/oracledesk-stellar/packages/bindings/market-core/src/index.ts @@ -33,6 +33,7 @@ if (typeof window !== "undefined") { + export const Errors = { 1: {message:"NotInitialized"}, 2: {message:"Paused"}, @@ -71,29 +72,29 @@ export interface Market { /** * Unix seconds (ledger timestamp). Trading stops at this time. */ -close_time: u64; + close_time: u64; creator: string; /** * Snapshot of the fee at creation so later config changes never move it. */ -fee_bps: u32; + fee_bps: u32; /** * Trading fees waiting for `sweep_fees`. */ -fees_accrued: i128; + fees_accrued: i128; /** * Creator has withdrawn the pool remainder after finality. */ -lp_claimed: boolean; + lp_claimed: boolean; /** * IPFS CID / URI of the question metadata. */ -meta_uri: string; + meta_uri: string; /** * sha256 of the canonical question JSON stored off-chain (IPFS), so a * client can prove the text it shows is the text that was committed. */ -question_hash: Buffer; + question_hash: Buffer; reserve_no: i128; reserve_yes: i128; /** @@ -103,11 +104,11 @@ question_hash: Buffer; * against this hash before configuring itself for the market. See * `docs/adr/0001-resolution-commitment.md`. */ -resolution_hash: Buffer; + resolution_hash: Buffer; /** * Complete sets in existence == collateral locked for this market. */ -sets_minted: i128; + sets_minted: i128; status: MarketStatus; } @@ -146,6 +147,16 @@ export interface Client { */ sell: ({trader, market_id, outcome, collateral_out, max_shares_in}: {trader: string, market_id: u64, outcome: Outcome, collateral_out: i128, max_shares_in: i128}, options?: MethodOptions) => Promise>> + /** + * Construct and simulate a sell_shares transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Sell exactly `shares_in` of `outcome` and receive the collateral the + * curve allows. Returns `collateral_out`. Reverts if less than + * `min_collateral_out` would be received. Rounding is pool-favoring: + * `k = r_yes * r_no` never decreases and the payout never exceeds what + * `sell` would charge the same share count for. + */ + sell_shares: ({trader, market_id, outcome, shares_in, min_collateral_out}: {trader: string, market_id: u64, outcome: Outcome, shares_in: i128, min_collateral_out: i128}, options?: MethodOptions) => Promise>> + /** * Construct and simulate a redeem transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Pays out `holder`'s position for a final market and clears it. @@ -156,7 +167,7 @@ export interface Client { /** * Construct and simulate a resolve transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. */ - resolve: ({market_id, outcome}: {market_id: u64, outcome: Outcome}, options?: MethodOptions) => Promise>> + resolve: ({market_id, outcome}: {market_id: u64, outcome: Outcome}, options?: MethodOptions) => Promise> /** * Construct and simulate a get_price transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. @@ -186,6 +197,13 @@ export interface Client { */ quote_sell: ({market_id, outcome, collateral_out}: {market_id: u64, outcome: Outcome, collateral_out: i128}, options?: MethodOptions) => Promise>> + /** + * Construct and simulate a quote_sell_shares transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Collateral a `sell_shares` of exactly `shares_in` would return right + * now. Does not check tradability, consistent with `quote_sell`. + */ + quote_sell_shares: ({market_id, outcome, shares_in}: {market_id: u64, outcome: Outcome, shares_in: i128}, options?: MethodOptions) => Promise>> + /** * Construct and simulate a set_paused transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. */ @@ -224,100 +242,1126 @@ export interface Client { set_treasury: ({treasury}: {treasury: string}, options?: MethodOptions) => Promise>> /** - * Construct and simulate a create_market transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - * Creates a market and seeds the pool. `creator` becomes the sole LP. - * `initial_yes_bps` in [100, 9900] sets the opening YES probability; - * the creator receives the leftover shares of the likelier outcome. + * Construct and simulate a set_fee transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. */ - create_market: ({creator, question_hash, resolution_hash, meta_uri, category, close_time, seed_amount, initial_yes_bps}: {creator: string, question_hash: Buffer, resolution_hash: Buffer, meta_uri: string, category: Category, close_time: u64, seed_amount: i128, initial_yes_bps: u32}, options?: MethodOptions) => Promise>> + set_fee: ({fee_bps}: {fee_bps: u32}, options?: MethodOptions) => Promise>> /** - * Construct and simulate a set_default_fee_bps transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - * Affects markets created after the call; existing markets keep their fee. + * Construct and simulate a set_admin transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. */ - set_default_fee_bps: ({fee_bps}: {fee_bps: u32}, options?: MethodOptions) => Promise>> + set_admin: ({admin}: {admin: string}, options?: MethodOptions) => Promise>> /** - * Construct and simulate a claim_pool_remainder transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - * Creator (the LP) withdraws whatever the pool still holds once final. + * Construct and simulate a claim_lp transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. */ - claim_pool_remainder: ({market_id}: {market_id: u64}, options?: MethodOptions) => Promise>> + claim_lp: ({market_id}: {market_id: u64}, options?: MethodOptions) => Promise>> + /** + * Construct and simulate a create_market transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + */ + create_market: ({creator, question_hash, meta_uri, close_time, category, resolution_hash, fee_bps}: {creator: string, question_hash: Buffer, meta_uri: string, close_time: u64, category: Category, resolution_hash: Buffer, fee_bps: Option}, options?: MethodOptions) => Promise>> + + /** + * Construct and simulate a initialize transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + */ + initialize: ({admin, collateral, treasury, resolver, default_fee_bps}: {admin: string, collateral: string, treasury: string, resolver: string, default_fee_bps: u32}, options?: MethodOptions) => Promise>> } + export class Client extends ContractClient { - static async deploy( - /** Constructor/Initialization Args for the contract's `__constructor` method */ - {admin, collateral, treasury, resolver, default_fee_bps}: {admin: string, collateral: string, treasury: string, resolver: string, default_fee_bps: u32}, - /** Options for initializing a Client as well as for calling a method, with extras specific to deploying. */ - options: MethodOptions & - Omit & { - /** The hash of the Wasm blob, which must already be installed on-chain. */ - wasmHash: Buffer | string; - /** Salt used to generate the contract's ID. Passed through to {@link Operation.createCustomContract}. Default: random. */ - salt?: Buffer | Uint8Array; - /** The format used to decode `wasmHash`, if it's provided as a string. */ - format?: "hex" | "base64"; - } - ): Promise> { - return ContractClient.deploy({admin, collateral, treasury, resolver, default_fee_bps}, options) + readonly options: ContractClientOptions; + + constructor(options: ContractClientOptions) { + super(new ContractSpec(metadata)); + this.options = options; + } + + buy = async ({trader, market_id, outcome, collateral_in, min_shares_out}: {trader: string, market_id: u64, outcome: Outcome, collateral_in: i128, min_shares_out: i128}, options?: MethodOptions): Promise>> => { + return this.call("buy", {trader, market_id, outcome, collateral_in, min_shares_out}, options); + } + + sell = async ({trader, market_id, outcome, collateral_out, max_shares_in}: {trader: string, market_id: u64, outcome: Outcome, collateral_out: i128, max_shares_in: i128}, options?: MethodOptions): Promise>> => { + return this.call("sell", {trader, market_id, outcome, collateral_out, max_shares_in}, options); + } + + sell_shares = async ({trader, market_id, outcome, shares_in, min_collateral_out}: {trader: string, market_id: u64, outcome: Outcome, shares_in: i128, min_collateral_out: i128}, options?: MethodOptions): Promise>> => { + return this.call("sell_shares", {trader, market_id, outcome, shares_in, min_collateral_out}, options); + } + + redeem = async ({holder, market_id}: {holder: string, market_id: u64}, options?: MethodOptions): Promise>> => { + return this.call("redeem", {holder, market_id}, options); + } + + resolve = async ({market_id, outcome}: {market_id: u64, outcome: Outcome}, options?: MethodOptions): Promise>> => { + return this.call("resolve", {market_id, outcome}, options); + } + + get_price = async ({market_id, outcome}: {market_id: u64, outcome: Outcome}, options?: MethodOptions): Promise>> => { + return this.call("get_price", {market_id, outcome}, options); + } + + quote_buy = async ({market_id, outcome, collateral_in}: {market_id: u64, outcome: Outcome, collateral_in: i128}, options?: MethodOptions): Promise>> => { + return this.call("quote_buy", {market_id, outcome, collateral_in}, options); + } + + get_config = async (options?: MethodOptions): Promise>> => { + return this.call("get_config", {}, options); } - constructor(public readonly options: ContractClientOptions) { - super( - new ContractSpec([ "AAAABAAAAAAAAAAAAAAABUVycm9yAAAAAAAAEgAAAAAAAAAOTm90SW5pdGlhbGl6ZWQAAAAAAAEAAAAAAAAABlBhdXNlZAAAAAAAAgAAAAAAAAAKSW52YWxpZEZlZQAAAAAAAwAAAAAAAAANSW52YWxpZEFtb3VudAAAAAAAAAQAAAAAAAAAEkludmFsaWRQcm9iYWJpbGl0eQAAAAAABQAAAAAAAAAQSW52YWxpZENsb3NlVGltZQAAAAYAAAAAAAAADk1hcmtldE5vdEZvdW5kAAAAAAAHAAAAAAAAAA1NYXJrZXROb3RPcGVuAAAAAAAACAAAAAAAAAAMTWFya2V0Q2xvc2VkAAAACQAAAAAAAAAMTm90Q2xvc2VkWWV0AAAACgAAAAAAAAAITm90RmluYWwAAAALAAAAAAAAABBTbGlwcGFnZUV4Y2VlZGVkAAAADAAAAAAAAAASSW5zdWZmaWNpZW50U2hhcmVzAAAAAAANAAAAAAAAABVJbnN1ZmZpY2llbnRMaXF1aWRpdHkAAAAAAAAOAAAAAAAAAA5NYXJrZXRUb29MYXJnZQAAAAAADwAAAAAAAAAPTm90aGluZ1RvUmVkZWVtAAAAABAAAAAAAAAADkFscmVhZHlDbGFpbWVkAAAAAAARAAAAAAAAAAhPdmVyZmxvdwAAABI=", - "AAAABQAAAAAAAAAAAAAABVRyYWRlAAAAAAAAAQAAAAV0cmFkZQAAAAAAAAkAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAABAAAAAAAAAAZ0cmFkZXIAAAAAABMAAAABAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAAAAAAAAZpc19idXkAAAAAAAEAAAAAAAAAAAAAAApjb2xsYXRlcmFsAAAAAAALAAAAAAAAAAAAAAAGc2hhcmVzAAAAAAALAAAAAAAAAAAAAAADZmVlAAAAAAsAAAAAAAAAAAAAAAtyZXNlcnZlX3llcwAAAAALAAAAAAAAAAAAAAAKcmVzZXJ2ZV9ubwAAAAAACwAAAAAAAAAC", - "AAAAAQAAAAAAAAAAAAAABkNvbmZpZwAAAAAABgAAAAAAAAAFYWRtaW4AAAAAAAATAAAAAAAAAApjb2xsYXRlcmFsAAAAAAATAAAAAAAAAA9kZWZhdWx0X2ZlZV9icHMAAAAABAAAAAAAAAAGcGF1c2VkAAAAAAABAAAAAAAAAAhyZXNvbHZlcgAAABMAAAAAAAAACHRyZWFzdXJ5AAAAEw==", - "AAAAAQAAAAAAAAAAAAAABk1hcmtldAAAAAAADQAAAAAAAAAIY2F0ZWdvcnkAAAfQAAAACENhdGVnb3J5AAAAPFVuaXggc2Vjb25kcyAobGVkZ2VyIHRpbWVzdGFtcCkuIFRyYWRpbmcgc3RvcHMgYXQgdGhpcyB0aW1lLgAAAApjbG9zZV90aW1lAAAAAAAGAAAAAAAAAAdjcmVhdG9yAAAAABMAAABGU25hcHNob3Qgb2YgdGhlIGZlZSBhdCBjcmVhdGlvbiBzbyBsYXRlciBjb25maWcgY2hhbmdlcyBuZXZlciBtb3ZlIGl0LgAAAAAAB2ZlZV9icHMAAAAABAAAACZUcmFkaW5nIGZlZXMgd2FpdGluZyBmb3IgYHN3ZWVwX2ZlZXNgLgAAAAAADGZlZXNfYWNjcnVlZAAAAAsAAAA4Q3JlYXRvciBoYXMgd2l0aGRyYXduIHRoZSBwb29sIHJlbWFpbmRlciBhZnRlciBmaW5hbGl0eS4AAAAKbHBfY2xhaW1lZAAAAAAAAQAAAChJUEZTIENJRCAvIFVSSSBvZiB0aGUgcXVlc3Rpb24gbWV0YWRhdGEuAAAACG1ldGFfdXJpAAAAEAAAAIZzaGEyNTYgb2YgdGhlIGNhbm9uaWNhbCBxdWVzdGlvbiBKU09OIHN0b3JlZCBvZmYtY2hhaW4gKElQRlMpLCBzbyBhCmNsaWVudCBjYW4gcHJvdmUgdGhlIHRleHQgaXQgc2hvd3MgaXMgdGhlIHRleHQgdGhhdCB3YXMgY29tbWl0dGVkLgAAAAAADXF1ZXN0aW9uX2hhc2gAAAAAAAPuAAAAIAAAAAAAAAAKcmVzZXJ2ZV9ubwAAAAAACwAAAAAAAAALcmVzZXJ2ZV95ZXMAAAAACwAAAS9Db21taXRtZW50IHRvIGhvdyB0aGlzIG1hcmtldCB3aWxsIGJlIHJlc29sdmVkLCBmaXhlZCBhdCBjcmVhdGlvbiBzbwp0aGUgY3JlYXRvciBjYW5ub3QgcGljayBmYXZvcmFibGUgcmVzb2x1dGlvbiBydWxlcyBhZnRlciBzZWVpbmcKdHJhZGluZyBhY3Rpdml0eS4gVGhlIHJlc29sdmVyIGNvbnRyYWN0IHZlcmlmaWVzIGEgcmV2ZWFsZWQgc3BlYwphZ2FpbnN0IHRoaXMgaGFzaCBiZWZvcmUgY29uZmlndXJpbmcgaXRzZWxmIGZvciB0aGUgbWFya2V0LiBTZWUKYGRvY3MvYWRyLzAwMDEtcmVzb2x1dGlvbi1jb21taXRtZW50Lm1kYC4AAAAAD3Jlc29sdXRpb25faGFzaAAAAAPuAAAAIAAAAEBDb21wbGV0ZSBzZXRzIGluIGV4aXN0ZW5jZSA9PSBjb2xsYXRlcmFsIGxvY2tlZCBmb3IgdGhpcyBtYXJrZXQuAAAAC3NldHNfbWludGVkAAAAAAsAAAAAAAAABnN0YXR1cwAAAAAH0AAAAAxNYXJrZXRTdGF0dXM=", - "AAAAAAAAAIFTcGVuZCBgY29sbGF0ZXJhbF9pbmAgdG8gYnV5IGBvdXRjb21lYCBzaGFyZXMuIFJldHVybnMgc2hhcmVzIHJlY2VpdmVkLgpSZXZlcnRzIGlmIGZld2VyIHRoYW4gYG1pbl9zaGFyZXNfb3V0YCB3b3VsZCBiZSByZWNlaXZlZC4AAAAAAAADYnV5AAAAAAUAAAAAAAAABnRyYWRlcgAAAAAAEwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAHb3V0Y29tZQAAAAfQAAAAB091dGNvbWUAAAAAAAAAAA1jb2xsYXRlcmFsX2luAAAAAAAACwAAAAAAAAAObWluX3NoYXJlc19vdXQAAAAAAAsAAAABAAAD6QAAAAsAAAAD", - "AAAAAgAAAAAAAAAAAAAAB0RhdGFLZXkAAAAACQAAAAAAAAAAAAAABUFkbWluAAAAAAAAAAAAAAAAAAAKQ29sbGF0ZXJhbAAAAAAAAAAAAAAAAAAIVHJlYXN1cnkAAAAAAAAAAAAAAAhSZXNvbHZlcgAAAAAAAAAAAAAADURlZmF1bHRGZWVCcHMAAAAAAAAAAAAAAAAAAAZQYXVzZWQAAAAAAAAAAAAAAAAABk5leHRJZAAAAAAAAQAAAAAAAAAGTWFya2V0AAAAAAABAAAABgAAAAEAAAAAAAAACFBvc2l0aW9uAAAAAgAAAAYAAAAT", - "AAAAAgAAAAAAAAAAAAAAB091dGNvbWUAAAAAAgAAAAAAAAAAAAAAA1llcwAAAAAAAAAAAAAAAAJObwAA", - "AAAAAAAAARBSZWNlaXZlIGV4YWN0bHkgYGNvbGxhdGVyYWxfb3V0YCBieSBzZWxsaW5nIGBvdXRjb21lYCBzaGFyZXMuIFJldHVybnMKc2hhcmVzIHNvbGQuIFJldmVydHMgaWYgbW9yZSB0aGFuIGBtYXhfc2hhcmVzX2luYCB3b3VsZCBiZSBuZWVkZWQuCkNsaWVudHMgdHVybiAic2VsbCBOIHNoYXJlcyIgaW50byBgY29sbGF0ZXJhbF9vdXRgIG9mZi1jaGFpbgooc2VlIGBtYXRoOjpjYWxjX3NlbGxgIGRvY3MpIGFuZCBhZGQgYSBzbGlwcGFnZSBtYXJnaW4gdG8gYG1heF9zaGFyZXNfaW5gLgAAAARzZWxsAAAABQAAAAAAAAAGdHJhZGVyAAAAAAATAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAAAAAAADmNvbGxhdGVyYWxfb3V0AAAAAAALAAAAAAAAAA1tYXhfc2hhcmVzX2luAAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", - "AAAAAgAAAAAAAAAAAAAACENhdGVnb3J5AAAABgAAAAAAAAAAAAAABkNyeXB0bwAAAAAAAAAAAAAAAAAFTWFjcm8AAAAAAAAAAAAAAAAAAAtHZW9wb2xpdGljcwAAAAAAAAAAAAAAAAZTcG9ydHMAAAAAAAAAAAAAAAAAB0N1bHR1cmUAAAAAAAAAAAAAAAAFT3RoZXIAAAA=", - "AAAAAQAAAAAAAAAAAAAACFBvc2l0aW9uAAAAAgAAAAAAAAACbm8AAAAAAAsAAAAAAAAAA3llcwAAAAAL", - "AAAABQAAAAAAAAAAAAAACFJlZGVlbWVkAAAAAQAAAAhyZWRlZW1lZAAAAAMAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAABAAAAAAAAAAZob2xkZXIAAAAAABMAAAABAAAAAAAAAAZwYXlvdXQAAAAAAAsAAAAAAAAAAg==", - "AAAAAAAAAGlQYXlzIG91dCBgaG9sZGVyYCdzIHBvc2l0aW9uIGZvciBhIGZpbmFsIG1hcmtldCBhbmQgY2xlYXJzIGl0LgpMb3NpbmcgcG9zaXRpb25zIGNsZWFyIHdpdGggYSBwYXlvdXQgb2YgMC4AAAAAAAAGcmVkZWVtAAAAAAACAAAAAAAAAAZob2xkZXIAAAAAABMAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAABAAAD6QAAAAsAAAAD", - "AAAABQAAAAAAAAAAAAAACUZlZXNTd2VwdAAAAAAAAAEAAAAKZmVlc19zd2VwdAAAAAAAAgAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAEAAAAAAAAABmFtb3VudAAAAAAACwAAAAAAAAAC", - "AAAAAAAAAAAAAAAHcmVzb2x2ZQAAAAACAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAAAAAAdvdXRjb21lAAAAB9AAAAAHT3V0Y29tZQAAAAABAAAD6QAAAAIAAAAD", - "AAAAAgAAAAAAAAAAAAAADE1hcmtldFN0YXR1cwAAAAMAAAAAAAAAQlRyYWRpbmcgYWxsb3dlZCB1bnRpbCBgY2xvc2VfdGltZWAsIHRoZW4gd2FpdGluZyBmb3IgdGhlIHJlc29sdmVyLgAAAAAABE9wZW4AAAABAAAAIUZpbmFsLiBXaW5uaW5nIHNoYXJlcyByZWRlZW0gMToxLgAAAAAAAAhSZXNvbHZlZAAAAAEAAAfQAAAAB091dGNvbWUAAAAAAAAAACtJbnZhbGlkIG1hcmtldC4gRXZlcnkgc2hhcmUgcmVkZWVtcyBhdCAwLjUuAAAAAARWb2lk", - "AAAAAAAAAD9TcG90IHByaWNlIG9mIGBvdXRjb21lYCBpbiBicHMgKDAuLj0xMF8wMDApLiBZRVMgKyBOTyB+IDEwXzAwMC4AAAAACWdldF9wcmljZQAAAAAAAAIAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAAAAAAAB291dGNvbWUAAAAH0AAAAAdPdXRjb21lAAAAAAEAAAPpAAAABAAAAAM=", - "AAAAAAAAADlTaGFyZXMgYSBgYnV5YCBvZiBgY29sbGF0ZXJhbF9pbmAgd291bGQgcmV0dXJuIHJpZ2h0IG5vdy4AAAAAAAAJcXVvdGVfYnV5AAAAAAAAAwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAHb3V0Y29tZQAAAAfQAAAAB091dGNvbWUAAAAAAAAAAA1jb2xsYXRlcmFsX2luAAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", - "AAAAAAAAAAAAAAAKZ2V0X2NvbmZpZwAAAAAAAAAAAAEAAAPpAAAH0AAAAAZDb25maWcAAAAAAAM=", - "AAAAAAAAAAAAAAAKZ2V0X21hcmtldAAAAAAAAQAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAEAAAPpAAAH0AAAAAZNYXJrZXQAAAAAAAM=", - "AAAAAAAAAEVTaGFyZXMgYSBgc2VsbGAgZm9yIGV4YWN0bHkgYGNvbGxhdGVyYWxfb3V0YCB3b3VsZCBjb25zdW1lIHJpZ2h0IG5vdy4AAAAAAAAKcXVvdGVfc2VsbAAAAAAAAwAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAHb3V0Y29tZQAAAAfQAAAAB091dGNvbWUAAAAAAAAAAA5jb2xsYXRlcmFsX291dAAAAAAACwAAAAEAAAPpAAAACwAAAAM=", - "AAAAAAAAAAAAAAAKc2V0X3BhdXNlZAAAAAAAAQAAAAAAAAAGcGF1c2VkAAAAAAABAAAAAQAAA+kAAAACAAAAAw==", - "AAAAAAAAAC1BbnlvbmUgY2FuIG1vdmUgYWNjcnVlZCBmZWVzIHRvIHRoZSB0cmVhc3VyeS4AAAAAAAAKc3dlZXBfZmVlcwAAAAAAAQAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAEAAAPpAAAACwAAAAM=", - "AAAABQAAAAAAAAAAAAAADU1hcmtldENyZWF0ZWQAAAAAAAABAAAADm1hcmtldF9jcmVhdGVkAAAAAAAGAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAQAAAAAAAAAHY3JlYXRvcgAAAAATAAAAAAAAAAAAAAAIY2F0ZWdvcnkAAAfQAAAACENhdGVnb3J5AAAAAAAAAAAAAAAKY2xvc2VfdGltZQAAAAAABgAAAAAAAAAAAAAAC3NlZWRfYW1vdW50AAAAAAsAAAAAAAAAAAAAAA9pbml0aWFsX3llc19icHMAAAAABAAAAAAAAAAC", - "AAAAAAAAAEFJbnZhbGlkIG9yIHVucmVzb2x2YWJsZSBtYXJrZXQuIEFsbG93ZWQgYW55IHRpbWUgYmVmb3JlIGZpbmFsaXR5LgAAAAAAAAt2b2lkX21hcmtldAAAAAABAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAQAAA+kAAAACAAAAAw==", - "AAAABQAAAAAAAAAAAAAADk1hcmtldFJlc29sdmVkAAAAAAABAAAAD21hcmtldF9yZXNvbHZlZAAAAAACAAAAAAAAAAltYXJrZXRfaWQAAAAAAAAGAAAAAQAAACFOb25lIG1lYW5zIHRoZSBtYXJrZXQgd2FzIHZvaWRlZC4AAAAAAAAHb3V0Y29tZQAAAAPoAAAH0AAAAAdPdXRjb21lAAAAAAAAAAAC", - "AAAAAAAAAAAAAAAMZ2V0X3Bvc2l0aW9uAAAAAgAAAAAAAAAJbWFya2V0X2lkAAAAAAAABgAAAAAAAAAGaG9sZGVyAAAAAAATAAAAAQAAB9AAAAAIUG9zaXRpb24=", - "AAAAAAAAAAAAAAAMbWFya2V0X2NvdW50AAAAAAAAAAEAAAAG", - "AAAAAAAAAAAAAAAMc2V0X3Jlc29sdmVyAAAAAQAAAAAAAAAIcmVzb2x2ZXIAAAATAAAAAQAAA+kAAAACAAAAAw==", - "AAAAAAAAAAAAAAAMc2V0X3RyZWFzdXJ5AAAAAQAAAAAAAAAIdHJlYXN1cnkAAAATAAAAAQAAA+kAAAACAAAAAw==", - "AAAAAAAAAEdSdW5zIGF0b21pY2FsbHkgYXQgZGVwbG95IHRpbWUsIHNvIG5vYm9keSBjYW4gZnJvbnQtcnVuIGluaXRpYWxpemF0aW9uLgAAAAANX19jb25zdHJ1Y3RvcgAAAAAAAAUAAAAAAAAABWFkbWluAAAAAAAAEwAAAAAAAAAKY29sbGF0ZXJhbAAAAAAAEwAAAAAAAAAIdHJlYXN1cnkAAAATAAAAAAAAAAhyZXNvbHZlcgAAABMAAAAAAAAAD2RlZmF1bHRfZmVlX2JwcwAAAAAEAAAAAA==", - "AAAAAAAAAMhDcmVhdGVzIGEgbWFya2V0IGFuZCBzZWVkcyB0aGUgcG9vbC4gYGNyZWF0b3JgIGJlY29tZXMgdGhlIHNvbGUgTFAuCmBpbml0aWFsX3llc19icHNgIGluIFsxMDAsIDk5MDBdIHNldHMgdGhlIG9wZW5pbmcgWUVTIHByb2JhYmlsaXR5Owp0aGUgY3JlYXRvciByZWNlaXZlcyB0aGUgbGVmdG92ZXIgc2hhcmVzIG9mIHRoZSBsaWtlbGllciBvdXRjb21lLgAAAA1jcmVhdGVfbWFya2V0AAAAAAAACAAAAAAAAAAHY3JlYXRvcgAAAAATAAAAAAAAAA1xdWVzdGlvbl9oYXNoAAAAAAAD7gAAACAAAAAAAAAAD3Jlc29sdXRpb25faGFzaAAAAAPuAAAAIAAAAAAAAAAIbWV0YV91cmkAAAAQAAAAAAAAAAhjYXRlZ29yeQAAB9AAAAAIQ2F0ZWdvcnkAAAAAAAAACmNsb3NlX3RpbWUAAAAAAAYAAAAAAAAAC3NlZWRfYW1vdW50AAAAAAsAAAAAAAAAD2luaXRpYWxfeWVzX2JwcwAAAAAEAAAAAQAAA+kAAAAGAAAAAw==", - "AAAAAAAAAEhBZmZlY3RzIG1hcmtldHMgY3JlYXRlZCBhZnRlciB0aGUgY2FsbDsgZXhpc3RpbmcgbWFya2V0cyBrZWVwIHRoZWlyIGZlZS4AAAATc2V0X2RlZmF1bHRfZmVlX2JwcwAAAAABAAAAAAAAAAdmZWVfYnBzAAAAAAQAAAABAAAD6QAAAAIAAAAD", - "AAAAAAAAAERDcmVhdG9yICh0aGUgTFApIHdpdGhkcmF3cyB3aGF0ZXZlciB0aGUgcG9vbCBzdGlsbCBob2xkcyBvbmNlIGZpbmFsLgAAABRjbGFpbV9wb29sX3JlbWFpbmRlcgAAAAEAAAAAAAAACW1hcmtldF9pZAAAAAAAAAYAAAABAAAD6QAAAAsAAAAD" ]), - options - ) + + get_market = async ({market_id}: {market_id: u64}, options?: MethodOptions): Promise>> => { + return this.call("get_market", {market_id}, options); + } + + quote_sell = async ({market_id, outcome, collateral_out}: {market_id: u64, outcome: Outcome, collateral_out: i128}, options?: MethodOptions): Promise>> => { + return this.call("quote_sell", {market_id, outcome, collateral_out}, options); + } + + quote_sell_shares = async ({market_id, outcome, shares_in}: {market_id: u64, outcome: Outcome, shares_in: i128}, options?: MethodOptions): Promise>> => { + return this.call("quote_sell_shares", {market_id, outcome, shares_in}, options); + } + + set_paused = async ({paused}: {paused: boolean}, options?: MethodOptions): Promise>> => { + return this.call("set_paused", {paused}, options); + } + + sweep_fees = async ({market_id}: {market_id: u64}, options?: MethodOptions): Promise>> => { + return this.call("sweep_fees", {market_id}, options); + } + + void_market = async ({market_id}: {market_id: u64}, options?: MethodOptions): Promise>> => { + return this.call("void_market", {market_id}, options); + } + + get_position = async ({market_id, holder}: {market_id: u64, holder: string}, options?: MethodOptions): Promise> => { + return this.call("get_position", {market_id, holder}, options); + } + + market_count = async (options?: MethodOptions): Promise> => { + return this.call("market_count", {}, options); + } + + set_resolver = async ({resolver}: {resolver: string}, options?: MethodOptions): Promise>> => { + return this.call("set_resolver", {resolver}, options); + } + + set_treasury = async ({treasury}: {treasury: string}, options?: MethodOptions): Promise>> => { + return this.call("set_treasury", {treasury}, options); + } + + set_fee = async ({fee_bps}: {fee_bps: u32}, options?: MethodOptions): Promise>> => { + return this.call("set_fee", {fee_bps}, options); + } + + set_admin = async ({admin}: {admin: string}, options?: MethodOptions): Promise>> => { + return this.call("set_admin", {admin}, options); + } + + claim_lp = async ({market_id}: {market_id: u64}, options?: MethodOptions): Promise>> => { + return this.call("claim_lp", {market_id}, options); + } + + create_market = async ({creator, question_hash, meta_uri, close_time, category, resolution_hash, fee_bps}: {creator: string, question_hash: Buffer, meta_uri: string, close_time: u64, category: Category, resolution_hash: Buffer, fee_bps: Option}, options?: MethodOptions): Promise>> => { + return this.call("create_market", {creator, question_hash, meta_uri, close_time, category, resolution_hash, fee_bps}, options); } - public readonly fromJSON = { - buy: this.txFromJSON>, - sell: this.txFromJSON>, - redeem: this.txFromJSON>, - resolve: this.txFromJSON>, - get_price: this.txFromJSON>, - quote_buy: this.txFromJSON>, - get_config: this.txFromJSON>, - get_market: this.txFromJSON>, - quote_sell: this.txFromJSON>, - set_paused: this.txFromJSON>, - sweep_fees: this.txFromJSON>, - void_market: this.txFromJSON>, - get_position: this.txFromJSON, - market_count: this.txFromJSON, - set_resolver: this.txFromJSON>, - set_treasury: this.txFromJSON>, - create_market: this.txFromJSON>, - set_default_fee_bps: this.txFromJSON>, - claim_pool_remainder: this.txFromJSON> + + initialize = async ({admin, collateral, treasury, resolver, default_fee_bps}: {admin: string, collateral: string, treasury: string, resolver: string, default_fee_bps: u32}, options?: MethodOptions): Promise>> => { + return this.call("initialize", {admin, collateral, treasury, resolver, default_fee_bps}, options); } +} + +export const networks = { + testnet: { + networkPassphrase: "Test S CF Network ; September 2015", + contractId: "", + }, + futurenet: { + networkPassphrase: "Test S CF Network ; September 2015", + contractId: "", + }, + mainnet: { + networkPassphrase: "Public Global Key Network ; September 2015", + contractId: "", + }, +} + +export const metadata = { + "entries": [ + { + "type": "function", + "name": "buy", + "inputs": [ + { + "name": "trader", + "type": { + "type": "address" + } + }, + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + }, + { + "name": "collateral_in", + "type": { + "type": "i128" + } + }, + { + "name": "min_shares_out", + "type": { + "type": "i128" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "sell", + "inputs": [ + { + "name": "trader", + "type": { + "type": "address" + } + }, + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + }, + { + "name": "collateral_out", + "type": { + "type": "i128" + } + }, + { + "name": "max_shares_in", + "type": { + "type": "i128" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "sell_shares", + "inputs": [ + { + "name": "trader", + "type": { + "type": "address" + } + }, + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + }, + { + "name": "shares_in", + "type": { + "type": "i128" + } + }, + { + "name": "min_collateral_out", + "type": { + "type": "i128" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "redeem", + "inputs": [ + { + "name": "holder", + "type": { + "type": "address" + } + }, + { + "name": "market_id", + "type": { + "type": "u64" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "resolve", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "void" + } + } + ] + }, + { + "type": "function", + "name": "get_price", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "u32" + } + } + ] + }, + { + "type": "function", + "name": "quote_buy", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + }, + { + "name": "collateral_in", + "type": { + "type": "i128" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "get_config", + "inputs": [], + "outputs": [ + { + "type": "result", + "value": { + "type": "custom", + "name": "Config" + } + } + ] + }, + { + "type": "function", + "name": "get_market", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "custom", + "name": "Market" + } + } + ] + }, + { + "type": "function", + "name": "quote_sell", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + }, + { + "name": "collateral_out", + "type": { + "type": "i128" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "quote_sell_shares", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + }, + { + "name": "shares_in", + "type": { + "type": "i128" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "set_paused", + "inputs": [ + { + "name": "paused", + "type": { + "type": "boolean" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "void" + } + } + ] + }, + { + "type": "function", + "name": "sweep_fees", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "void_market", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "void" + } + } + ] + }, + { + "type": "function", + "name": "get_position", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "holder", + "type": { + "type": "address" + } + } + ], + "outputs": [ + { + "type": "custom", + "name": "Position" + } + ] + }, + { + "type": "function", + "name": "market_count", + "inputs": [], + "outputs": [ + { + "type": "u64" + } + ] + }, + { + "type": "function", + "name": "set_resolver", + "inputs": [ + { + "name": "resolver", + "type": { + "type": "address" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "void" + } + } + ] + }, + { + "type": "function", + "name": "set_treasury", + "inputs": [ + { + "name": "treasury", + "type": { + "type": "address" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "void" + } + } + ] + }, + { + "type": "function", + "name": "set_fee", + "inputs": [ + { + "name": "fee_bps", + "type": { + "type": "u32" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "void" + } + } + ] + }, + { + "type": "function", + "name": "set_admin", + "inputs": [ + { + "name": "admin", + "type": { + "type": "address" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "void" + } + } + ] + }, + { + "type": "function", + "name": "claim_lp", + "inputs": [ + { + "name": "market_id", + "type": { + "type": "u64" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "i128" + } + } + ] + }, + { + "type": "function", + "name": "create_market", + "inputs": [ + { + "name": "creator", + "type": { + "type": "address" + } + }, + { + "name": "question_hash", + "type": { + "type": "bytes" + } + }, + { + "name": "meta_uri", + "type": "string" + }, + { + "name": "close_time", + "type": { + "type": "u64" + } + }, + { + "name": "category", + "type": { + "type": "custom", + "name": "Category" + } + }, + { + "name": "resolution_hash", + "type": { + "type": "bytes" + } + }, + { + "name": "fee_bps", + "type": { + "type": "option", + "value": { + "type": "u32" + } + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "u64" + } + } + ] + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "admin", + "type": { + "type": "address" + } + }, + { + "name": "collateral", + "type": { + "type": "address" + } + }, + { + "name": "treasury", + "type": { + "type": "address" + } + }, + { + "name": "resolver", + "type": { + "type": "address" + } + }, + { + "name": "default_fee_bps", + "type": { + "type": "u32" + } + } + ], + "outputs": [ + { + "type": "result", + "value": { + "type": "void" + } + } + ] + }, + { + "type": "event", + "name": "Trade", + "inputs": [ + { + "name": "trader", + "type": { + "type": "address" + } + }, + { + "name": "market_id", + "type": { + "type": "u64" + } + }, + { + "name": "outcome", + "type": { + "type": "custom", + "name": "Outcome" + } + }, + { + "name": "is_buy", + "type": { + "type": "boolean" + } + }, + { + "name": "shares", + "type": { + "type": "i128" + } + }, + { + "name": "collateral", + "type": { + "type": "i128" + } + }, + { + "name": "fee", + "type": { + "type": "i128" + } + } + ] + }, + { + "type": "enum", + "name": "Outcome", + "cases": [ + { + "name": "Yes" + }, + { + "name": "No" + } + ] + }, + { + "type": "enum", + "name": "Category", + "cases": [ + { + "name": "Crypto" + }, + { + "name": "Macro" + }, + { + "name": "Geopolitics" + }, + { + "name": "Sports" + }, + { + "name": "Culture" + }, + { + "name": "Other" + } + ] + }, + { + "type": "struct", + "name": "Config", + "fields": [ + { + "name": "admin", + "type": { + "type": "address" + } + }, + { + "name": "collateral", + "type": { + "type": "address" + } + }, + { + "name": "default_fee_bps", + "type": { + "type": "u32" + } + }, + { + "name": "paused", + "type": { + "type": "boolean" + } + }, + { + "name": "resolver", + "type": { + "type": "address" + } + }, + { + "name": "treasury", + "type": { + "type": "address" + } + } + ] + }, + { + "type": "struct", + "name": "Market", + "fields": [ + { + "name": "category", + "type": { + "type": "custom", + "name": "Category" + } + }, + { + "name": "close_time", + "type": { + "type": "u64" + } + }, + { + "name": "creator", + "type": { + "type": "address" + } + }, + { + "name": "fee_bps", + "type": { + "type": "u32" + } + }, + { + "name": "fees_accrued", + "type": { + "type": "i128" + } + }, + { + "name": "lp_claimed", + "type": { + "type": "boolean" + } + }, + { + "name": "meta_uri", + "type": { + "type": "string" + } + }, + { + "name": "question_hash", + "type": { + "type": "bytes" + } + }, + { + "name": "reserve_no", + "type": { + "type": "i128" + } + }, + { + "name": "reserve_yes", + "type": { + "type": "i128" + } + }, + { + "name": "resolution_hash", + "type": { + "type": "bytes" + } + }, + { + "name": "sets_minted", + "type": { + "type": "i128" + } + }, + { + "name": "status", + "type": { + "type": "custom", + "name": "MarketStatus" + } + } + ] + }, + { + "type": "struct", + "name": "Position", + "fields": [ + { + "name": "no", + "type": { + "type": "i128" + } + }, + { + "name": "yes", + "type": { + "type": "i128" + } + } + ] + }, + { + "type": "enum", + "name": "MarketStatus", + "cases": [ + { + "name": "Open" + }, + { + "name": "Resolved", + "cases": [ + { + "name": "__0", + "type": { + "type": "custom", + "name": "Outcome" + } + } + ] + }, + { + "name": "Void" + } + ] + }, + { + "type": "error", + "name": "Error", + "cases": [ + { + "name": "NotInitialized", + "value": 1 + }, + { + "name": "Paused", + "value": 2 + }, + { + "name": "InvalidFee", + "value": 3 + }, + { + "name": "InvalidAmount", + "value": 4 + }, + { + "name": "InvalidProbability", + "value": 5 + }, + { + "name": "InvalidCloseTime", + "value": 6 + }, + { + "name": "MarketNotFound", + "value": 7 + }, + { + "name": "MarketNotOpen", + "value": 8 + }, + { + "name": "MarketClosed", + "value": 9 + }, + { + "name": "NotClosedYet", + "value": 10 + }, + { + "name": "NotFinal", + "value": 11 + }, + { + "name": "SlippageExceeded", + "value": 12 + }, + { + "name": "InsufficientShares", + "value": 13 + }, + { + "name": "InsufficientLiquidity", + "value": 14 + }, + { + "name": "MarketTooLarge", + "value": 15 + }, + { + "name": "NothingToRedeem", + "value": 16 + }, + { + "name": "AlreadyClaimed", + "value": 17 + }, + { + "name": "Overflow", + "value": 18 + } + ] + } + ] } \ No newline at end of file