Skip to content
Closed
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
15 changes: 15 additions & 0 deletions oracledesk-stellar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ are all native Soroban contracts, collateralized in a SEP-41 USDC token.
Polymarket survives only as an optional, execution-free reference price an
agent's strategy may read (see `agents/core/strategy.ts`).

`market-core` reads the collateral token's `decimals()` once at deploy and
derives its economic bounds from it (`min_seed = 100 * 10^d`,
`max_sets = 1e8 * 10^d`, supported `d` in `0..=18`), so any SEP-41 token โ€”
7-decimal Classic USDC, 18-decimal Soroban-native assets, or bridged tokens โ€”
can back a market without changing the contract. The fixed-point helpers use
256-bit intermediates, so a result that fits in `i128` is always returned
exactly regardless of `d`. The Python (`model/fpmm_model.py`) and TypeScript
(`agents/core/fpmm.ts`) mirrors track the same bounds and overflow behavior.

## Architecture

```mermaid
Expand Down Expand Up @@ -102,6 +111,12 @@ deployments/ testnet.json โ€” the current live testnet addresses
docs/ architecture, ADRs, frontend-integration, STATUS
```

The deploy script self-issues a 7-decimal Classic asset as test USDC. To
exercise other decimals, deploy a SEP-41 token with the desired `decimals()`
first and pass its contract ID as the collateral; `market-core`'s
`__constructor` will reject anything above 18 decimals with
`Error::UnsupportedDecimals`.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, branch/commit
Expand Down
64 changes: 53 additions & 11 deletions oracledesk-stellar/agents/core/fpmm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,68 @@
* plus a property check that `collateralOutForShares` (the off-chain
* inverse for "sell N shares") round-trips through `calcSell`.
*
* All amounts are non-negative bigints in collateral base units (USDC on
* Stellar = 7 decimals). Rounding always favours the pool, matching the
* contract.
* All amounts are non-negative bigints in collateral base units. The
* collateral decimals are discovered at deploy time by the contract and
* drive the economic bounds (`min_seed` / `max_sets`). This mirror
* exposes `boundsForDecimals` so agents can derive the same bounds.
* 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%

/**
* @deprecated 7-decimal alias kept for tests and legacy callers.
* Contract logic derives the bound from collateral decimals instead;
* use `boundsForDecimals`.
*/
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 I18_MAX = (1n << 127n) - 1n ; // i128::max

export const MAX_SUPPORTED_DECIMALS = 18;

export class MathError extends Error {}

/**
* Derived economic bounds for a collateral with `d` decimals.
* Mirrors `math::baunds_for_decimals` in the Rust contract.
*/
export interface Bounds {
minSeed: bigint;
maxSets: bigint;
}

export function boundsForDecimals(d: number): Bounds {
if (!Number.isInteger(d) || d < 0 || d > MAX_SUPPORTED_DECIMALS) {
throw new MathError("unsupported decimals");
}
const scale = 10n ** BigInt(d);
const minSeed = 100n * scale;
const maxSets = 100_000_000n * scale; // 1e8 * 10^d
if (minSeed > I128_MAX || maxSets > I128_MAX) {
throw new MathError("overflow");
}
return { minSeed, maxSets };
}

function mulDivFloor(a: bigint, b: bigint, c: bigint): bigint {
if (a < 0n || b < 0n || c <= 0n) throw new MathError("invalid input");
return (a * b) / c;
const q = (a * b) / c;
if (q > I18_MAX) throw new MathError("overflow");
return q;
}

function mulDivCeil(a: bigint, b: bigint, c: bigint): bigint {
if (a < 0n || b < 0n || c <= 0n) throw new MathError("invalid input");
const p = a * b;
const q = p / c;
return p % c !== 0n ? q + 1n : q;
const r = p % c;
const result = r !== 0n ? q + 1n : q;
if (result > I128_MAX) throw new MathError("overflow");
return result;
}

export function priceYesBps(reserveYes: bigint, reserveNo: bigint): bigint {
Expand Down Expand Up @@ -117,12 +157,14 @@ export type Settlement = "yes" | "no" | "void";
export function settleValue(kind: Settlement, yes: bigint, no: bigint): bigint {
if (kind === "yes") return yes;
if (kind === "no") return no;
return (yes + no) / 2n;
const sum = yes + no;
if (sum > I128_MAX) throw new MathError("overflow");
return sum / 2n;
}

/**
* Integer square root (Newton's method), needed because `sell` takes an
* exact collateral amount rather than a share count (no on-chain sqrt โ€” see
* Integer square root (Newton's method), needed because `sell` takes
* an exact collateral amount rather than a share count (no on-chain sqrt โ€” see
* the docstring on `sell` in contracts/market-core/src/lib.rs). This is
* what makes `grossForShares` computable in BigInt without floating point.
*/
Expand Down
Loading
Loading