# Saturn DEX — Full Developer Reference > Saturn is a modular, fully on-chain decentralized exchange (DEX) and lending protocol built on the Phantasma blockchain. This documents every public smart-contract method that developers and AI agents can call to build on Saturn. Two products: v4 (30 modular contracts across core DEX, advanced pools & capital, financial products, agent automation, and lending) and v3 (the legacy monolithic SATRN contract). Read methods are free to call; write methods require the caller's wallet signature (witness). Address each contract by its lowercase symbol (e.g. saturnrouter, saturnswap, saturnmarket) or SATRN for v3. > Generated 2026-06-06. Canonical: https://devops.saturnx.cc/ · Apps: v4 https://m4.saturnx.cc/ | v3 https://m3.saturnx.cc/ (mainnet), v4 https://d4.saturnx.cc/ | v3 https://d3.saturnx.cc/ (devnet). ## How to call Saturn contracts Use phantasma-sdk-ts (or the Poltergeist / Ecto wallets). To READ: build a script with ScriptBuilder.callContract(contractId, method, [args]) and call api.invokeRawScript('main', script), then decode the result. To WRITE: build a transaction that calls the method with its args; the user signs and their wallet becomes the on-chain witness. Gas is paid in KCAL on Phantasma. Read methods are marked READ (free, any caller). Write methods are marked WRITE (require the caller's wallet witness). Admin-only and internal cross-contract methods are intentionally omitted. # Saturn DEX v4 ## Core DEX Admin config, pool registry, liquidity, the swap engine, fee vault, the SATURN NFT certificate, reward campaigns, and router views. ### SaturnAdmin — `saturnadmin` *Protocol Configuration* — Saturn DEX v4 · Core DEX Central configuration hub. Exposes global protocol parameters — fee splits, pool fee range, scaling limits, minimum amounts, and SOUL storage fees. All other Saturn contracts read from here, and so does your frontend. Every method below is a pure view call, free to invoke, and the primary source-of-truth for showing limits to users before they submit transactions. #### Ownership & Access ##### `getAdmin(): address` — READ Returns the current protocol admin address. Use this to verify whether a wallet has privileged control or simply to display protocol ownership in your UI. Parameters: none Returns: `address` — The admin address for the Saturn protocol. Notes: Always returns a valid, non-null address. No reverts. ```js // Phantasma SDK — invokeRawScript to read const script = ScriptBuilder .begin() .callContract("saturnadmin", "getAdmin", []) .endScript(); const res = await api.invokeRawScript("main", script); const adminAddress = res.decoded[0]; // address ``` #### Fee Split Configuration ##### `getFeeSplitRatios(): string` — READ Returns all three fee split ratios packed into a single string of the form "reinvest:X_provider:Y_admin:Z". Convenient for single-call rendering of the complete fee split. Parameters: none Returns: `string` — Example: "reinvest:60_provider:10_admin:30" — percentages sum to 100. Notes: The three numbers always sum to 100. Admin is always ≥ 1. Use this if you want one round-trip instead of three. ```js const script = ScriptBuilder .begin() .callContract("saturnadmin", "getFeeSplitRatios", []) .endScript(); const { decoded } = await api.invokeRawScript("main", script); // "reinvest:60_provider:10_admin:30" ``` ##### `getReinvestPct(): number` — READ Percentage of each swap fee that is reinvested back into the pool's reserves. This grows the pool's depth over time. Parameters: none Returns: `number` — Percentage out of 100 (default: 60). Notes: Typically 60. Always ≥ 0 and ≤ 100. ```js const reinvest = await readContract("saturnadmin", "getReinvestPct", []); ``` ##### `getProviderPct(): number` — READ Percentage of each swap fee paid to the pool provider, accrued in the FeeVault and claimable via SaturnFees.claimProviderFees(). Parameters: none Returns: `number` — Percentage out of 100 (default: 10). Notes: Typically 10. Always ≥ 0. ```js const provider = await readContract("saturnadmin", "getProviderPct", []); ``` ##### `getAdminPct(): number` — READ Percentage of each swap fee routed directly to the protocol admin treasury at swap time. Parameters: none Returns: `number` — Percentage out of 100 (default: 30). Notes: Always ≥ 1 (protocol guarantee). ```js const admin = await readContract("saturnadmin", "getAdminPct", []); ``` #### Pool Fee Range ##### `getPoolFeeRange(): string` — READ Returns both min and max per-pool fee rate as a single packed string. Use when creating a pool so the UI can clamp the user's fee slider to the valid range. Parameters: none Returns: `string` — Example: "min:30_max:3000" (basis points per 10k → 0.3% to 30%). Notes: min is always < max. min ≥ 10, max ≤ 5000. ```js const range = await readContract("saturnadmin", "getPoolFeeRange", []); // "min:30_max:3000" ``` ##### `getMinPoolFeePer10k(): number` — READ Minimum fee rate (in basis points out of 10,000) a provider may set when creating a pool. 30 means 0.3%. Parameters: none Returns: `number` — Minimum fee in basis points (default: 30 = 0.3%). Notes: Never below 10. Default is 30 (0.3%). ```js const minFee = await readContract("saturnadmin", "getMinPoolFeePer10k", []); ``` ##### `getMaxPoolFeePer10k(): number` — READ Maximum allowed pool fee rate (in basis points out of 10,000). 3000 means 30%. Parameters: none Returns: `number` — Maximum fee in basis points (default: 3000 = 30%). Notes: Never above 5000. Default is 3000 (30%). ```js const maxFee = await readContract("saturnadmin", "getMaxPoolFeePer10k", []); ``` #### Scaling & Minimum Amounts ##### `getTargetDecimals(): number` — READ The internal scaling target — all tokens are scaled up to this many decimals inside the protocol to keep math precise regardless of each token's native decimals. Parameters: none Returns: `number` — Target decimals (default: 8). Notes: Never changes after deployment in practice. Default: 8. ```js const dec = await readContract("saturnadmin", "getTargetDecimals", []); ``` ##### `getMinScaledPoolUnits(): number` — READ Minimum amount (in scaled units) required when creating a pool. Combined with each token's scale factor, this becomes the raw-unit minimum returned by SaturnRouter.getMinRawForPoolCreation(). Parameters: none Returns: `number` — Minimum scaled units for pool creation. Notes: Default: 10,000,000,000. Used by Liquidity Manager when validating createPool(). ```js const minPool = await readContract("saturnadmin", "getMinScaledPoolUnits", []); ``` ##### `getMinScaledSwapUnits(): number` — READ Minimum amount (in scaled units) required per swap. Enforced by the Swap Engine and used by Router views to show users the minimum swap they can submit. Parameters: none Returns: `number` — Minimum scaled units for a single swap. Notes: Default: 112,000,000,000. ```js const minSwap = await readContract("saturnadmin", "getMinScaledSwapUnits", []); ``` ##### `getMinScaledAddLiqUnits(): number` — READ Minimum amount (in scaled units) when adding liquidity to an existing pool. Parameters: none Returns: `number` — Minimum scaled units for add-liquidity. Notes: Default: 100,000,000. ```js const minAdd = await readContract("saturnadmin", "getMinScaledAddLiqUnits", []); ``` ##### `getAbsoluteMinRaw(): number` — READ The absolute floor for any computed raw-unit minimum. Even if scaling math would give a smaller number, raw minimums never drop below this value. Parameters: none Returns: `number` — Absolute minimum raw units (default: 100). Notes: Default: 100. Protects against tokens with extreme scale factors. ```js const floor = await readContract("saturnadmin", "getAbsoluteMinRaw", []); ``` ##### `getMaxTruncationPercent(): number` — READ Maximum allowable truncation loss (in %) when removing a pool. If scale-down rounding would lose more than this percentage of either token's reserve, removePool() reverts. Parameters: none Returns: `number` — Maximum truncation percent (default: 10). Notes: Range: 1–50. Default: 10%. ```js const maxTrunc = await readContract("saturnadmin", "getMaxTruncationPercent", []); ``` #### SOUL Storage Fees ##### `getSOULfeeCreatePool(): number` — READ The SOUL-denominated storage fee a provider must pay (on top of their token pair) when calling createPool(). The fee is staked by the protocol for on-chain storage rent. Parameters: none Returns: `number` — Raw SOUL amount (default: 2,500,000). Notes: The provider must have getSOULfeeCreatePool() + a little buffer in their wallet before calling createPool(). Default: 2,500,000. ```js const fee = await readContract("saturnadmin", "getSOULfeeCreatePool", []); ``` ##### `getSOULfeeAddLiquidity(): number` — READ SOUL-denominated storage fee required when calling addLiquidity() on an existing pool. Parameters: none Returns: `number` — Raw SOUL amount (default: 1,250,000). Notes: Half of the pool-creation fee by default. ```js const fee = await readContract("saturnadmin", "getSOULfeeAddLiquidity", []); ``` ##### `getStorageFee(): number` — READ Current balance of SOUL fees the protocol has accrued but not yet staked. Purely informational — useful for protocol dashboards. Parameters: none Returns: `number` — Raw SOUL currently held pending staking. Notes: The protocol auto-stakes once this crosses 100,000,000 (1 SOUL), so this value resets periodically. ```js const pending = await readContract("saturnadmin", "getStorageFee", []); ``` #### Reentrancy Inspection ##### `getGuardLocked(user: address): number` — READ Returns 1 if the given user currently holds a reentrancy guard slot (meaning they are mid-transaction somewhere in the protocol), 0 otherwise. Normally this should be 0 between transactions. Parameters: - `user` (`address`): The wallet address to inspect. Returns: `number` — 1 = locked, 0 = free. Notes: If this ever returns 1 for a user outside an active call, it means a previous transaction left a stuck guard (very rare). Admin can clear it via clearReentrancy(). ```js const locked = await readContract("saturnadmin", "getGuardLocked", [userAddress]); if (locked === 1) console.warn("guard stuck for", userAddress); ``` ### SaturnPools — `saturnpools` *Pool Registry & Scaling* — Saturn DEX v4 · Core DEX Canonical source of every pool in the protocol. Stores per-pool state (provider, token pair, reserves, fee, active/pawned/lock flags), the canonical pair index, global reserves and token scale factors. Most methods here are view calls that your frontend uses to enumerate pools and read state. Two actions are available to the pool provider: updating the per-pool fee and pawning / unpawning a pool. #### Canonical Keys ##### `getCanonicalPairKey(symbolA: string, symbolB: string): string` — READ Returns the protocol's canonical key for a token pair. Symbols are sorted alphabetically so that "SOUL+KCAL" and "KCAL+SOUL" both map to the same key "KCAL_SOUL". Use this whenever you need to look up how many pools exist for a pair. Parameters: - `symbolA` (`string`): First token symbol. - `symbolB` (`string`): Second token symbol. Returns: `string` — Canonical pair key, e.g. "KCAL_SOUL". Notes: Pure function — always returns the same key for the same two symbols in any order. Never reverts. ```js const key = await readContract("saturnpools", "getCanonicalPairKey", ["SOUL", "KCAL"]); // "KCAL_SOUL" const count = await readContract("saturnpools", "getPairPoolCount", [key]); ``` ##### `getPoolPairKey(poolId: number): string` — READ Returns the canonical pair key for a specific pool. Equivalent to calling getCanonicalPairKey() with that pool's two token symbols. Parameters: - `poolId` (`number`): The pool ID. Returns: `string` — Canonical pair key for the pool's token pair. Notes: Reverts on non-existent poolId. ```js const pairKey = await readContract("saturnpools", "getPoolPairKey", [poolId]); ``` #### Scaling Helpers ##### `scaleUp(amount: number, symbol: string): number` — READ Converts a raw (on-chain decimal) amount into the protocol's internal scaled units, using the stored scale factor for that token. Use this before passing amounts to any method that expects scaled units. Parameters: - `amount` (`number`): Raw token amount. - `symbol` (`string`): Token symbol. Returns: `number` — Scaled amount (amount * scaleFactor). Notes: If the scale factor has never been computed for this token, returns the amount unchanged. Most tokens are pre-scaled during pool creation. ```js const scaled = await readContract("saturnpools", "scaleUp", [1000000, "KCAL"]); ``` ##### `scaleDown(amount: number, symbol: string): number` — READ Inverse of scaleUp — converts a scaled internal amount back into raw token units for display. Use this when reading reserves, payouts, or any number returned by a protocol view. Parameters: - `amount` (`number`): Scaled amount. - `symbol` (`string`): Token symbol. Returns: `number` — Raw token amount (scaled / scaleFactor). Notes: Rounds down (integer division). Safe to call on any token symbol. ```js const scaledReserve = await readContract("saturnpools", "getPoolReserveA", [poolId]); const tokenA = await readContract("saturnpools", "getPoolTokenA", [poolId]); const raw = await readContract("saturnpools", "scaleDown", [scaledReserve, tokenA]); ``` ##### `getScaleFactor(tokenSymbol: string): number` — READ Returns the stored scale factor for a token. A scale factor of 0 means the token has not yet been registered in any pool. Parameters: - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Multiplier used to scale raw amounts to protocol units. Notes: Returns 0 for tokens that have never been added to a pool. ```js const factor = await readContract("saturnpools", "getScaleFactor", ["SOUL"]); ``` ##### `getMinRawForToken(symbol: string, minScaledUnits: number): number` — READ Given a token and a scaled-unit minimum from SaturnAdmin (e.g. getMinScaledSwapUnits), returns the equivalent raw-unit minimum that a user must provide. The result is floored by getAbsoluteMinRaw so tiny scale factors can't produce zero minimums. Parameters: - `symbol` (`string`): Token symbol. - `minScaledUnits` (`number`): Scaled-unit threshold. Returns: `number` — Minimum raw amount the user must submit. Notes: Equivalent to the higher-level helpers SaturnRouter exposes (getMinRawForSwap/AddLiquidity). Prefer those unless you want fine control. ```js const minScaled = await readContract("saturnadmin", "getMinScaledSwapUnits", []); const minRaw = await readContract("saturnpools", "getMinRawForToken", ["KCAL", minScaled]); ``` ##### `validateTokenSymbol(symbol: string)` — READ Reverts with "Token does not exist" if the given symbol isn't registered as a Phantasma token. Useful as a cheap preflight before submitting a transaction that would otherwise fail mid-execution. Parameters: - `symbol` (`string`): Token symbol to validate. Returns: `void` — No return value — reverts on invalid token. Notes: Success = symbol exists. Failure throws and your invokeRawScript will return an error containing the revert message. ```js try { await readContract("saturnpools", "validateTokenSymbol", ["MYTOKEN"]); // token exists } catch (e) { console.error("Unknown token"); } ``` #### Per-Pool State ##### `getPoolProvider(poolId: number): address` — READ Returns the address of the wallet that created (and currently owns) the given pool. Parameters: - `poolId` (`number`): The pool ID. Returns: `address` — The pool provider's wallet address. Notes: Pool ownership is transferable via the SATURN NFT — this value reflects the current holder. ```js const provider = await readContract("saturnpools", "getPoolProvider", [poolId]); ``` ##### `getPoolTokenA(poolId: number): string` — READ Returns the symbol of the pool's first token (slot A). Note: A/B slot order is provider-chosen at creation and is NOT canonical. Parameters: - `poolId` (`number`): The pool ID. Returns: `string` — Token symbol stored in slot A. Notes: Use getCanonicalPairKey if you need a consistent ordering. ```js const tokenA = await readContract("saturnpools", "getPoolTokenA", [poolId]); ``` ##### `getPoolTokenB(poolId: number): string` — READ Returns the symbol of the pool's second token (slot B). Parameters: - `poolId` (`number`): The pool ID. Returns: `string` — Token symbol stored in slot B. Notes: Together with getPoolTokenA, identifies the pool's trading pair. ```js const tokenB = await readContract("saturnpools", "getPoolTokenB", [poolId]); ``` ##### `getPoolReserveA(poolId: number): number` — READ Returns the scaled reserve of token A in the pool. Pass the result through scaleDown() to get the raw amount for display. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — Scaled reserve of token A. Notes: Updated atomically by swap / add-liquidity / fee-accrual flows. ```js const resA = await readContract("saturnpools", "getPoolReserveA", [poolId]); const symA = await readContract("saturnpools", "getPoolTokenA", [poolId]); const displayA = await readContract("saturnpools", "scaleDown", [resA, symA]); ``` ##### `getPoolReserveB(poolId: number): number` — READ Returns the scaled reserve of token B in the pool. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — Scaled reserve of token B. Notes: Pair with getPoolReserveA for full constant-product state. ```js const resB = await readContract("saturnpools", "getPoolReserveB", [poolId]); ``` ##### `getPoolActive(poolId: number): number` — READ Returns 1 if the pool is active (can be swapped, can accept liquidity), 0 if it has been removed. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — 1 = active, 0 = removed. Notes: Removed pools keep their ID forever but are filtered out by router views. ```js const isActive = (await readContract("saturnpools", "getPoolActive", [poolId])) === 1; ``` ##### `getPoolPawned(poolId: number): number` — READ Returns 1 if the pool is currently pawned (provider has locked it against transfer / removal), 0 otherwise. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — 1 = pawned, 0 = free. Notes: Pawned pools still swap normally but can't be transferred or removed until unpawned. ```js const pawned = await readContract("saturnpools", "getPoolPawned", [poolId]); ``` ##### `getPoolNftId(poolId: number): number` — READ Returns the token ID of the SATURN NFT certificate that represents ownership of this pool. Transferring this NFT transfers pool ownership. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — SATURN NFT token ID (0 if not yet minted). Notes: Set by the SATURN NFT contract immediately after pool creation. ```js const nftId = await readContract("saturnpools", "getPoolNftId", [poolId]); ``` ##### `getPoolFee(poolId: number): number` — READ Returns the pool's per-swap fee rate in basis points out of 10,000. 300 = 3%. Range: 30–3000 (0.3%–30%). Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — Fee in basis points per 10k. Notes: Always between getMinPoolFeePer10k() and getMaxPoolFeePer10k(). ```js const feeBp = await readContract("saturnpools", "getPoolFee", [poolId]); const pct = feeBp / 100; // e.g. 300 → 3% ``` ##### `getPoolScaledLiquidity(poolId: number): number` — READ Returns the smaller of the pool's two scaled reserves — a cheap "depth" metric for ranking pools. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — min(reserveA, reserveB) in scaled units. Notes: Returns 0 if either reserve is zero (drained or uninitialized). ```js const depth = await readContract("saturnpools", "getPoolScaledLiquidity", [poolId]); ``` ##### `getPoolCampaignLockCount(poolId: number): number` — READ How many active reward campaigns currently reference this pool. When > 0, the provider can't change the pool fee or remove the pool. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — Number of active campaign locks on the pool. Notes: Managed entirely by SaturnRewards — you should never see negative values. ```js const locks = await readContract("saturnpools", "getPoolCampaignLockCount", [poolId]); ``` ##### `getPoolFinancialLockCount(poolId: number): number` — READ How many active bond, rental, option, or syndicate positions are holding this pool. While > 0, the pool cannot be removed. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — Number of financial-product locks on the pool. Notes: Incremented by bonds/rental/options/syndicate when a position opens, decremented on settle/expire/cancel. ```js const finLocks = await readContract("saturnpools", "getPoolFinancialLockCount", [poolId]); ``` #### Pair Index & Listings ##### `getPairPoolCount(pairKey: string): number` — READ Returns how many pools exist for a given canonical pair key. Use it to iterate pools for a pair with getPairPoolAtIndex(). Parameters: - `pairKey` (`string`): Canonical pair key from getCanonicalPairKey(). Returns: `number` — Number of pools for the pair. Notes: Includes removed pools in the count — always check getPoolActive() afterwards. ```js const key = await readContract("saturnpools", "getCanonicalPairKey", ["SOUL", "KCAL"]); const n = await readContract("saturnpools", "getPairPoolCount", [key]); ``` ##### `getPairPoolAtIndex(lookupKey: string): number` — READ Returns the poolId stored at a given index within a pair's pool list. The lookupKey is the canonical pair key concatenated with "_INDEX". Parameters: - `lookupKey` (`string`): Format: "_" — e.g. "KCAL_SOUL_0". Returns: `number` — Pool ID at that index (0 if none). Notes: Use in a loop from index 0 up to getPairPoolCount()-1. ```js const key = "KCAL_SOUL"; const count = await readContract("saturnpools", "getPairPoolCount", [key]); for (let i = 0; i < count; i++) { const poolId = await readContract("saturnpools", "getPairPoolAtIndex", [`${key}_${i}`]); } ``` ##### `getReserveValue(tokenSymbol: string): number` — READ Total scaled reserve of a token across every pool in the DEX. Useful for protocol-level stats. Parameters: - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Sum of all scaled reserves of the token. Notes: Scaled units — apply scaleDown() for display. ```js const scaledTotal = await readContract("saturnpools", "getReserveValue", ["SOUL"]); ``` ##### `getCountOfTokensOnList(): number` — READ Total number of unique token symbols that have ever been added to any pool. Parameters: none Returns: `number` — Token count. Notes: Monotonically increasing. ```js const n = await readContract("saturnpools", "getCountOfTokensOnList", []); ``` ##### `getCountOfPairsOnList(): number` — READ Total number of unique token pairs that have ever had at least one pool. Parameters: none Returns: `number` — Pair count. Notes: Monotonically increasing. ```js const n = await readContract("saturnpools", "getCountOfPairsOnList", []); ``` ##### `getTokensInDEXList(): string*` — READ Generator-style method that yields every token symbol registered in the DEX. Phantasma exposes this as an enumerable script call. Parameters: none Returns: `string*` — Iterable of token symbols. Notes: Order matches insertion order — tokens are appended as new pairs are created. ```js // Phantasma invokeRawScript returns all yielded values in a single list const script = ScriptBuilder .begin() .callContract("saturnpools", "getTokensInDEXList", []) .endScript(); const { decoded } = await api.invokeRawScript("main", script); ``` ##### `getPairsInDEXList(): string*` — READ Generator yielding every canonical pair key currently indexed. Parameters: none Returns: `string*` — Iterable of canonical pair keys. Notes: Safe to enumerate even on large DEXes — returns at most a few hundred keys. ```js const script = ScriptBuilder .begin() .callContract("saturnpools", "getPairsInDEXList", []) .endScript(); ``` ##### `getAllPoolIds(): number*` — READ Generator yielding every poolId that has ever been created, including removed ones. Filter with getPoolActive() if you only want live pools. Parameters: none Returns: `number*` — Iterable of pool IDs. Notes: Useful for building an explorer view. For per-pair scans prefer getPairPoolAtIndex. ```js const script = ScriptBuilder .begin() .callContract("saturnpools", "getAllPoolIds", []) .endScript(); ``` ##### `getContractTokenBalanceEach(tokenSymbol: string): number` — READ Raw on-chain token balance held by the SaturnPools contract itself — the custodial balance backing every pool's reserves. Parameters: - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Raw balance (not scaled). Notes: Used for accounting/debug. Under normal operation this should equal scaleDown(getReserveValue(symbol), symbol). ```js const balance = await readContract("saturnpools", "getContractTokenBalanceEach", ["SOUL"]); ``` #### Pool Totals ##### `getNextPoolId(): number` — READ The poolId that will be assigned to the next pool created. Parameters: none Returns: `number` — Next pool ID (starts at 1). Notes: Total pools created so far = getNextPoolId() - 1. ```js const next = await readContract("saturnpools", "getNextPoolId", []); ``` ##### `getTotalPoolCount(): number` — READ Total number of pools ever created in the protocol, including removed ones. Parameters: none Returns: `number` — Total pool count. Notes: Equivalent to allPoolIds.count(). Monotonically increasing. ```js const total = await readContract("saturnpools", "getTotalPoolCount", []); ``` #### Provider Actions ##### `updatePoolFee(from: address, poolId: number, newFeePer10k: number)` — WRITE Lets the pool provider change their pool's per-swap fee. The new fee must lie inside the protocol range (30–3000 basis points by default) and the pool must not be locked in any reward campaign. Parameters: - `from` (`address`): Provider wallet (must be witness). - `poolId` (`number`): Pool to update. - `newFeePer10k` (`number`): New fee in basis points (out of 10,000). Returns: `void` — Success = fee stored. Notes: Reverts with: "Not authorized to change fee" (not provider), "Pool locked in campaign", "Fee too low/high", or "Pool not active". ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 1500) .callContract("saturnpools", "updatePoolFee", [from, poolId, 250]) // 2.5% .spendGas(from) .endScript(); // sign with wallet, broadcast ``` ##### `pawnPool(from: address, poolId: number)` — WRITE Marks the pool as pawned. A pawned pool continues to earn and swap normally, but the provider cannot transfer or remove it — useful as collateral or a commitment signal for external products. Parameters: - `from` (`address`): Provider wallet (must be witness). - `poolId` (`number`): Pool to pawn. Returns: `void` — Success = pool marked pawned. Notes: Reverts if caller is not the provider, pool is not active, or pool is already pawned. ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 1500) .callContract("saturnpools", "pawnPool", [from, poolId]) .spendGas(from) .endScript(); ``` ##### `unpawnPool(from: address, poolId: number)` — WRITE Clears the pawned flag on a pool. Either the pool provider or the protocol admin can call this. Parameters: - `from` (`address`): Caller wallet (provider or admin). - `poolId` (`number`): Pool to unpawn. Returns: `void` — Success = pool marked free. Notes: Reverts if the pool is not pawned, or the caller is neither provider nor admin witness. ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 1500) .callContract("saturnpools", "unpawnPool", [from, poolId]) .spendGas(from) .endScript(); ``` ### SaturnLiquidity — `saturnliquidity` *Pool Creation & Liquidity* — Saturn DEX v4 · Core DEX The single entry point for creating a new pool, adding liquidity to your own pool, and removing a pool entirely. All three methods charge a SOUL-denominated storage fee (configured by SaturnAdmin), transfer tokens to custodial storage inside the protocol, and coordinate with SaturnPools and the SATURN NFT contract. Every method acquires a reentrancy guard and requires the caller to be a wallet witness. #### Pool Lifecycle ##### `createPool(from: address, amountToken0: number, amountToken1: number, token0Symbol: string, token1Symbol: string, customFeePer10k: number)` — WRITE Creates a brand new pool for a token pair. The caller sets the starting reserves and chooses the per-swap fee rate (in basis points — must fall inside the protocol range). On success the pool is registered, the SATURN NFT certificate for this pool is minted to the caller, and the caller's tokens plus a SOUL storage fee are transferred into protocol custody. Parameters: - `from` (`address`): Creator wallet (must be witness). - `amountToken0` (`number`): Raw amount of the first token to seed. - `amountToken1` (`number`): Raw amount of the second token to seed. - `token0Symbol` (`string`): Symbol of the first token. - `token1Symbol` (`string`): Symbol of the second token. - `customFeePer10k` (`number`): Per-swap fee rate in basis points (30–3000 by default). Returns: `void` — Success = pool is created and NFT certificate minted to from. Notes: Common reverts: "Same token", "Token does not exist", "Storage fee required" (needs getSOULfeeCreatePool + gas), "token0/1 needs at least N raw units" (below min pool size), "Fee too low/high". Pre-check minimums with SaturnRouter.getMinRawForPoolCreation() so the UI can clamp user inputs. ```js // Create a 0.3% fee SOUL/KCAL pool const soulFee = await readContract("saturnadmin", "getSOULfeeCreatePool", []); // User must hold: amount0 + amount1 + soulFee + gas buffer const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 3000) .callContract("saturnliquidity", "createPool", [from, 10000000000, 10000000000, "SOUL", "KCAL", 30]) .spendGas(from) .endScript(); // sign & broadcast; read resulting poolId from events / getNextPoolId() ``` ##### `addLiquidity(from: address, poolId: number, amountTokenA: number, maxAmountTokenB: number)` — WRITE Adds proportional liquidity to an existing pool. You specify exactly how much of token A you want to add; the contract calculates the matching amount of token B based on the current reserve ratio and deposits both. maxAmountTokenB is your slippage cap — the call reverts if the required B amount exceeds it. Only the pool provider can add liquidity to their own pool. Parameters: - `from` (`address`): Pool provider wallet (must be witness). - `poolId` (`number`): The pool to deposit into. - `amountTokenA` (`number`): Raw amount of token A to add. - `maxAmountTokenB` (`number`): Maximum raw amount of token B you're willing to add. Returns: `void` — Success = reserves increased and storage fee paid. Notes: Reverts on: "Only pool provider can add liquidity", "Pool is pawned - cannot modify", "Exceeds max: N" (slippage cap hit), "Rounds to zero" (amountTokenA too small), or "Min N raw units for TOKEN". Compute the expected B amount off-chain from current reserves before submitting. ```js // Read reserves to project the required B amount const resA = await readContract("saturnpools", "getPoolReserveA", [poolId]); const resB = await readContract("saturnpools", "getPoolReserveB", [poolId]); const tokenA = await readContract("saturnpools", "getPoolTokenA", [poolId]); const tokenB = await readContract("saturnpools", "getPoolTokenB", [poolId]); const scaledAddA = await readContract("saturnpools", "scaleUp", [amountA, tokenA]); const requiredBraw = await readContract("saturnpools", "scaleDown", [(scaledAddA * resB) / resA, tokenB]); const maxB = Math.floor(requiredBraw * 1.01); // 1% slippage const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2000) .callContract("saturnliquidity", "addLiquidity", [from, poolId, amountA, maxB]) .spendGas(from) .endScript(); ``` ##### `removePool(from: address, poolId: number)` — WRITE Removes a pool entirely. All scaled reserves are paid out to the provider (converted back to raw units), any pending provider fees are auto-claimed, the pool is deactivated, and the SATURN NFT certificate is burned. The pool cannot be removed while it is pawned, locked in a reward campaign, or held by any active bond / rental / option / syndicate. Parameters: - `from` (`address`): Pool provider wallet (must be witness). - `poolId` (`number`): Pool to remove. Returns: `void` — Success = pool marked inactive and tokens returned. Notes: Reverts on: "Only pool provider", "Pool is pawned", "Pool locked in reward campaign", "Pool has active financial products", or "Truncation N% on TOKEN" (scale-down rounding would lose more than getMaxTruncationPercent of a reserve). Check all locks via SaturnPools getters first to give users a clear error before they sign. ```js // Pre-flight checks const pawned = await readContract("saturnpools", "getPoolPawned", [poolId]); const campLocks = await readContract("saturnpools", "getPoolCampaignLockCount", [poolId]); const finLocks = await readContract("saturnpools", "getPoolFinancialLockCount", [poolId]); if (pawned || campLocks || finLocks) throw new Error("Pool not removable"); const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 3000) .callContract("saturnliquidity", "removePool", [from, poolId]) .spendGas(from) .endScript(); ``` ### SaturnSwap — `saturnswap` *AMM Swap Engine* — Saturn DEX v4 · Core DEX The hot path of the DEX. A constant-product AMM that reads pool reserves and the pool's custom fee rate, splits the fee between reinvestment (stays in the pool), the provider (accrued in the FeeVault), and the admin (paid immediately), and sends the output to the swapper. Exposes one public write — swap() — plus a helper to retrieve this contract's address. The internal-only swapFromContract() is reserved for limit orders, flash arbitrage, and agent vaults and is not callable directly. #### Swapping ##### `swap(from: address, poolId: number, amountIn: number, tokenIn: string, tokenOut: string, minAmountOut: number): number` — WRITE Executes a swap against a specific pool. The caller sends amountIn of tokenIn; the engine runs the constant-product formula (after the pool fee, provider split, and admin split are taken), then transfers tokenOut to the caller. Set minAmountOut to protect against slippage (pass 0 to disable the check). Always query SaturnRouter first to find the best pool for a pair — passing a sub-optimal poolId here won't revert but will give you a worse rate. Parameters: - `from` (`address`): Swapper wallet (must be witness). - `poolId` (`number`): The pool to swap against. - `amountIn` (`number`): Raw amount of tokenIn to send. - `tokenIn` (`string`): Symbol of the token being sold. - `tokenOut` (`string`): Symbol of the token being bought. - `minAmountOut` (`number`): Minimum acceptable raw amount of tokenOut. 0 = no slippage check. Returns: `number` — Raw amount of tokenOut actually received by the caller. Notes: Reverts on: "Pool not active", "Token pair mismatch" (tokens don't belong to this pool), "Below minimum swap" (use SaturnRouter.getMinRawForSwap first), "Insufficient liquidity", "Slippage exceeded", "Admin fee rounds to zero" (amount too small to pay a 1-unit admin fee), or "Cannot drain pool". ```js // 1) Find the best pool for SOUL→KCAL const best = await readContract("saturnrouter", "getBestPoolForSwap", ["SOUL", "KCAL", amountIn]); // best is an object or string containing the poolId and expected output // 2) Compute a slippage-protected minimum const minOut = Math.floor(expectedOut * 0.99); // 1% slippage // 3) Submit the swap const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2500) .callContract("saturnswap", "swap", [from, poolId, amountIn, "SOUL", "KCAL", minOut]) .spendGas(from) .endScript(); ``` #### Introspection ##### `getContractAddress(): address` — READ Returns the on-chain address of the SaturnSwap contract. Useful if your frontend needs to display token balances held by the swap engine (for example to debug settled-but-unclaimed fees), or if you're integrating another contract that must transfer tokens here before calling swapFromContract(). Parameters: none Returns: `address` — The SaturnSwap contract's address. Notes: Never reverts. The address is fixed once the protocol is deployed. ```js const swapAddr = await readContract("saturnswap", "getContractAddress", []); const balance = await fetchTokenBalance(swapAddr, "SOUL"); ``` ### SaturnFees — `saturnfees` *Provider Fee Vault* — Saturn DEX v4 · Core DEX Holds the provider's share of every swap fee until it is claimed. Each pool has two running balances — one per token in the pair — scaled to the protocol's internal precision. The provider can claim both sides at once with claimProviderFees(). When a pool is sold via a bond or rented via a rental, fee claiming is automatically redirected; the provider can see this with getFeeRedirectActive(). #### Claiming ##### `claimProviderFees(from: address, poolId: number)` — WRITE The pool provider calls this to sweep all accumulated provider fees for their pool — both tokens at once. The amounts are scaled down to raw units before being transferred out. The call reverts if the pool currently has an active fee redirect (meaning a bond or rental owns the fee stream right now). Parameters: - `from` (`address`): Pool provider wallet (must be witness). - `poolId` (`number`): Pool whose fees to claim. Returns: `void` — Success = both token balances zeroed and transferred to the provider. Notes: Reverts on: "Only pool provider", "Fees redirected by active bond or rental". Zero-balance claims are a no-op (no revert) but still cost gas — query getProviderClaimable() first to decide whether to submit. ```js // Check claimable on both sides before claiming const tokenA = await readContract("saturnpools", "getPoolTokenA", [poolId]); const tokenB = await readContract("saturnpools", "getPoolTokenB", [poolId]); const pA = await readContract("saturnfees", "getProviderClaimable", [poolId, tokenA]); const pB = await readContract("saturnfees", "getProviderClaimable", [poolId, tokenB]); if (pA > 0 || pB > 0) { const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 1500) .callContract("saturnfees", "claimProviderFees", [from, poolId]) .spendGas(from) .endScript(); } ``` #### Views ##### `getProviderClaimable(poolId: number, tokenSymbol: string): number` — READ Returns the scaled amount of a specific token currently claimable by the pool's provider. Call this once for tokenA and once for tokenB to show a complete balance. Apply SaturnPools.scaleDown() before showing the number to users. Parameters: - `poolId` (`number`): The pool ID. - `tokenSymbol` (`string`): Either token in the pair. Returns: `number` — Scaled pending fee amount for that token. Notes: Returns 0 if no fees are pending, or if the wrong token symbol is passed. Never reverts. ```js const scaled = await readContract("saturnfees", "getProviderClaimable", [poolId, "SOUL"]); const raw = await readContract("saturnpools", "scaleDown", [scaled, "SOUL"]); ``` ##### `getFeeRedirectActive(poolId: number): number` — READ Returns 1 if the pool currently has a fee redirect active (the fee stream is owned by a bond or rental), 0 otherwise. Use this to disable the "Claim Fees" button in your UI and show the right explanation to the provider. Parameters: - `poolId` (`number`): The pool ID. Returns: `number` — 1 = fees redirected, 0 = free to claim. Notes: Redirects are set by SaturnBonds when a bond is purchased and by SaturnRental when a rental starts; they're cleared on settlement / end. ```js const redirected = await readContract("saturnfees", "getFeeRedirectActive", [poolId]); if (redirected === 1) { // UI: "Fees are currently being earned by a bond/rental holder" } ``` ### SATURN — `SATURN` *Pool Ownership NFT* — Saturn DEX v4 · Core DEX The SATURN token is a non-fungible certificate representing ownership of a pool. One unique NFT is minted to the provider when they call SaturnLiquidity.createPool() and burned when they call removePool(). Transferring the NFT transfers pool ownership — the new holder earns the provider fee split and can close the pool. Minting and burning are handled automatically by SaturnLiquidity and are not callable directly; the public methods below are the reads your frontend will need. #### Views ##### `getTotalNftMinted(): number` — READ Total number of SATURN pool NFTs currently in circulation. Equivalent to the total number of active pools (burned NFTs are not counted). Parameters: none Returns: `number` — Live SATURN NFT count. Notes: Increases by one on every createPool() and decreases by one on every removePool(). Useful for a protocol dashboard. ```js const activePools = await readContract("SATURN", "getTotalNftMinted", []); ``` ##### `getUserPools(from: address): number*` — READ Generator yielding every SATURN NFT token ID currently owned by the given address. Each token ID maps to a specific pool — read the ROM metadata of each NFT (via Phantasma's NFT API) to get the associated poolId, token pair, and display name. Parameters: - `from` (`address`): The wallet to inspect. Returns: `number*` — Iterable of SATURN NFT token IDs held by the address. Notes: Returns nothing for wallets that don't own any pools. Use this to populate a "My Pools" dashboard — resolve each token ID to a poolId by reading the NFT's ROM.poolId field. ```js const script = ScriptBuilder .begin() .callContract("SATURN", "getUserPools", [userAddress]) .endScript(); const { decoded } = await api.invokeRawScript("main", script); // For each returned nftId, fetch the NFT metadata to get poolId: for (const nftId of decoded) { const nft = await api.getNFT("SATURN", nftId); console.log("Pool #" + nft.properties.poolId, nft.properties.name); } ``` ### SaturnRewards — `saturnrewards` *Liquidity Mining Campaigns* — Saturn DEX v4 · Core DEX Anyone can fund a reward campaign for a specific token pair. Pool providers opt in by enrolling; at enrollment time the pool's scaled liquidity is snapshot and used to compute a proportional share of the reward pot at the end. Enrolled pools are locked (their fee cannot be changed and they cannot be removed) until the provider claims or withdraws. The campaign creator can reclaim any unclaimed rewards after a 30-day grace period following the end time. #### Campaign Lifecycle ##### `createCampaign(from: address, tokenA: string, tokenB: string, rewardToken: string, rewardAmount: number, durationSeconds: number)` — WRITE Creates a new reward campaign targeting a specific token pair. The full rewardAmount of rewardToken is transferred up front from the creator's wallet into the contract, and will be distributed proportionally to pools that enroll during the campaign window. Parameters: - `from` (`address`): Campaign creator wallet (must be witness). - `tokenA` (`string`): First token in the target pair. - `tokenB` (`string`): Second token in the target pair. - `rewardToken` (`string`): Token paid out as the reward. - `rewardAmount` (`number`): Total raw reward amount to escrow. - `durationSeconds` (`number`): Campaign length in seconds. Min 86400 (1 day), max 31536000 (1 year). Returns: `void` — Success = campaign stored and reward token deposited. Notes: Reverts with: "No pools exist for this pair", "Reward must be > 0", "Min duration: 1 day", "Max duration: 1 year", or "Insufficient reward token balance". ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2500) .callContract("saturnrewards", "createCampaign", [from, "SOUL", "KCAL", "GHOST", 1000000000, 7 * 86400]) // 7-day GHOST campaign .spendGas(from) .endScript(); ``` ##### `enrollInCampaign(from: address, poolId: number, campaignId: number)` — WRITE Called by a pool provider to enroll their pool in an active campaign. The pool's current scaled liquidity is snapshot and added to the campaign's total locked liquidity — the provider's final reward is proportional to this snapshot, not to the pool's future depth. Enrollment locks the pool (cannot change fee, cannot remove) until the reward is claimed or withdrawn. Parameters: - `from` (`address`): Pool provider wallet (must be witness). - `poolId` (`number`): Pool to enroll. - `campaignId` (`number`): Campaign to enroll into. Returns: `void` — Success = pool enrolled and campaign lock incremented. Notes: Reverts on: "Only pool provider", "Campaign already ended", "Pool pair does not match campaign pair", "Already enrolled", "Pool has no liquidity", or "Campaign not active". ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2000) .callContract("saturnrewards", "enrollInCampaign", [from, poolId, campaignId]) .spendGas(from) .endScript(); ``` ##### `claimCampaignReward(from: address, poolId: number, campaignId: number)` — WRITE Called after the campaign's end time. Transfers the provider's proportional share of the reward pot (snapshotLiquidity / totalLockedLiquidity × totalReward) and removes the campaign lock from the pool. Can only be called once per enrollment. Parameters: - `from` (`address`): Pool provider wallet (must be witness). - `poolId` (`number`): Enrolled pool. - `campaignId` (`number`): Campaign being claimed. Returns: `void` — Success = reward transferred and pool unlocked. Notes: Reverts on: "Only pool provider", "Not enrolled", "Already claimed", "Campaign not ended yet", or "Reward rounds to zero". Preview the expected reward with getExpectedReward() before submitting. ```js const expected = await readContract("saturnrewards", "getExpectedReward", [poolId, campaignId]); if (expected > 0) { const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2000) .callContract("saturnrewards", "claimCampaignReward", [from, poolId, campaignId]) .spendGas(from) .endScript(); } ``` ##### `withdrawFromCampaign(from: address, poolId: number, campaignId: number)` — WRITE Early exit for a provider who wants out before the campaign ends. Forfeits the pool's reward share — the forfeited amount is absorbed by the remaining participants — and unlocks the pool immediately. Parameters: - `from` (`address`): Pool provider wallet (must be witness). - `poolId` (`number`): Enrolled pool. - `campaignId` (`number`): Campaign to withdraw from. Returns: `void` — Success = enrollment cleared and pool unlocked. Notes: Reverts on: "Only pool provider", "Not enrolled", or "Already claimed". Use this only if the provider really wants to give up their share. ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2000) .callContract("saturnrewards", "withdrawFromCampaign", [from, poolId, campaignId]) .spendGas(from) .endScript(); ``` ##### `endCampaign(from: address, campaignId: number)` — WRITE Called by the campaign creator at least 30 days after the end time. Marks the campaign inactive and returns any unclaimed reward tokens to the creator. The grace period gives providers plenty of time to claim before the creator can reclaim anything. Parameters: - `from` (`address`): Original campaign creator wallet (must be witness). - `campaignId` (`number`): Campaign to end. Returns: `void` — Success = campaign closed and unclaimed tokens returned. Notes: Reverts on: "Only campaign creator", "Campaign not active", or "Wait 30 days after end for providers to claim". ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2000) .callContract("saturnrewards", "endCampaign", [from, campaignId]) .spendGas(from) .endScript(); ``` #### Campaign Views ##### `getCampaignInfo(campaignId: number): string` — READ Returns a single packed string with every top-level field of a campaign. Cheap to render — one round-trip instead of eight. Parse client-side by splitting on underscores. Parameters: - `campaignId` (`number`): Campaign ID. Returns: `string` — Format: "pair:_reward:_total:_claimed:_start:_end:_active:<0|1>_locked:". Notes: Suitable for list views. For numeric math prefer the single-value getters. ```js const info = await readContract("saturnrewards", "getCampaignInfo", [campaignId]); // e.g. "pair:KCAL_SOUL_reward:GHOST_total:1000000000_claimed:0_start:1700000000_end:1700604800_active:1_locked:0" ``` ##### `getCampaignActive(campaignId: number): number` — READ Returns 1 if the campaign is still active, 0 if it has been ended by the creator. Parameters: - `campaignId` (`number`): Campaign ID. Returns: `number` — 1 = active, 0 = ended. Notes: "Active" refers to the campaign's lifecycle state, not whether time has expired — the creator must explicitly call endCampaign(). ```js const active = await readContract("saturnrewards", "getCampaignActive", [campaignId]); ``` ##### `getCampaignRewardToken(campaignId: number): string` — READ Symbol of the token being distributed as the campaign reward. Parameters: - `campaignId` (`number`): Campaign ID. Returns: `string` — Reward token symbol. Notes: Set at creation time and immutable thereafter. ```js const token = await readContract("saturnrewards", "getCampaignRewardToken", [campaignId]); ``` ##### `getCampaignRewardTotal(campaignId: number): number` — READ Total reward amount that was escrowed at creation (raw units). Parameters: - `campaignId` (`number`): Campaign ID. Returns: `number` — Raw total reward amount. Notes: Constant once the campaign exists. ```js const total = await readContract("saturnrewards", "getCampaignRewardTotal", [campaignId]); ``` ##### `getCampaignTotalLocked(campaignId: number): number` — READ Sum of the snapshot liquidity of every pool currently enrolled in the campaign. Parameters: - `campaignId` (`number`): Campaign ID. Returns: `number` — Scaled sum of enrolled liquidity. Notes: Grows as pools enroll, shrinks when pools withdraw early. Combine with getPoolCampaignLiquidity() to compute share percentages. ```js const totalLiq = await readContract("saturnrewards", "getCampaignTotalLocked", [campaignId]); ``` ##### `getCampaignEndTime(campaignId: number): number` — READ Unix timestamp (seconds) at which the campaign ends and providers can begin claiming. Parameters: - `campaignId` (`number`): Campaign ID. Returns: `number` — End timestamp. Notes: Set at creation = startTime + durationSeconds. Immutable. ```js const endTs = await readContract("saturnrewards", "getCampaignEndTime", [campaignId]); const msLeft = Math.max(0, endTs * 1000 - Date.now()); ``` ##### `getNextCampaignId(): number` — READ The ID that will be assigned to the next campaign created. Parameters: none Returns: `number` — Next campaign ID (starts at 1). Notes: Total campaigns so far = getNextCampaignId() - 1. ```js const nextId = await readContract("saturnrewards", "getNextCampaignId", []); ``` ##### `getAllCampaignIds(): number*` — READ Generator yielding every campaignId ever created, including ended ones. Parameters: none Returns: `number*` — Iterable of campaign IDs. Notes: Combine with getCampaignActive() to filter to running campaigns only. ```js const script = ScriptBuilder .begin() .callContract("saturnrewards", "getAllCampaignIds", []) .endScript(); ``` #### Pool Enrollment Views ##### `getPoolEnrolledInCampaign(poolId: number, campaignId: number): number` — READ Returns 1 if the pool is currently enrolled in the given campaign, 0 otherwise. Parameters: - `poolId` (`number`): Pool ID. - `campaignId` (`number`): Campaign ID. Returns: `number` — 1 = enrolled, 0 = not enrolled. Notes: Cleared to 0 when the provider claims or withdraws. ```js const enrolled = await readContract("saturnrewards", "getPoolEnrolledInCampaign", [poolId, campaignId]); ``` ##### `getPoolCampaignLiquidity(poolId: number, campaignId: number): number` — READ Returns the snapshot liquidity this pool had at the moment it enrolled. This is the weight used in the proportional reward calculation — it does NOT update as the pool's reserves change later. Parameters: - `poolId` (`number`): Pool ID. - `campaignId` (`number`): Campaign ID. Returns: `number` — Scaled liquidity at enrollment time. Notes: Zero if the pool is not enrolled. ```js const snap = await readContract("saturnrewards", "getPoolCampaignLiquidity", [poolId, campaignId]); ``` ##### `getPoolCampaignClaimed(poolId: number, campaignId: number): number` — READ Returns the raw reward amount already claimed for this (pool, campaign) pair. Zero until the provider calls claimCampaignReward(). Parameters: - `poolId` (`number`): Pool ID. - `campaignId` (`number`): Campaign ID. Returns: `number` — Amount already claimed. Notes: Non-zero = already claimed. Use to hide the claim button in the UI. ```js const claimed = await readContract("saturnrewards", "getPoolCampaignClaimed", [poolId, campaignId]); ``` ##### `getExpectedReward(poolId: number, campaignId: number): number` — READ Computes the raw reward amount the provider will receive if they claim right now, given the current totalLocked. The number will drift if other pools enroll or withdraw before the campaign ends — always requery just before submitting a claim. Parameters: - `poolId` (`number`): Enrolled pool. - `campaignId` (`number`): Campaign the pool is enrolled in. Returns: `number` — Projected reward in raw units (0 if not enrolled). Notes: Returns 0 if the pool isn't enrolled or if totalLocked is zero. ```js const projected = await readContract("saturnrewards", "getExpectedReward", [poolId, campaignId]); ``` ### SaturnRouter — `saturnrouter` *Router & Aggregated Views* — Saturn DEX v4 · Core DEX The recommended entry point for any frontend or SDK. SaturnRouter exposes read-only helpers that pick the best pool for a swap, compute the raw minimums a user must meet before they submit a transaction, return aggregated pool info in a single call, and re-export the most useful values from SaturnAdmin and SaturnPools so your app only needs to know one contract name. Everything here is pure view — no witness, no gas, no side effects. #### Swap Routing ##### `getBestPoolForSwap(tokenIn: string, tokenOut: string, amountIn: number): number` — READ Iterates every active pool for the token pair and returns the poolId that would give the highest net output after that pool's fee. This is the first call your swap flow should make — pass the result into SaturnSwap.swap(). Parameters: - `tokenIn` (`string`): Symbol of the token being sold. - `tokenOut` (`string`): Symbol of the token being bought. - `amountIn` (`number`): Raw amount the user wants to sell. Returns: `number` — Pool ID with the best output, or 0 if no viable pool exists. Notes: Returns 0 when no pools exist for the pair, or every candidate pool has zero reserves. Safe to run on large pair sets (loops in O(n) over pools for the pair). ```js const poolId = await readContract("saturnrouter", "getBestPoolForSwap", ["SOUL", "KCAL", 10000000000]); if (poolId === 0) throw new Error("No liquidity available"); ``` ##### `getPoolPrice(poolId: number, tokenIn: string): number` — READ Returns the current spot price of tokenIn expressed in the other token of the pool. The result is scaled by 1e8 so you can divide by 100_000_000 on the client to get a floating-point ratio. Parameters: - `poolId` (`number`): Pool to quote. - `tokenIn` (`string`): Token you want the price of. Returns: `number` — Price × 1e8 (0 if the relevant reserve is empty). Notes: No slippage adjustment — this is the marginal price before any trade. ```js const scaled = await readContract("saturnrouter", "getPoolPrice", [poolId, "SOUL"]); const price = scaled / 1e8; // display price of SOUL in the other token ``` ##### `getPoolCountForPair(tokenA: string, tokenB: string): number` — READ Shortcut for getCanonicalPairKey → getPairPoolCount. Returns the number of pools (active or removed) that exist for a pair. Parameters: - `tokenA` (`string`): First token symbol. - `tokenB` (`string`): Second token symbol. Returns: `number` — Number of pools for the pair. Notes: Includes removed pools — check getPoolActive() when iterating. ```js const n = await readContract("saturnrouter", "getPoolCountForPair", ["SOUL", "KCAL"]); ``` ##### `getPoolIdForPairAtIndex(tokenA: string, tokenB: string, index: number): number` — READ Returns the poolId at a given index in the pair's pool list. Use together with getPoolCountForPair() to iterate every pool for a pair without building a canonical key yourself. Parameters: - `tokenA` (`string`): First token symbol. - `tokenB` (`string`): Second token symbol. - `index` (`number`): Zero-based index. Returns: `number` — Pool ID at that index (0 if out of range). Notes: Indices are stable — removed pools keep their slot. ```js const count = await readContract("saturnrouter", "getPoolCountForPair", ["SOUL", "KCAL"]); for (let i = 0; i < count; i++) { const pid = await readContract("saturnrouter", "getPoolIdForPairAtIndex", ["SOUL", "KCAL", i]); } ``` #### Aggregated Views ##### `getPoolFullInfo(poolId: number): string` — READ One-call view that returns every field a frontend typically needs to render a pool row: tokens, scaled reserves, fee rate, active flag, pawned flag, and campaign lock count, packed into a single underscore-delimited string. Parameters: - `poolId` (`number`): Pool ID. Returns: `string` — Format: "tokenA:_tokenB:_resA:_resB:_fee:_active:<0|1>_pawned:<0|1>_locks:". Notes: Much cheaper than eight individual getPool* round trips if you render many pools at once. ```js const info = await readContract("saturnrouter", "getPoolFullInfo", [poolId]); const parts = Object.fromEntries( info.split("_").map(p => { const [k, ...v] = p.split(":"); return [k, v.join(":")]; }) ); ``` ##### `getProviderClaimable(poolId: number, tokenSymbol: string): number` — READ Convenience re-export of SaturnFees.getProviderClaimable() so your app can hit a single contract. Parameters: - `poolId` (`number`): Pool ID. - `tokenSymbol` (`string`): Either token in the pair. Returns: `number` — Scaled pending provider fees for that token. Notes: Same return semantics as SaturnFees.getProviderClaimable. ```js const pending = await readContract("saturnrouter", "getProviderClaimable", [poolId, "SOUL"]); ``` ##### `getPoolScaledLiquidity(poolId: number): number` — READ Re-export of SaturnPools.getPoolScaledLiquidity — cheap "depth" metric for ranking pools. Parameters: - `poolId` (`number`): Pool ID. Returns: `number` — min(reserveA, reserveB) in scaled units. Notes: 0 if either reserve is zero. ```js const depth = await readContract("saturnrouter", "getPoolScaledLiquidity", [poolId]); ``` #### Raw Minimums ##### `getMinRawForPoolCreation(tokenSymbol: string): number` — READ Returns the minimum raw amount of a token a provider must supply when calling createPool(). Already applies the token's scale factor and the absolute floor from SaturnAdmin. Parameters: - `tokenSymbol` (`string`): Token symbol to quote. Returns: `number` — Minimum raw amount for pool creation. Notes: Use this to pre-populate the pool-creation form and reject bad inputs before the user signs. ```js const minA = await readContract("saturnrouter", "getMinRawForPoolCreation", ["SOUL"]); const minB = await readContract("saturnrouter", "getMinRawForPoolCreation", ["KCAL"]); ``` ##### `getMinRawForSwap(tokenSymbol: string): number` — READ Returns the minimum raw amount a user must send as the input of a swap. Enforces both the scaled-units minimum and the absolute floor. Parameters: - `tokenSymbol` (`string`): Input token symbol. Returns: `number` — Minimum raw amount per swap. Notes: Show this as the "minimum swap" hint next to the input field. ```js const minSwap = await readContract("saturnrouter", "getMinRawForSwap", ["SOUL"]); ``` ##### `getMinRawForAddLiquidity(tokenSymbol: string): number` — READ Returns the minimum raw amount of token A a user must add in a single addLiquidity() call. Parameters: - `tokenSymbol` (`string`): Token A symbol. Returns: `number` — Minimum raw amount per add-liquidity. Notes: Token B's required amount is derived proportionally from current reserves — no minimum is enforced on B directly. ```js const minAdd = await readContract("saturnrouter", "getMinRawForAddLiquidity", ["SOUL"]); ``` #### Protocol Config Passthroughs ##### `getScaleFactor(tokenSymbol: string): number` — READ Re-export of SaturnPools.getScaleFactor — the multiplier used to go from raw units to scaled units. Parameters: - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Scale factor (0 = never seen). Notes: Same as SaturnPools.getScaleFactor. ```js const f = await readContract("saturnrouter", "getScaleFactor", ["SOUL"]); ``` ##### `getTargetDecimals(): number` — READ Re-export of SaturnAdmin.getTargetDecimals (8 by default). Parameters: none Returns: `number` — Internal target decimals. Notes: Never changes in practice. ```js const dec = await readContract("saturnrouter", "getTargetDecimals", []); ``` ##### `getMaxTruncationPercent(): number` — READ Re-export of SaturnAdmin.getMaxTruncationPercent (default 10). Parameters: none Returns: `number` — Maximum truncation loss allowed on removePool. Notes: Use to warn providers when a small-decimal token would push their removal close to the limit. ```js const maxTrunc = await readContract("saturnrouter", "getMaxTruncationPercent", []); ``` ##### `getFeeSplitRatios(): string` — READ Re-export of SaturnAdmin.getFeeSplitRatios. Returns the full reinvest / provider / admin breakdown in one string. Parameters: none Returns: `string` — "reinvest:X_provider:Y_admin:Z". Notes: Parts always sum to 100. ```js const split = await readContract("saturnrouter", "getFeeSplitRatios", []); ``` ##### `getPoolFeeRange(): string` — READ Re-export of SaturnAdmin.getPoolFeeRange. Returns both min and max per-pool fee rates packed into one string. Parameters: none Returns: `string` — "min:X_max:Y" in basis points. Notes: Use to clamp the fee slider in the pool-creation form. ```js const range = await readContract("saturnrouter", "getPoolFeeRange", []); ``` ## Advanced Pools & Capital Concentrated-liquidity ranges, sandwich-resistant TWAMM streams, Aave-style flash loans, and stake-to-earn holder rewards. ### SaturnCLPools — `saturnclpools` *Concentrated Liquidity Pools* — Saturn DEX v4 · Advanced Pools & Capital Range-bound AMM that allows liquidity providers to concentrate capital within a chosen price band (priceMin–priceMax), dramatically increasing capital efficiency relative to a full-range pool. Swaps execute the standard xy=k formula but revert the moment the resulting price would exit the declared range — making the pool's behaviour predictable and composable. Build on it to offer narrow-spread stablecoin pairs, pegged-asset vaults, or any strategy that benefits from tighter spreads without the dilution of idle out-of-range reserves. #### Pool Lifecycle ##### `createClPool(from: address, tokenA: string, tokenB: string, amountA: number, amountB: number, priceMin: number, priceMax: number, feePer10k: number)` — WRITE Creates a new concentrated liquidity pool seeded with the caller's initial liquidity. The initial price (scaledB / scaledA × 1e8) must fall inside [priceMin, priceMax]; the call reverts otherwise. The pool is immediately active, assigned a monotonically increasing poolId, and indexed under the canonical pair key so router views can discover it. Both tokens must pass the saturnpools symbol validator. Only the creator (from) can later add or remove liquidity. Parameters: - `from` (`address`): Wallet that owns and seeds the pool; must be the transaction signer. - `tokenA` (`string`): Symbol of the first token (e.g. "SOUL"). - `tokenB` (`string`): Symbol of the second token (e.g. "KCAL"). - `amountA` (`number`): Raw (unscaled) amount of tokenA to deposit. - `amountB` (`number`): Raw (unscaled) amount of tokenB to deposit. - `priceMin` (`number`): Lower bound of the price range (scaled: tokenB per tokenA × 1e8). Must be > 0. - `priceMax` (`number`): Upper bound of the price range. Must exceed priceMin. - `feePer10k` (`number`): Swap fee in basis points out of 10,000 (e.g. 30 = 0.3%). Must be within protocol min/max. Notes: Reverts if tokenA == tokenB, either amount is 0, priceMin >= priceMax, the initial price is outside the range, or feePer10k is outside the protocol-configured min/max (read from saturnadmin). The reentrancy guard is held for the duration; do not call re-entrantly. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnclpools", "createClPool", [ from, "SOUL", "KCAL", 1000000000, // amountA: 10 SOUL (8-decimal example) 5000000000, // amountB: 50 KCAL 40000000, // priceMin: 0.4 KCAL/SOUL (× 1e8) 60000000, // priceMax: 0.6 KCAL/SOUL (× 1e8) 30 // feePer10k: 0.3% ]) .spendGas(from) .endScript(); ``` ##### `removeClPool(from: address, poolId: number)` — WRITE Permanently deactivates a CL pool and returns all reserves plus any unclaimed fees to the pool provider. Only the original creator of the pool may call this. After removal the pool cannot be reactivated; poolId remains in storage with active=0. Any accumulated fees are bundled into the refund — a separate claimClFees call is not required. Parameters: - `from` (`address`): Pool provider; must match the address stored at pool creation. - `poolId` (`number`): Numeric ID of the CL pool to remove. Notes: Reverts if the pool is already inactive (active != 1) or if from is not the pool provider. Emits ClPoolRemoved with the exact amounts returned for both tokens plus the fee amounts bundled in. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnclpools", "removeClPool", [from, poolId]) .spendGas(from) .endScript(); ``` #### Liquidity Management ##### `addClLiquidity(from: address, poolId: number, amountA: number, maxAmountB: number)` — WRITE Adds liquidity to an existing active CL pool at the current ratio. The amount of tokenB actually deposited is calculated from the pool's current reserves and the supplied amountA; if that computed amount exceeds maxAmountB the call reverts, giving the caller a slippage guard. Only the pool provider (the original creator) can add liquidity. Both token amounts are transferred from the caller to the liquidity vault. Parameters: - `from` (`address`): Pool provider; must be the original pool creator and transaction signer. - `poolId` (`number`): ID of the CL pool to supply. - `amountA` (`number`): Raw amount of tokenA to add. - `maxAmountB` (`number`): Maximum raw amount of tokenB the caller is willing to deposit (slippage cap). Notes: Reverts if pool is inactive, from is not the provider, amountA is 0, or the computed tokenB amount exceeds maxAmountB. The exact tokenB amount deposited is logged in the ClLiquidityAdded event. ```js // Read the current price first to estimate required tokenB const resA = await readContract("saturnclpools", "getClPoolReserveA", [poolId]); const resB = await readContract("saturnclpools", "getClPoolReserveB", [poolId]); const addA = 500000000n; const estimatedB = (addA * BigInt(resB)) / BigInt(resA); const maxB = estimatedB + (estimatedB / 100n); // +1% slippage const tx = sb.begin() .allowGas(from) .callContract("saturnclpools", "addClLiquidity", [from, poolId, addA, maxB]) .spendGas(from) .endScript(); ``` #### Swapping ##### `swapClPool(from: address, poolId: number, amountIn: number, tokenIn: string, tokenOut: string, minAmountOut: number): number` — WRITE Executes a swap against a specific CL pool using the xy=k formula. The fee (feePer10k basis points) is deducted from amountIn before the AMM math runs, and is accumulated in the pool for the provider to claim later. The swap reverts if the resulting price would fall outside the pool's declared [priceMin, priceMax] range — this is the core CL invariant. Returns the actual raw output amount delivered to the caller. Use minAmountOut to guard against slippage. Parameters: - `from` (`address`): Trader; must be the transaction signer. - `poolId` (`number`): ID of the CL pool to swap through. - `amountIn` (`number`): Raw amount of tokenIn to sell. - `tokenIn` (`string`): Symbol of the input token (must be one of the pool's pair). - `tokenOut` (`string`): Symbol of the output token (the other side of the pair). - `minAmountOut` (`number`): Minimum acceptable raw output; reverts if output is below this (slippage guard). Returns: `number` — Raw amount of tokenOut received by the caller after fee deduction. Notes: Reverts if: pool inactive; tokenIn == tokenOut; token pair does not match pool; either reserve is zero; fee consumes entire input; output rounds to zero; output < minAmountOut; or resulting price would push outside [priceMin, priceMax]. Always quote via getClPoolPrice() and getClPoolInfo() before submitting to set a reasonable minAmountOut. ```js // Preview the current price, then swap const price = await readContract("saturnclpools", "getClPoolPrice", [poolId]); console.log("Current price (scaled 1e8):", price); const tx = sb.begin() .allowGas(from) .callContract("saturnclpools", "swapClPool", [ from, poolId, 100000000, "SOUL", "KCAL", 0 ]) .spendGas(from) .endScript(); ``` #### Fee Collection ##### `claimClFees(from: address, poolId: number)` — WRITE Withdraws all accumulated swap fees from both sides of a CL pool to the pool provider. Fees are denominated in the raw token amounts that were charged at swap time. After claiming, the fee accumulators reset to zero. The pool does not need to be active — fees can be claimed even on an inactive pool (though removeClPool bundles fees into the refund anyway, so a separate claim before removal is optional). Parameters: - `from` (`address`): Pool provider; must match the stored provider address. - `poolId` (`number`): ID of the CL pool whose fees to collect. Notes: Reverts if from is not the pool provider or if both fee accumulators are zero (nothing to claim). Emits ClFeesClaimed with feesA and feesB amounts. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnclpools", "claimClFees", [from, poolId]) .spendGas(from) .endScript(); ``` #### Pool Views ##### `getContractVersion(): string` — READ Returns the current contract version string, useful for verifying on-chain deployment matches your SDK expectations. Parameters: none Returns: `string` — Version string, e.g. "saturnclpools-4.2.4". ```js const version = await readContract("saturnclpools", "getContractVersion", []); ``` ##### `getClPoolInfo(poolId: number): string` — READ Returns all core pool fields packed into a single underscore-delimited string: "tokenA:X_tokenB:Y_resA:N_resB:N_priceMin:N_priceMax:N_fee:N_active:N". Reserves are in scaled (internal) units. Use this for a single-round-trip refresh of a known pool. Parameters: - `poolId` (`number`): ID of the CL pool to inspect. Returns: `string` — Packed field string. Parse by splitting on "_" then on ":". Notes: Returns the storage values as-is; no validation. Reserves are scaled (internal decimals), not raw user-facing amounts. ```js const info = await readContract("saturnclpools", "getClPoolInfo", [poolId]); // "tokenA:SOUL_tokenB:KCAL_resA:1000000000_resB:5000000000_priceMin:40000000_priceMax:60000000_fee:30_active:1" const fields = Object.fromEntries(info.split("_").map(f => f.split(":"))); ``` ##### `getClPoolProvider(poolId: number): address` — READ Returns the address of the wallet that created and owns the specified CL pool. Parameters: - `poolId` (`number`): CL pool ID. Returns: `address` — Pool provider address. ##### `getClPoolTokenA(poolId: number): string` — READ Returns the symbol of the first token in the pool's pair. Parameters: - `poolId` (`number`): CL pool ID. Returns: `string` — Token symbol, e.g. "SOUL". ##### `getClPoolTokenB(poolId: number): string` — READ Returns the symbol of the second token in the pool's pair. Parameters: - `poolId` (`number`): CL pool ID. Returns: `string` — Token symbol, e.g. "KCAL". ##### `getClPoolReserveA(poolId: number): number` — READ Returns the current scaled reserve of tokenA. Divide by the token's scale factor to get the user-facing amount. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — Scaled tokenA reserve (internal units). ##### `getClPoolReserveB(poolId: number): number` — READ Returns the current scaled reserve of tokenB. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — Scaled tokenB reserve (internal units). ##### `getClPoolPriceMin(poolId: number): number` — READ Returns the lower bound of the pool's active price range. Price is stored as (reserveB / reserveA) × 1e8. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — Minimum price (scaled 1e8). ##### `getClPoolPriceMax(poolId: number): number` — READ Returns the upper bound of the pool's active price range. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — Maximum price (scaled 1e8). ##### `getClPoolFeePer10k(poolId: number): number` — READ Returns the pool's swap fee in basis points out of 10,000. For example, 30 means 0.3%. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — Fee in basis points (e.g. 30 = 0.3%). ##### `getClPoolActive(poolId: number): number` — READ Returns 1 if the pool is active and accepting swaps/liquidity, 0 if it has been removed. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — 1 = active, 0 = removed/inactive. ##### `getClPoolFeesARaw(poolId: number): number` — READ Returns the current unclaimed fee accumulator for tokenA, in raw (unscaled) units. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — Unclaimed tokenA fees (raw units). ##### `getClPoolFeesBRaw(poolId: number): number` — READ Returns the current unclaimed fee accumulator for tokenB, in raw (unscaled) units. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — Unclaimed tokenB fees (raw units). ##### `getClPoolPrice(poolId: number): number` — READ Computes and returns the current spot price of the pool as (reserveB × 1e8) / reserveA. Returns 0 if reserveA is zero (pool drained or not yet funded). Use this to display a live price quote or to estimate slippage before swapping. Parameters: - `poolId` (`number`): CL pool ID. Returns: `number` — Current price, scaled by 1e8 (tokenB per tokenA). 0 if pool has no tokenA reserve. ```js const price = await readContract("saturnclpools", "getClPoolPrice", [poolId]); const humanPrice = Number(price) / 1e8; // e.g. 0.5 KCAL/SOUL ``` ##### `getNextClPoolId(): number` — READ Returns the ID that will be assigned to the next CL pool created. Pool IDs are sequential starting from 1. Parameters: none Returns: `number` — Next available pool ID. ##### `getClPairPoolCount(pairKey: string): number` — READ Returns the number of CL pools that exist for a given canonical pair key (as produced by saturnpools.getCanonicalPairKey). Use this to paginate pair pools before fetching them with getClPairPoolAtIndex. Parameters: - `pairKey` (`string`): Canonical pair key, e.g. the value returned by saturnpools.getCanonicalPairKey("SOUL", "KCAL"). Returns: `number` — Number of CL pools for the pair. ```js const count = await readContract("saturnclpools", "getClPairPoolCount", [pairKey]); for (let i = 0; i < count; i++) { const pid = await readContract("saturnclpools", "getClPairPoolAtIndex", [pairKey + "_" + i]); } ``` ##### `getClPairPoolAtIndex(lookupKey: string): number` — READ Returns the poolId stored at a specific index under a pair key. The lookupKey format is "_", e.g. "KCAL~SOUL_0". Iterate from 0 to getClPairPoolCount(pairKey)-1 to enumerate all pools for a pair. Parameters: - `lookupKey` (`string`): Compound key formed as pairKey + "_" + index (e.g. "KCAL~SOUL_0"). Returns: `number` — Pool ID at that index. ##### `getAllClPoolIds(): number*` — READ Streams all ever-created CL pool IDs (including inactive ones) as a sequence. Use getActiveClPoolIds() if you only want live pools. Parameters: none Returns: `number*` — Sequence of all CL pool IDs (active and inactive). ```js const allIds = await readContract("saturnclpools", "getAllClPoolIds", []); ``` ##### `getActiveClPoolIds(): number*` — READ Streams the IDs of all currently active (non-removed) CL pools. More efficient for UI discovery than getAllClPoolIds() when removed pools should be hidden. Parameters: none Returns: `number*` — Sequence of active CL pool IDs. ```js const activeIds = await readContract("saturnclpools", "getActiveClPoolIds", []); ``` ##### `getActiveClPoolsData(): string*` — READ Batch view that streams one encoded row per active CL pool — eliminates N round-trips when building a pool-list UI. Each row is pipe-delimited: "poolId|provider|tokenA|tokenB|reserveA|reserveB|priceMin|priceMax|fee|active". Reserves are in scaled internal units. Parameters: none Returns: `string*` — Sequence of pipe-delimited pool data rows. One row per active pool. Notes: Reserves are scaled (internal) units. Parse with row.split("|"). ```js const rows = await readContract("saturnclpools", "getActiveClPoolsData", []); // rows is an array of strings, e.g.: // ["1|S1abc...|SOUL|KCAL|1000000000|5000000000|40000000|60000000|30|1", ...] const pools = rows.map(row => { const [poolId, provider, tokenA, tokenB, resA, resB, priceMin, priceMax, fee, active] = row.split("|"); return { poolId, provider, tokenA, tokenB, resA, resB, priceMin, priceMax, fee, active }; }); ``` ### SaturnTWAMM — `saturntwamm` *Time-Weighted Average Market Maker* — Saturn DEX v4 · Advanced Pools & Capital Sandwich-resistant streaming swap engine. A user submits a large trade as a stream — "swap N tokens over T seconds" — depositing the full input upfront. The stream is split into time-proportional chunks; anyone (including bots and automation agents) can call executeStreamingChunk to fire the next slice and earn a configurable bounty paid from each chunk's output. The target pool experiences a smooth, low-impact flow rather than a single large-impact trade, collapsing the MEV opportunity. Output accumulates inside the contract and is claimed by the stream owner at any time. Streams can be cancelled early, returning all unspent input and accumulated output in one call. #### Stream Management ##### `placeStreamingOrder(from: address, poolId: number, amountIn: number, durationSeconds: number, tokenIn: string, tokenOut: string, minChunkSeconds: number, minOutputPerChunk: number, bountyPer10k: number)` — WRITE Opens a new streaming order. The full amountIn is transferred from the caller into the TWAMM contract's escrow immediately. The stream runs for durationSeconds, executing chunks no faster than one per minChunkSeconds. Each chunk executor receives bountyPer10k basis-points of that chunk's output as a bounty; set to 0 to disable executor incentives (only recommended for self-operated bots). minOutputPerChunk is a per-chunk slippage guard — if the market moves severely during the stream the chunk executor's call will revert rather than deliver bad fills. Parameters: - `from` (`address`): Stream owner; deposits the input tokens and claims the output. Must be the transaction signer. - `poolId` (`number`): ID of the target pool (regular Saturn pool) through which each chunk will be swapped. - `amountIn` (`number`): Total raw amount of tokenIn to stream over the duration. - `durationSeconds` (`number`): Total duration of the stream in seconds. Minimum 60, maximum 31,536,000 (1 year). - `tokenIn` (`string`): Symbol of the input token; must be one side of the target pool's pair. - `tokenOut` (`string`): Symbol of the desired output token; must be the other side of the pool's pair. - `minChunkSeconds` (`number`): Minimum seconds that must elapse between chunk executions. At least 60; cannot exceed durationSeconds. - `minOutputPerChunk` (`number`): Minimum raw output required per chunk; reverts the executeStreamingChunk call if the swap would produce less. Set 0 to disable. - `bountyPer10k` (`number`): Basis-points share of each chunk's gross output paid to the executor (0–500, i.e. 0–5%). Notes: Reverts if amountIn == 0; durationSeconds < 60 or > 31,536,000; minChunkSeconds < 60 or > durationSeconds; bountyPer10k > 500; tokenIn == tokenOut; the target pool is not active; or tokenIn/tokenOut are not both in the pool's pair. The caller must have sufficient tokenIn balance. ```js // Stream 100 SOUL into KCAL over 1 hour, one chunk per minute, 1% bounty const amountIn = 10000000000n; // 100 SOUL (8 decimals) const duration = 3600; // 1 hour const minChunk = 60; // chunks every 60 s minimum const minOut = 0; // no per-chunk floor const bounty = 100; // 1% bounty to executors const tx = sb.begin() .allowGas(from) .callContract("saturntwamm", "placeStreamingOrder", [ from, poolId, amountIn, duration, "SOUL", "KCAL", minChunk, minOut, bounty ]) .spendGas(from) .endScript(); ``` ##### `cancelStream(from: address, streamId: number)` — WRITE Cancels an active stream early. Returns all remaining unstreamed input AND any accumulated output that has not yet been claimed — both in a single call. Status is set to 2 (cancelled) and the stream is removed from the active list. Only the stream owner may cancel. Once cancelled a stream cannot be resumed. Parameters: - `from` (`address`): Stream owner; must match the address that placed the order. - `streamId` (`number`): ID of the stream to cancel. Notes: Reverts if from is not the stream owner or if the stream is not active (status != 0). Emits StreamClosed with the refunded input and final accumulated output amounts. ```js const tx = sb.begin() .allowGas(from) .callContract("saturntwamm", "cancelStream", [from, streamId]) .spendGas(from) .endScript(); ``` #### Chunk Execution (Bounty Path) ##### `executeStreamingChunk(from: address, streamId: number)` — WRITE Executes the next time-proportional chunk of an active stream. Permissionless — any address can call this, not just the stream owner. The chunk size is computed from (totalAmountIn × elapsedSinceStart / duration) minus what has already been streamed, so cumulative integer-rounding error stays at zero; the final chunk settles any residue. The chunk is routed through saturnswap.swapFromContract against the stream's target pool. The executor receives bountyPer10k basis-points of the chunk's gross output, paid immediately; the remainder accumulates for the stream owner to claim. When the last chunk is executed the stream status transitions to 1 (completed) automatically. Parameters: - `from` (`address`): Executor address; receives the bounty from this chunk. Does not need to be the stream owner. - `streamId` (`number`): ID of the stream to advance. Notes: Reverts if stream is not active (status != 0); the chunk pacing interval has not elapsed since the last execution; no streamable amount has accumulated yet (can happen with very fine-grained minChunkSeconds early in a long stream); or the swap output is below the stream's minOutputPerChunk. Always check getStreamLastExecutionTime() + getStreamMinChunkSeconds() to know when the next call is valid. ```js // Bot / agent loop: execute whenever a chunk is ready const lastExec = await readContract("saturntwamm", "getStreamLastExecutionTime", [streamId]); const minInterval = await readContract("saturntwamm", "getStreamMinChunkSeconds", [streamId]); const nextWindow = Number(lastExec) + Number(minInterval); if (Date.now() / 1000 >= nextWindow) { const tx = sb.begin() .allowGas(executor) .callContract("saturntwamm", "executeStreamingChunk", [executor, streamId]) .spendGas(executor) .endScript(); } ``` #### Output Claims ##### `claimStreamingOutput(from: address, streamId: number)` — WRITE Transfers all accumulated tokenOut from completed chunk swaps to the stream owner. Can be called at any point during or after the stream — mid-stream partial claims are fully supported. Resets the accumulator to zero after payout. The stream does not need to be completed; active, completed, and cancelled streams all allow claims as long as accumulated > 0. Parameters: - `from` (`address`): Stream owner; must match the address that placed the order. - `streamId` (`number`): ID of the stream from which to claim output. Notes: Reverts if from is not the stream owner or if the accumulated output is zero (nothing has been swapped yet, or everything was already claimed). ```js const tx = sb.begin() .allowGas(from) .callContract("saturntwamm", "claimStreamingOutput", [from, streamId]) .spendGas(from) .endScript(); ``` #### Stream Views ##### `getContractVersion(): string` — READ Returns the current TWAMM contract version string. Parameters: none Returns: `string` — Version string, e.g. "saturntwamm-4.2.1". ```js const version = await readContract("saturntwamm", "getContractVersion", []); ``` ##### `getStreamInfo(streamId: number): string` — READ Returns all key stream fields as a single underscore-delimited string for a one-round-trip refresh. Format: "pool:N_in:TOKEN_out:TOKEN_total:N_streamed:N_remaining:N_accOut:N_start:N_end:N_status:N". All amounts in raw units; times are Unix timestamps. Parameters: - `streamId` (`number`): Stream ID to inspect. Returns: `string` — Packed field string. Parse by splitting on "_" then on ":". Notes: status: 0 = active, 1 = completed, 2 = cancelled. ```js const info = await readContract("saturntwamm", "getStreamInfo", [streamId]); const fields = Object.fromEntries(info.split("_").map(f => f.split(":"))); // fields.status === "0" → still running ``` ##### `getStreamOwner(streamId: number): address` — READ Returns the address of the wallet that placed the streaming order. Parameters: - `streamId` (`number`): Stream ID. Returns: `address` — Stream owner address. ##### `getStreamPoolId(streamId: number): number` — READ Returns the target pool ID that chunks are swapped through. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Target pool ID. ##### `getStreamTokenIn(streamId: number): string` — READ Returns the symbol of the input token being streamed. Parameters: - `streamId` (`number`): Stream ID. Returns: `string` — Input token symbol. ##### `getStreamTokenOut(streamId: number): string` — READ Returns the symbol of the output token being accumulated. Parameters: - `streamId` (`number`): Stream ID. Returns: `string` — Output token symbol. ##### `getStreamAmountInTotal(streamId: number): number` — READ Returns the total input amount placed when the stream was created. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Original total raw amountIn. ##### `getStreamAmountInRemaining(streamId: number): number` — READ Returns how much of the input has not yet been streamed. Use this to compute percentage completion. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Raw input amount still in escrow, not yet swapped. ```js const total = await readContract("saturntwamm", "getStreamAmountInTotal", [streamId]); const remaining = await readContract("saturntwamm", "getStreamAmountInRemaining", [streamId]); const pctDone = ((Number(total) - Number(remaining)) / Number(total) * 100).toFixed(1); ``` ##### `getStreamAmountStreamedSoFar(streamId: number): number` — READ Returns the cumulative raw input amount that has been swapped across all executed chunks. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Cumulative swapped input amount (raw units). ##### `getStreamAmountOutAccumulated(streamId: number): number` — READ Returns the current unclaimed output balance sitting in the TWAMM contract for this stream. Decremented to zero each time claimStreamingOutput is called. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Raw unclaimed tokenOut accumulated so far. ##### `getStreamStartTime(streamId: number): number` — READ Returns the Unix timestamp when the stream was placed. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Unix start timestamp. ##### `getStreamEndTime(streamId: number): number` — READ Returns the Unix timestamp at which the stream's duration expires. Chunks can still execute after this time to settle residue. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Unix end timestamp (startTime + durationSeconds). ##### `getStreamLastExecutionTime(streamId: number): number` — READ Returns the Unix timestamp of the most recent chunk execution. Add getStreamMinChunkSeconds() to determine when the next chunk becomes executable. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Unix timestamp of last executeStreamingChunk call. ```js const last = await readContract("saturntwamm", "getStreamLastExecutionTime", [streamId]); const minSecs = await readContract("saturntwamm", "getStreamMinChunkSeconds", [streamId]); const ready = Date.now() / 1000 >= Number(last) + Number(minSecs); ``` ##### `getStreamMinChunkSeconds(streamId: number): number` — READ Returns the minimum pacing interval (in seconds) between consecutive chunk executions for this stream. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Minimum seconds between chunks. ##### `getStreamMinOutputPerChunk(streamId: number): number` — READ Returns the per-chunk minimum output slippage guard set when the stream was placed. Zero means no floor. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Minimum raw tokenOut per chunk (0 = disabled). ##### `getStreamBountyPer10k(streamId: number): number` — READ Returns the executor bounty rate in basis points out of 10,000. Multiply by chunk output and divide by 10,000 to estimate what an executor earns per call. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — Bounty in basis points (0–500). ##### `getStreamStatus(streamId: number): number` — READ Returns the lifecycle status of a stream: 0 = active, 1 = completed (fully streamed), 2 = cancelled. Parameters: - `streamId` (`number`): Stream ID. Returns: `number` — 0 = active, 1 = completed, 2 = cancelled. ##### `getNextStreamId(): number` — READ Returns the ID that will be assigned to the next stream. Stream IDs are sequential starting from 1. Parameters: none Returns: `number` — Next available stream ID. ##### `getTotalStreamsPlaced(): number` — READ Returns the cumulative count of all streams ever placed, including completed and cancelled ones. Parameters: none Returns: `number` — All-time streams placed. ##### `getTotalStreamsCompleted(): number` — READ Returns the number of streams that have been fully executed to completion (all input streamed). Parameters: none Returns: `number` — All-time streams completed. ##### `getActiveStreamCount(): number` — READ Returns the current number of active (status = 0) streams. Useful for a live dashboard counter. Parameters: none Returns: `number` — Number of currently active streams. ```js const active = await readContract("saturntwamm", "getActiveStreamCount", []); ``` ##### `getAllActiveStreamIds(): number*` — READ Streams the IDs of all currently active (status = 0) streams. Pair with getStreamInfo() or getActiveStreamsData() to build a live order-book view. Parameters: none Returns: `number*` — Sequence of active stream IDs. ```js const ids = await readContract("saturntwamm", "getAllActiveStreamIds", []); ``` ##### `getActiveStreamIdsByOwner(owner: address): number*` — READ Returns only the active stream IDs that belong to a specific owner address. Useful for a personal dashboard showing all of a user's live streams. Parameters: - `owner` (`address`): Address whose active streams to query. Returns: `number*` — Sequence of active stream IDs owned by the given address. ```js const myStreams = await readContract("saturntwamm", "getActiveStreamIdsByOwner", [userAddress]); ``` ##### `getActiveStreamIdsByPool(poolId: number): number*` — READ Returns only the active stream IDs targeting a specific pool. Useful for pool analytics pages that want to show pending TWAMM flow alongside live reserves. Parameters: - `poolId` (`number`): Pool ID to filter streams by. Returns: `number*` — Sequence of active stream IDs targeting the given pool. ```js const poolStreams = await readContract("saturntwamm", "getActiveStreamIdsByPool", [poolId]); ``` ##### `getActiveStreamsData(): string*` — READ Batch view that streams one encoded row per active stream, eliminating N round-trips for list UIs. Each row is pipe-delimited: "streamId|poolId|owner|tokenIn|tokenOut|amountInTotal|amountInRemaining|amountOutAccumulated|endTime|status". Parameters: none Returns: `string*` — Sequence of pipe-delimited stream data rows. One row per active stream. Notes: Only includes status = 0 streams. Parse each row with row.split("|"). ```js const rows = await readContract("saturntwamm", "getActiveStreamsData", []); const streams = rows.map(row => { const [streamId, poolId, owner, tokenIn, tokenOut, amtTotal, amtRemaining, amtOut, endTime, status] = row.split("|"); return { streamId, poolId, owner, tokenIn, tokenOut, amtTotal, amtRemaining, amtOut, endTime, status }; }); ``` ##### `getActiveStreamsDataByOwner(owner: address): string*` — READ Same pipe-delimited batch format as getActiveStreamsData() but filtered to a single owner. Use this on a wallet portfolio page to load all of a user's live streams in one call. Parameters: - `owner` (`address`): Address to filter active streams by. Returns: `string*` — Sequence of pipe-delimited active stream rows for the given owner. ```js const myRows = await readContract("saturntwamm", "getActiveStreamsDataByOwner", [userAddress]); ``` ### SaturnFlash — `saturnflash` *Flash Loans & Flash Arbitrage* — Saturn DEX v4 · Advanced Pools & Capital Executes atomic two-pool flash arbitrage using idle liquidity borrowed from saturnliquidity. The contract borrows a tokenStart amount, routes it through two pools (tokenStart → tokenMid → tokenStart) in a single transaction, repays the principal plus a small flash fee, and forwards the net profit to the executor. Because TOMB requires literal call targets, Aave-style open-callback flash loans are not supported; the arbitrage logic is baked in. Build bots that call executeFlashArb whenever cross-pool price discrepancies exceed the combined flash fee and swap fees. #### Flash Arbitrage ##### `executeFlashArb(from: address, poolIdBuy: number, poolIdSell: number, tokenStart: string, amountIn: number, minNetProfit: number): number` — WRITE Core flash-arbitrage entrypoint. Borrows amountIn of tokenStart from saturnliquidity, executes leg 1 (tokenStart → tokenMid via poolIdBuy) and leg 2 (tokenMid → tokenStart via poolIdSell), repays the borrowed principal plus the flash fee, and transfers the net profit to from. The transaction reverts atomically if the round-trip yields no profit, the profit is too small to cover the flash fee, or netProfit falls below minNetProfit — so the caller's reserves are never at risk, only their gas. The tokenMid is resolved automatically from poolIdBuy's pair. poolIdBuy and poolIdSell must be different pools and both must contain tokenStart. Parameters: - `from` (`address`): Executor's address; must be the transaction witness and is the recipient of net profit. - `poolIdBuy` (`number`): ID of the pool used for leg 1 (tokenStart → tokenMid). tokenMid is inferred as the other token in this pool. - `poolIdSell` (`number`): ID of the pool used for leg 2 (tokenMid → tokenStart). Must contain both tokenStart and the resolved tokenMid. - `tokenStart` (`string`): Token symbol to borrow and to denominate profit in. Must be present in both pools. - `amountIn` (`number`): Raw-unit amount to borrow from saturnliquidity. Must be > 0 and ≤ available liquidity balance. - `minNetProfit` (`number`): Minimum acceptable net profit in raw units of tokenStart after deducting the flash fee. Set to 0 to accept any positive profit; set higher to protect against thin-margin executions. Returns: `number` — Net profit in raw units of tokenStart delivered to the executor. Notes: Reverts if flashEnabled == 0 (paused), amountIn == 0, poolIdBuy == poolIdSell, tokenStart is not a valid symbol, the liquidity pool has insufficient balance, the round-trip is unprofitable, grossProfit ≤ flashFee, or netProfit < minNetProfit. On revert every state change unwinds — no fee is charged and no principal is lost. ```js const from = "S3...myBotAddress"; const tx = sb.begin() .allowGas(from) .callContract("saturnflash", "executeFlashArb", [from, poolIdBuy, poolIdSell, "SOUL", amountIn, minNetProfit]) .spendGas(from) .endScript(); // returns netProfit (number) — raw units of tokenStart ``` #### Fee & Config Views ##### `quoteFlashFee(amount: number): number` — READ Returns the flash fee that would be charged on a given borrow amount at the current flashFeePer10k rate. Use this before calling executeFlashArb to calculate whether the expected arb spread exceeds the total cost (2× swap fees + flash fee + gas). Parameters: - `amount` (`number`): The borrow amount in raw token units. Returns: `number` — Flash fee in raw token units: (amount × flashFeePer10k) / 10000. Notes: Pure math, never reverts. Returns 0 if amount is too small to produce a non-zero fee at the current rate. ```js const fee = await readContract("saturnflash", "quoteFlashFee", [amountIn]); // fee must be < expected spread for the arb to be profitable ``` ##### `getMaxBorrowable(tokenSymbol: string): number` — READ Returns the current raw-unit balance of tokenSymbol held by saturnliquidity — the maximum amount that can be borrowed in a single executeFlashArb call. Always query this before sizing amountIn to avoid an insufficient-liquidity revert. Parameters: - `tokenSymbol` (`string`): The token symbol to check borrowable liquidity for (e.g. "SOUL", "KCAL"). Returns: `number` — Maximum borrowable amount in raw units of tokenSymbol. Notes: Reflects real-time liquidity. Concurrent trades may lower this between query and execution. ```js const max = await readContract("saturnflash", "getMaxBorrowable", ["SOUL"]); const amountIn = Math.floor(max * 0.5); // use a fraction for safety ``` ##### `getFlashFeePer10k(): number` — READ Returns the current flash fee rate in basis points out of 10,000 (e.g. 5 = 0.05%). This rate is applied to amountIn to determine the fee the executor pays on top of loan repayment. Parameters: none Returns: `number` — Flash fee rate per 10,000 (range: 1–100; default: 5). Notes: Always between 1 (0.01%) and 100 (1%). ```js const feePer10k = await readContract("saturnflash", "getFlashFeePer10k", []); ``` ##### `getFlashEnabled(): number` — READ Returns 1 if flash arbitrage is currently active, 0 if the admin has paused it. Check this before attempting executeFlashArb to surface a clear status in your bot or UI. Parameters: none Returns: `number` — 1 = enabled; 0 = paused by admin. Notes: Returns 0 or 1 only. ```js const enabled = await readContract("saturnflash", "getFlashEnabled", []); if (enabled !== 1) throw new Error("Flash arb is currently paused"); ``` ##### `getContractVersion(): string` — READ Returns the deployed version string for this contract. Parameters: none Returns: `string` — Version identifier, e.g. "saturnflash-4.2.4". ```js const ver = await readContract("saturnflash", "getContractVersion", []); ``` ##### `getContractAddress(): address` — READ Returns the on-chain address of this contract. Useful for constructing token-transfer pre-approvals or inspecting on-chain balances. Parameters: none Returns: `address` — The deployed address of the saturnflash contract. ```js const addr = await readContract("saturnflash", "getContractAddress", []); ``` #### Statistics Views ##### `getTotalFlashArbs(): number` — READ Returns the cumulative count of successful flash arbitrage executions since deployment. Parameters: none Returns: `number` — Total successful executeFlashArb calls. ```js const total = await readContract("saturnflash", "getTotalFlashArbs", []); ``` ##### `getTotalFlashFeesCollected(): number` — READ Returns the lifetime total of flash fees (in raw token units) collected by the protocol admin across all successful arb executions. Parameters: none Returns: `number` — Cumulative flash fees forwarded to admin, in raw token units. ```js const fees = await readContract("saturnflash", "getTotalFlashFeesCollected", []); ``` ##### `getExecutorArbCount(executor: address): number` — READ Returns the number of successful flash arb executions performed by a specific executor address. Use this on leaderboards or to track your own bot's activity. Parameters: - `executor` (`address`): The executor address to query. Returns: `number` — Number of successful executeFlashArb calls made by this address. ```js const count = await readContract("saturnflash", "getExecutorArbCount", [botAddress]); ``` ##### `getExecutorTotalProfit(executor: address): number` — READ Returns the lifetime net profit (in raw token units of tokenStart) paid out to a specific executor across all successful arb executions. Parameters: - `executor` (`address`): The executor address to query. Returns: `number` — Lifetime net profit in raw token units paid to this executor. ```js const profit = await readContract("saturnflash", "getExecutorTotalProfit", [botAddress]); ``` ### SaturnHolders — `saturnholders` *Holder Rewards (Stake-to-Earn)* — Saturn DEX v4 · Advanced Pools & Capital MasterChef-style stake-to-earn vault that distributes a slice of every swap's fee to token holders pro-rata by stake. Users deposit tokens into saturnholders and earn the holder portion of swap fees (accrued by saturnswap on each trade) plus their share of arbitrage profits from saturnstakearb — all from the same deposit, with no separate claim step required. Staking also enables idle capital to be flash-borrowed by saturnstakearb for atomic round-trip arbs that generate additional yield. Reward tokens always match the staked token: stake SOUL, earn SOUL from SOUL-side swaps. #### Stake & Unstake ##### `stake(from: address, tokenSymbol: string, amount: number)` — WRITE Deposits amount raw units of tokenSymbol into the rewards vault on behalf of from. If the user already has an open position, any pending rewards are settled first so they aren't lost. The user's bookmark is set to the current accumulator value, ensuring they only earn from future accruals. Tokens are transferred from the caller's wallet into this contract's custody and cannot be traded while staked — this custody model prevents the wash-claim attack that pure hold-to-earn is vulnerable to. Parameters: - `from` (`address`): Staker's address. Must be the transaction witness. - `tokenSymbol` (`string`): Symbol of the token to stake (must be a valid Saturn pool token). - `amount` (`number`): Raw-unit amount to deposit. Must be > 0. Notes: Reverts if from is not the witness, amount == 0, or tokenSymbol is not a valid token. Pending rewards for any existing position are auto-settled before the new stake is recorded. Token must be in the from wallet in sufficient balance. ```js const from = "S3...userAddress"; const tx = sb.begin() .allowGas(from) .callContract("saturnholders", "stake", [from, "SOUL", stakeAmount]) .spendGas(from) .endScript(); ``` ##### `unstake(from: address, tokenSymbol: string, amount: number)` — WRITE Withdraws amount raw units of tokenSymbol from the vault back to from. Pending rewards are settled automatically before the stake is decremented, so the user receives everything they've earned. Partial unstakes are supported — only the requested amount is returned and the remainder continues earning. If amount would reduce the stake to zero, the distinct-stakers count is decremented. In v4.5.0, any portion of the stake locked via a saturntaz pledge cannot be unstaked until saturntaz calls unlockPledge. Parameters: - `from` (`address`): Staker's address. Must be the transaction witness. - `tokenSymbol` (`string`): Symbol of the token to withdraw. - `amount` (`number`): Raw-unit amount to withdraw. Must be > 0 and ≤ (stakedAmount - pledgeLocked). Notes: Reverts if from is not the witness, amount == 0, stake < amount, or amount exceeds unlocked stake (stake minus pledgeLocked). Pending rewards are always settled first — they are never forfeited. ```js const from = "S3...userAddress"; const tx = sb.begin() .allowGas(from) .callContract("saturnholders", "unstake", [from, "SOUL", unstakeAmount]) .spendGas(from) .endScript(); ``` #### Claim Rewards ##### `claim(from: address, tokenSymbol: string)` — WRITE Pays out all pending rewards for from's tokenSymbol stake without altering the stake balance. Reward tokens are transferred from saturnliquidity (the central custodian) directly to from. The user's bookmark is bumped to the current accumulator so subsequent calls report zero pending until new swaps accrue more fees. Can be called at any time after stake(). Parameters: - `from` (`address`): Staker's address. Must be the transaction witness and must have an active stake. - `tokenSymbol` (`string`): Symbol of the staked token whose rewards to claim. Notes: Reverts if from is not the witness or has no stake in tokenSymbol. If pending is zero, the call succeeds silently (no transfer) and the bookmark is still updated. ```js const from = "S3...userAddress"; const tx = sb.begin() .allowGas(from) .callContract("saturnholders", "claim", [from, "SOUL"]) .spendGas(from) .endScript(); ``` #### Pledge Lock (v4.5.0) ##### `getPledgeLocked(user: address, tokenSymbol: string): number` — READ Returns the raw-unit amount of tokenSymbol currently locked against unstaking for user by a Saturn Lending pledge (via saturntaz). The available-for-unstake amount is stakedAmount − pledgeLocked. Query this alongside getStakedAmount to surface the correct withdrawable balance in your UI. Parameters: - `user` (`address`): The staker's address. - `tokenSymbol` (`string`): The staked token symbol. Returns: `number` — Raw-unit amount currently locked by an active saturntaz pledge. Returns 0 if no pledge is active. ```js const locked = await readContract("saturnholders", "getPledgeLocked", [userAddr, "SOUL"]); const staked = await readContract("saturnholders", "getStakedAmount", [userAddr, "SOUL"]); const available = staked - locked; ``` #### Stake-Arb Status ##### `getLoanOpen(tokenSymbol: string): number` — READ Returns 1 while a saturnstakearb flash-borrow of tokenSymbol's staked capital is in progress within a transaction, 0 otherwise. Under normal conditions this will always return 0 from an external query because the borrow opens and closes within a single atomic transaction. Useful for debugging or monitoring. Parameters: - `tokenSymbol` (`string`): Token symbol to check. Returns: `number` — 1 = flash loan open (within an active executeArb tx); 0 = no open loan. ```js const open = await readContract("saturnholders", "getLoanOpen", ["SOUL"]); ``` #### Individual Staker Views ##### `getStakedAmount(user: address, tokenSymbol: string): number` — READ Returns the raw-unit amount of tokenSymbol currently staked by user. This is the gross staked balance; subtract getPledgeLocked to get the amount available for immediate unstaking. Parameters: - `user` (`address`): The staker's address. - `tokenSymbol` (`string`): The staked token symbol. Returns: `number` — Raw-unit staked balance. Returns 0 if user has no stake. ```js const staked = await readContract("saturnholders", "getStakedAmount", [userAddr, "SOUL"]); ``` ##### `getPendingRewards(user: address, tokenSymbol: string): number` — READ Computes and returns the unclaimed rewards (in raw token units) accrued to user's tokenSymbol stake since their last claim, stake, or unstake. This is the primary view to display in a rewards dashboard — query it before calling claim() to show the user what they'll receive. Parameters: - `user` (`address`): The staker's address. - `tokenSymbol` (`string`): The staked token symbol. Returns: `number` — Pending reward amount in raw units of tokenSymbol. Returns 0 if user has no stake or no rewards have accrued. ```js const pending = await readContract("saturnholders", "getPendingRewards", [userAddr, "SOUL"]); console.log(`Claimable: ${pending} raw SOUL units`); ``` ##### `getUserRewardDebt(user: address, tokenSymbol: string): number` — READ Returns the user's reward-debt bookmark — the accFeePerToken value at the time of their last settle (stake, unstake, or claim). The difference between the current accumulator and this bookmark, multiplied by the user's stake, gives the pending rewards. Useful for verifying MasterChef math or building advanced analytics. Parameters: - `user` (`address`): The staker's address. - `tokenSymbol` (`string`): The staked token symbol. Returns: `number` — The scaled accumulator snapshot (1e12 precision) at the user's last settle. ```js const debt = await readContract("saturnholders", "getUserRewardDebt", [userAddr, "SOUL"]); ``` ##### `getStakeInfo(user: address, tokenSymbol: string): string` — READ Returns a packed summary string for a user's position in a single call: "stake:{n}_debt:{n}_pending:{n}". Convenient for a compact dashboard widget or a single-RPC snapshot of a user's full position. Parameters: - `user` (`address`): The staker's address. - `tokenSymbol` (`string`): The staked token symbol. Returns: `string` — Packed string e.g. "stake:500000000_debt:1234000000000_pending:12300". ```js const info = await readContract("saturnholders", "getStakeInfo", [userAddr, "SOUL"]); // "stake:500000000_debt:1234000000000_pending:12300" ``` ##### `getUserPortfolioData(user: address): string*` — READ Returns one row per token in which user has an active stake (stake > 0). Each row is "symbol|stake|pending|rewardDebt". Use this to render a user's entire staking portfolio in a single call instead of N per-token round-trips. Parameters: - `user` (`address`): The staker's address. Returns: `string*` — Iterator of rows; each row: "symbol|stake|pending|rewardDebt". Empty if user has no stakes. ```js const rows = await readContract("saturnholders", "getUserPortfolioData", [userAddr]); // ["SOUL|500000000|12300|1234000000000", "KCAL|200000000|4500|987000000000"] ``` #### Pool-Level & Global Views ##### `getTotalStaked(tokenSymbol: string): number` — READ Returns the total raw-unit amount of tokenSymbol currently staked across all holders. This is the denominator used in the MasterChef accumulator and also the maximum flash-borrowable amount for saturnstakearb. Parameters: - `tokenSymbol` (`string`): Token symbol to query. Returns: `number` — Total staked supply in raw token units. Returns 0 if no one has staked this token. ```js const totalStaked = await readContract("saturnholders", "getTotalStaked", ["SOUL"]); ``` ##### `getAccFeePerToken(tokenSymbol: string): number` — READ Returns the current global accumulated-fee-per-unit value for tokenSymbol, scaled by 1e12. This counter only ever increases. Use it alongside getUserRewardDebt to reconstruct the MasterChef pending-reward formula: pending = stake × (accNow − debt) / 1e12. Parameters: - `tokenSymbol` (`string`): Token symbol to query. Returns: `number` — Cumulative fee-per-unit accumulator scaled by 1e12. ```js const acc = await readContract("saturnholders", "getAccFeePerToken", ["SOUL"]); ``` ##### `getTotalStakers(tokenSymbol: string): number` — READ Returns the number of distinct addresses currently holding a non-zero stake of tokenSymbol. Useful for showing participation metrics in your UI. Parameters: - `tokenSymbol` (`string`): Token symbol to query. Returns: `number` — Count of unique stakers for this token. ```js const stakers = await readContract("saturnholders", "getTotalStakers", ["SOUL"]); ``` ##### `getLifetimeAccrued(tokenSymbol: string): number` — READ Returns the total raw-unit amount of tokenSymbol that has ever been accrued into the reward pool (from both swap fees and arb profits) since deployment. Useful for APR calculation and historical analytics. Parameters: - `tokenSymbol` (`string`): Token symbol to query. Returns: `number` — Lifetime accrued rewards in raw token units. ```js const lifetime = await readContract("saturnholders", "getLifetimeAccrued", ["SOUL"]); ``` ##### `getStakedTokenSymbols(): string*` — READ Returns an iterator of every token symbol that has ever been staked. Use this to enumerate all active staking markets without needing to know the token list in advance. Parameters: none Returns: `string*` — Iterator of token symbol strings (e.g. "SOUL", "KCAL", ...). ```js const symbols = await readContract("saturnholders", "getStakedTokenSymbols", []); // ["SOUL", "KCAL", "DYT", ...] ``` ##### `getStakedTokensData(): string*` — READ Returns one packed row per staked token symbol. Each row is "symbol|totalStaked|accFeePerToken|stakers|lifetimeAccrued|loanOpen". Use this for a single-call dashboard load of all token staking metrics. Parameters: none Returns: `string*` — Iterator of rows; each row: "symbol|totalStaked|accFeePerToken|stakers|lifetimeAccrued|loanOpen". ```js const rows = await readContract("saturnholders", "getStakedTokensData", []); // ["SOUL|500000000|1234000000000|42|9870000|0", ...] ``` ##### `registerStakedToken(tokenSymbol: string)` — WRITE Permissionless, idempotent utility that adds tokenSymbol to the enumeration index (getStakedTokenSymbols / getStakedTokensData) if it has totalStaked > 0 but is not yet listed. Only needed for tokens staked before the v4.4.1 enumeration index was introduced. New stakes self-register automatically inside stake(). Parameters: - `tokenSymbol` (`string`): Token symbol to backfill into the enumeration index. Notes: No-op if tokenSymbol is already listed or has zero total stake. Never reverts. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnholders", "registerStakedToken", ["LEGACY"]) .spendGas(from) .endScript(); ``` ##### `getContractVersion(): string` — READ Returns the deployed version string for this contract. Parameters: none Returns: `string` — Version identifier, e.g. "saturnholders-4.4.1". ```js const ver = await readContract("saturnholders", "getContractVersion", []); ``` ##### `getContractAddress(): address` — READ Returns the on-chain address of this contract. Useful for token-transfer approvals or direct balance lookups. Parameters: none Returns: `address` — The deployed address of the saturnholders contract. ```js const addr = await readContract("saturnholders", "getContractAddress", []); ``` ## Financial Products Securitized fee streams, franchise pool rentals, fee-rate options, crowdfunded syndicates, and fixed-price launchpads. ### SaturnBonds — `saturnbonds` *Securitized Fee Streams* — Saturn DEX v4 · Financial Products Providers can sell the future provider-fee stream of a pool as a fixed-yield bond. Buyers pay an up-front purchasePrice (less than faceValue) and collect the fees the pool earns over the bond's term — up to faceValue. Two modes: PARTIAL (1) pays only what the fees actually earn, and HYBRID (2) tops the payout up to faceValue using provider-posted collateral. Buying a bond locks the pool (it can't be removed) and redirects fees until the bond is settled. Anyone can settle a matured bond — the buyer has every incentive to. #### Bond Lifecycle ##### `listBond(from: address, poolId: number, faceValue: number, purchasePrice: number, feeToken: string, durationSeconds: number, mode: number, collateralAmount: number)` — WRITE Pool provider lists a new bond against one of their pools. faceValue is the max payout to the buyer; purchasePrice is what the buyer pays up front (must be strictly less than faceValue). In HYBRID mode (mode = 2) the provider deposits collateralAmount of feeToken now — this is held as a buffer to guarantee the buyer's payout. Parameters: - `from` (`address`): Pool provider wallet (must be witness). - `poolId` (`number`): Pool the bond is issued against. - `faceValue` (`number`): Max raw payout to the buyer at maturity. - `purchasePrice` (`number`): Raw amount the buyer pays (< faceValue). - `feeToken` (`string`): Token the bond is denominated in. - `durationSeconds` (`number`): Term length (min 86400, max 31536000). - `mode` (`number`): 1 = partial (no collateral), 2 = hybrid (collateral required). - `collateralAmount` (`number`): Raw collateral (must be 0 if mode=1, > 0 if mode=2). Returns: `void` — Success = bond listing created (status = 0). Notes: Reverts on: "Only pool provider", "Pool already has active bond or rental", "Purchase price must be less than face value", invalid mode, missing collateral in hybrid mode, or duration out of range. ```js // Hybrid mode bond for pool 42, 1M SOUL face, 900k purchase, 90 day term, 100k collateral const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2500) .callContract("saturnbonds", "listBond", [from, 42, 1000000, 900000, "SOUL", 90 * 86400, 2, 100000]) .spendGas(from) .endScript(); ``` ##### `cancelListing(from: address, bondId: number)` — WRITE Cancel a bond listing before any buyer has purchased it. Returns any deposited collateral back to the issuer. Parameters: - `from` (`address`): Original issuer wallet (must be witness). - `bondId` (`number`): Bond to cancel. Returns: `void` — Success = bond status set to 3 (cancelled). Notes: Reverts on: "Only bond issuer" or "Bond not in listed state". ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 1500) .callContract("saturnbonds", "cancelListing", [from, bondId]) .spendGas(from) .endScript(); ``` ##### `purchaseBond(from: address, bondId: number)` — WRITE Buyer pays the listing's purchasePrice directly to the issuer, becomes the bond holder, starts the term clock, and triggers fee redirection — all swap fees in feeToken now flow to this contract on behalf of the buyer. The pool becomes financially locked until settlement. Parameters: - `from` (`address`): Buyer wallet (must be witness, cannot be the issuer). - `bondId` (`number`): Bond to purchase. Returns: `void` — Success = bond status = 1 (active), fees redirected. Notes: Reverts on: "Cannot buy your own bond", "Bond not available for purchase", or "Insufficient balance to purchase bond". ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2000) .callContract("saturnbonds", "purchaseBond", [from, bondId]) .spendGas(from) .endScript(); ``` ##### `settleBond(from: address, bondId: number)` — WRITE Called any time after the bond's maturity. Claims the accumulated fees (plus collateral in hybrid mode), pays up to faceValue to the bond holder, returns any excess to the original issuer, clears the fee redirect, and unlocks the pool. Anyone can call this — in practice the holder does because they want their payout. Parameters: - `from` (`address`): Caller wallet (must be witness; any wallet works). - `bondId` (`number`): Bond to settle. Returns: `void` — Success = bond status = 2 (settled) and pool unlocked. Notes: Reverts on: "Bond not active" or "Bond has not matured yet". Also sweeps any accumulated fees in the OTHER token directly to the issuer — bonds only cover a single feeToken. ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 2500) .callContract("saturnbonds", "settleBond", [from, bondId]) .spendGas(from) .endScript(); ``` ##### `transferBond(from: address, to: address, bondId: number)` — WRITE Secondary-market transfer. The current holder assigns the bond to a different address. Bond must be active; recipient cannot be the current holder. Off-chain payment is up to the two parties. Parameters: - `from` (`address`): Current holder wallet (must be witness). - `to` (`address`): New holder address. - `bondId` (`number`): Bond being transferred. Returns: `void` — Success = buyer address updated. Notes: Reverts on: "Only bond holder", "Bond not active", "Invalid recipient", or "Cannot transfer to self". ```js const tx = ScriptBuilder .begin() .allowGas(from, Address.Null, 100000, 1500) .callContract("saturnbonds", "transferBond", [from, recipientAddress, bondId]) .spendGas(from) .endScript(); ``` #### Bond Views ##### `getBondInfo(bondId: number): string` — READ Packed string with every top-level bond field — pool, face value, purchase price, fee token, mode, maturity timestamp, status, and collateral. Parse with String.split on underscores. Parameters: - `bondId` (`number`): Bond ID. Returns: `string` — Format: "pool:_face:_price:_token:_mode:<1|2>_maturity:_status:<0..3>_collateral:". Notes: Status codes: 0 listed, 1 active, 2 settled, 3 cancelled. ```js const info = await readContract("saturnbonds", "getBondInfo", [bondId]); ``` ##### `getBondPoolId(bondId: number): number` — READ Pool the bond is issued against. Parameters: - `bondId` (`number`): Bond ID. Returns: `number` — Pool ID. Notes: Never changes after listing. ```js const pid = await readContract("saturnbonds", "getBondPoolId", [bondId]); ``` ##### `getBondIssuer(bondId: number): address` — READ Address of the wallet that created the bond listing. Parameters: - `bondId` (`number`): Bond ID. Returns: `address` — Issuer address. Notes: Set at list time and immutable. ```js const issuer = await readContract("saturnbonds", "getBondIssuer", [bondId]); ``` ##### `getBondBuyer(bondId: number): address` — READ Current holder of the bond. Null for listings that have not been purchased yet. Updated on transferBond(). Parameters: - `bondId` (`number`): Bond ID. Returns: `address` — Holder address (null if unsold). Notes: Compare against @null to check if still on the primary market. ```js const holder = await readContract("saturnbonds", "getBondBuyer", [bondId]); ``` ##### `getBondFaceValue(bondId: number): number` — READ Maximum raw payout the buyer can receive at maturity. Parameters: - `bondId` (`number`): Bond ID. Returns: `number` — Face value in feeToken raw units. Notes: Yield for the buyer = faceValue - purchasePrice. ```js const face = await readContract("saturnbonds", "getBondFaceValue", [bondId]); ``` ##### `getBondPurchasePrice(bondId: number): number` — READ Up-front raw amount the buyer paid (or pays) for the bond. Parameters: - `bondId` (`number`): Bond ID. Returns: `number` — Purchase price in feeToken raw units. Notes: Always strictly less than face value. ```js const price = await readContract("saturnbonds", "getBondPurchasePrice", [bondId]); ``` ##### `getBondFeeToken(bondId: number): string` — READ Symbol of the token the bond is denominated in — purchase price, face value, and collateral are all in this token, and only swap fees accrued in this token are counted toward the payout. Parameters: - `bondId` (`number`): Bond ID. Returns: `string` — Fee token symbol. Notes: Must be one of the two tokens in the pool. ```js const token = await readContract("saturnbonds", "getBondFeeToken", [bondId]); ``` ##### `getBondCollateral(bondId: number): number` — READ Raw collateral amount posted by the issuer (always 0 in partial mode). Parameters: - `bondId` (`number`): Bond ID. Returns: `number` — Collateral amount in feeToken raw units. Notes: Non-zero only for hybrid-mode bonds (mode = 2). ```js const col = await readContract("saturnbonds", "getBondCollateral", [bondId]); ``` ##### `getBondMode(bondId: number): number` — READ Returns 1 for PARTIAL (no collateral) or 2 for HYBRID (collateral-backed). Parameters: - `bondId` (`number`): Bond ID. Returns: `number` — 1 = partial, 2 = hybrid. Notes: Set at list time and immutable. ```js const mode = await readContract("saturnbonds", "getBondMode", [bondId]); ``` ##### `getBondStatus(bondId: number): number` — READ Lifecycle state of the bond. Parameters: - `bondId` (`number`): Bond ID. Returns: `number` — 0 = listed, 1 = active, 2 = settled, 3 = cancelled. Notes: Transitions: 0 → 1 (purchase), 1 → 2 (settle), 0 → 3 (cancel). ```js const s = await readContract("saturnbonds", "getBondStatus", [bondId]); ``` ##### `getBondMaturityTime(bondId: number): number` — READ Unix timestamp at which the bond can be settled. While the bond is listed (status 0), this field stores the term duration in seconds — it only becomes an absolute timestamp after purchase. Parameters: - `bondId` (`number`): Bond ID. Returns: `number` — Maturity timestamp (or duration if unsold). Notes: Check getBondStatus() to know how to interpret the value. ```js const maturity = await readContract("saturnbonds", "getBondMaturityTime", [bondId]); ``` ##### `getNextBondId(): number` — READ ID that will be assigned to the next bond listed. Parameters: none Returns: `number` — Next bond ID (starts at 1). Notes: Total bonds so far = getNextBondId() - 1. ```js const next = await readContract("saturnbonds", "getNextBondId", []); ``` ##### `getAllBondIds(): number*` — READ Generator yielding every bondId ever created. Parameters: none Returns: `number*` — Iterable of bond IDs. Notes: Filter by getBondStatus() to build the primary-market, secondary-market, or settled views. ```js const script = ScriptBuilder .begin() .callContract("saturnbonds", "getAllBondIds", []) .endScript(); ``` ### SaturnRental — `saturnrental` *Pool Rental Market* — Saturn DEX v4 · Financial Products Franchise model for pools. Providers list their pool with a daily SOUL rate, deposit, fee-bound window, and a minimum term. A renter pays the deposit plus prepaid rent and gains exclusive control of the pool's fee rate for the rental window — the renter collects every swap fee while the owner collects guaranteed rent. When the paid-through time expires, either party can settle up: remaining fees go to the renter, the deposit is refunded, the original fee rate is restored, and the pool is unlocked. While a rental is active the pool is financially locked and cannot be removed. #### Rental Lifecycle ##### `listForRent(from: address, poolId: number, dailyRate: number, depositRequired: number, minFeePer10k: number, maxFeePer10k: number, minTermDays: number)` — WRITE Pool provider lists their pool for rent. Saves the current fee rate so it can be restored at end, and defines the renter's operating envelope. Parameters: - `from` (`address`): Pool provider — must be a transaction witness. - `poolId` (`number`): The pool you own and want to rent out. Must be active, with no bond or rental already in effect. - `dailyRate` (`number`): Rent per day, in raw SOUL. Must be > 0. - `depositRequired` (`number`): Refundable deposit the renter must post, in raw SOUL. Must be > 0. - `minFeePer10k` (`number`): Lowest pool fee the renter may set. Must be >= protocol min (see SaturnAdmin.getPoolFeeRange). - `maxFeePer10k` (`number`): Highest pool fee the renter may set. Must be <= protocol max and >= minFeePer10k. - `minTermDays` (`number`): Minimum rental term, in days. Must be 1–365. The renter prepays dailyRate * minTermDays up-front. Notes: A new rentalId is created in status 0 (listed). Nothing is locked yet — no renter has stepped in. Use getAllRentalIds() + getRentalStatus() to show your listing on the marketplace. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnrental", "listForRent", [ from, poolId, dailyRateSoul, // raw SOUL per day depositSoul, // raw SOUL deposit 25, // 0.25% min fee 300, // 3.00% max fee 7 // minimum 7-day term ]) .spendGas(from) .endScript(); ``` ##### `cancelListing(from: address, rentalId: number)` — WRITE Pool provider cancels a listing that has not yet been rented. Only works while status = 0 (listed). Parameters: - `from` (`address`): Must be the provider who created the listing. - `rentalId` (`number`): The rental to cancel. Notes: Status flips to 3 (cancelled). The rentalId stays in allRentalIds for history but is no longer rentable. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnrental", "cancelListing", [from, rentalId]) .spendGas(from) .endScript(); ``` ##### `rentPool(from: address, rentalId: number)` — WRITE Renter takes the listing. Pays deposit + (dailyRate * minTermDays) in SOUL, gains fee control, and the pool's fee stream is redirected to the rental escrow. Parameters: - `from` (`address`): Renter — cannot be the pool provider. Must be a transaction witness. - `rentalId` (`number`): A listing in status 0 (listed). Notes: SOUL = deposit + dailyRate * minTermDays is pulled from your wallet in a single transfer. Status flips to 1 (rented), paidThroughTime = now + minTermDays*86400, and the pool's financial lock is incremented. From this point you control adjustFee() and every swap fee accrues to you until endRental(). ```js // Always preflight: read dailyRate + deposit + minTermDays first const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnrental", "rentPool", [from, rentalId]) .spendGas(from) .endScript(); ``` ##### `extendRental(from: address, rentalId: number, additionalDays: number)` — WRITE Renter prepays additional days before the current paid-through time. Avoids losing fee control when the term expires. Parameters: - `from` (`address`): Must be the current renter. - `rentalId` (`number`): An active rental (status 1). - `additionalDays` (`number`): Extra days to prepay. Must be >= 1. Notes: dailyRate * additionalDays SOUL is transferred from the renter to the rental escrow. paidThroughTime is pushed out by additionalDays * 86400 seconds. rentalRentAccrued rises, so the owner will be able to collectRent() the additional amount. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnrental", "extendRental", [from, rentalId, 30]) // +30 days .spendGas(from) .endScript(); ``` ##### `adjustFee(from: address, rentalId: number, newFeePer10k: number)` — WRITE Renter changes the pool's fee rate, within the [minFee, maxFee] window set at list time. This is the core lever of the franchise model — raise fees to earn more per swap, lower them to attract volume. Parameters: - `from` (`address`): Must be the current renter. - `rentalId` (`number`): An active rental that has not yet passed paidThroughTime. - `newFeePer10k` (`number`): New pool fee, per 10,000. Must be within [rentalMinFee, rentalMaxFee]. Notes: The pool's active fee rate updates immediately; the next swap uses the new rate. Call reverts if the rental term has expired — extend first, or end the rental. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnrental", "adjustFee", [from, rentalId, 150]) // 1.50% .spendGas(from) .endScript(); ``` ##### `claimRentalFees(from: address, rentalId: number)` — WRITE Renter harvests the swap fees accumulated on both sides of the pool while the rental has been active. Parameters: - `from` (`address`): Must be the current renter. - `rentalId` (`number`): An active rental (status 1). Notes: Both tokenA and tokenB redirected fee balances are transferred to the renter in one call. Safe to call frequently — if nothing has accrued since last claim, it's a no-op. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnrental", "claimRentalFees", [from, rentalId]) .spendGas(from) .endScript(); ``` ##### `collectRent(from: address, rentalId: number)` — WRITE Pool provider withdraws rent that the renter has already prepaid. Can be called at any time while the rental is active. Parameters: - `from` (`address`): Must be the listing owner (pool provider). - `rentalId` (`number`): An active rental (status 1). Notes: available = rentalRentAccrued - rentalRentCollected SOUL is sent to the owner. Reverts if available is zero. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnrental", "collectRent", [from, rentalId]) .spendGas(from) .endScript(); ``` ##### `endRental(from: address, rentalId: number)` — WRITE Either party settles the rental once the paid-through time has passed. Finalizes fees to the renter, pays remaining rent to the owner, refunds the deposit, restores the original fee rate, and unlocks the pool. Parameters: - `from` (`address`): Must be either the owner or the renter. - `rentalId` (`number`): An active rental whose paidThroughTime has elapsed. Notes: Any unclaimed swap fees go to the renter, remaining rent is swept to the owner, the deposit is returned to the renter, the pool's fee is set back to rentalOriginalFee, fee redirect is cleared, and the pool's financial lock count decrements. Status becomes 2 (ended). Reverts with 'Rental term not expired' if you call it too early. ```js // Check readiness first const pt = await readContract("saturnrental", "getRentalPaidThroughTime", [rentalId]); if (Date.now() / 1000 < pt) throw new Error("Not expired yet"); const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnrental", "endRental", [from, rentalId]) .spendGas(from) .endScript(); ``` #### Rental Views ##### `getRentalInfo(rentalId: number): string` — READ One-shot status snapshot used by marketplace UIs. Returns an underscore-delimited string with the key fields. Parameters: - `rentalId` (`number`): The rental to inspect. Returns: `string` — pool:_rate:_deposit:_minFee:_maxFee:_status:_paidThrough: Notes: Split on '_' and then on ':' to extract each field. Status decodes as 0=listed, 1=rented, 2=ended, 3=cancelled. ```js const raw = await readContract("saturnrental", "getRentalInfo", [rentalId]); const parts = Object.fromEntries( raw.split("_").map(kv => kv.split(":")) ); // parts.pool, parts.rate, parts.deposit, parts.minFee, parts.maxFee, parts.status, parts.paidThrough ``` ##### `getRentalPoolId(rentalId: number): number` — READ Returns the poolId this rental is attached to. Parameters: - `rentalId` (`number`): The rental to inspect. Returns: `number` — Underlying pool ID. Notes: Pair with SaturnRouter.getPoolFullInfo() to show the pool's current state next to the rental listing. ```js const poolId = await readContract("saturnrental", "getRentalPoolId", [rentalId]); ``` ##### `getRentalOwner(rentalId: number): address` — READ Returns the pool provider who listed this rental. Parameters: - `rentalId` (`number`): The rental to inspect. Returns: `address` — Listing owner. Notes: Use this for the 'collect rent' UI permission check — only this address can call collectRent. ```js const owner = await readContract("saturnrental", "getRentalOwner", [rentalId]); ``` ##### `getRentalOperator(rentalId: number): address` — READ Returns the address currently renting the pool, or @null if the listing has not been rented yet. Parameters: - `rentalId` (`number`): The rental to inspect. Returns: `address` — Current renter address, or @null when still in listed state. Notes: This is the address authorized to call adjustFee, extendRental, and claimRentalFees. ```js const renter = await readContract("saturnrental", "getRentalOperator", [rentalId]); ``` ##### `getRentalDailyRate(rentalId: number): number` — READ Returns the daily rent, in raw SOUL. Parameters: - `rentalId` (`number`): The rental to inspect. Returns: `number` — SOUL per day. Notes: Multiply by minTermDays (from getRentalInfo) to show 'total due' before calling rentPool. ```js const perDay = await readContract("saturnrental", "getRentalDailyRate", [rentalId]); ``` ##### `getRentalStatus(rentalId: number): number` — READ Returns the lifecycle status code. Parameters: - `rentalId` (`number`): The rental to inspect. Returns: `number` — 0=listed, 1=rented, 2=ended, 3=cancelled. Notes: Filter getAllRentalIds() on status 0 to build the open marketplace, status 1 for the active rentals tab. ```js const status = await readContract("saturnrental", "getRentalStatus", [rentalId]); ``` ##### `getRentalPaidThroughTime(rentalId: number): number` — READ Unix timestamp up to which rent has been prepaid. endRental cannot be called before this time. Parameters: - `rentalId` (`number`): The rental to inspect. Returns: `number` — Unix seconds. Notes: Render a countdown in the renter dashboard so they know when to extendRental or expect settlement. ```js const pt = await readContract("saturnrental", "getRentalPaidThroughTime", [rentalId]); const secondsLeft = pt - Math.floor(Date.now() / 1000); ``` ##### `getNextRentalId(): number` — READ Returns the rentalId that will be assigned to the next listing. Parameters: none Returns: `number` — Next rental ID (starts at 1). Notes: Total rentals ever listed = getNextRentalId() - 1. ```js const next = await readContract("saturnrental", "getNextRentalId", []); ``` ##### `getAllRentalIds(): number*` — READ Generator yielding every rentalId ever created. Parameters: none Returns: `number*` — Iterable of rental IDs. Notes: Combine with getRentalStatus() to partition listings into listed / rented / ended / cancelled buckets. ```js const script = ScriptBuilder .begin() .callContract("saturnrental", "getAllRentalIds", []) .endScript(); ``` ### SaturnFeeOptions — `saturnfeeopts` *Fee Rate Options* — Saturn DEX v4 · Financial Products Derivatives on pool fee rates. A pool provider writes an option — a contract that gives a buyer the right, for a fixed duration, to set that pool's fee to a specific target rate. The buyer pays a premium up-front (in any valid token) and earns premium income for the writer. Once bought, the option locks the pool and the buyer can exerciseOption() at any time during the window to snap the fee to the target. Buyers can releaseOption() early to return fee control; after expiry, the writer calls expireOption() to reclaim it. A single pool can carry several options at once, so structured strategies (collars, straddles) are possible. #### Option Lifecycle ##### `writeOption(from: address, poolId: number, targetFeePer10k: number, premium: number, premiumToken: string, durationSeconds: number)` — WRITE Pool provider creates and lists a new option. Captures the pool's current fee rate so it can be restored when the option unwinds. Parameters: - `from` (`address`): Pool provider — must be a transaction witness. - `poolId` (`number`): The active pool you own. - `targetFeePer10k` (`number`): Fee rate the buyer can snap the pool to. Must sit within SaturnAdmin.getPoolFeeRange. - `premium` (`number`): Up-front price the buyer pays, in raw premiumToken units. Must be > 0. - `premiumToken` (`string`): Token symbol the premium is paid in. Must be a validated symbol. - `durationSeconds` (`number`): Option window in seconds once bought. Must be between 3,600 (1h) and 2,592,000 (30d). Notes: A new optionId is created in status 0 (listed). The pool is NOT yet locked — it only locks when someone buys the option. Listing is free. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnfeeopts", "writeOption", [ from, poolId, 50, // target 0.50% fee premiumRaw, // e.g. 100 SOUL raw "SOUL", 604800 // 7 day window ]) .spendGas(from) .endScript(); ``` ##### `cancelListing(from: address, optionId: number)` — WRITE Option writer cancels a listing that has not yet been bought. Only works while status = 0 (listed). Parameters: - `from` (`address`): Must be the writer who created the listing. - `optionId` (`number`): The option to cancel. Notes: Status flips to 3 (cancelled). The optionId remains in allOptionIds for history but is no longer purchasable. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnfeeopts", "cancelListing", [from, optionId]) .spendGas(from) .endScript(); ``` ##### `buyOption(from: address, optionId: number)` — WRITE Buyer pays the premium to the writer and activates the option. The duration window starts now and the pool gets a financial lock. Parameters: - `from` (`address`): Buyer — cannot be the option writer. - `optionId` (`number`): A listing in status 0 (listed). Notes: premium tokens are transferred from buyer → writer in a single Token.transfer. Status flips to 1 (active), startTime = now, endTime = now + durationSeconds. The underlying pool's financial lock increments — it cannot be removed while the option is active. ```js // Always preflight to show the buyer total cost const premium = await readContract("saturnfeeopts", "getOptionPremium", [optionId]); const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnfeeopts", "buyOption", [from, optionId]) .spendGas(from) .endScript(); ``` ##### `exerciseOption(from: address, optionId: number)` — WRITE Buyer snaps the pool's fee rate to the option's target. Can be called any time before the option expires, even multiple times (each call just re-applies the same rate). Parameters: - `from` (`address`): Must be the option buyer. - `optionId` (`number`): An active option (status 1) whose endTime is still in the future. Notes: The pool's active fee rate updates to targetFeePer10k immediately; the next swap uses the new rate. exercised is set to 1 so that releaseOption/expireOption know to restore the writer's original fee when the option unwinds. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnfeeopts", "exerciseOption", [from, optionId]) .spendGas(from) .endScript(); ``` ##### `releaseOption(from: address, optionId: number)` — WRITE Buyer voluntarily gives up fee control before expiry. Useful if the buyer is done with the position and wants to unlock the pool so the writer can list again. Parameters: - `from` (`address`): Must be the option buyer. - `optionId` (`number`): An active option (status 1). Notes: If the option had been exercised, the pool's fee is restored to the writer's original rate. The pool's financial lock decrements and status becomes 4 (released). Premium is NOT refunded — it was consumed at buy time. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnfeeopts", "releaseOption", [from, optionId]) .spendGas(from) .endScript(); ``` ##### `expireOption(from: address, optionId: number)` — WRITE Anyone can trigger expiry after the option's endTime has passed. Typically called by the writer to reclaim full fee control and unlock the pool. Parameters: - `from` (`address`): Any witness — usually the writer. - `optionId` (`number`): An active option whose endTime has already passed. Notes: If the option had been exercised, the pool's fee snaps back to the writer's original rate. The pool's financial lock decrements and status becomes 2 (expired). Reverts with 'Option not expired yet' if called early — poll getOptionEndTime first. ```js const endTime = await readContract("saturnfeeopts", "getOptionEndTime", [optionId]); if (Date.now() / 1000 < endTime) throw new Error("Not expired yet"); const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnfeeopts", "expireOption", [from, optionId]) .spendGas(from) .endScript(); ``` #### Option Views ##### `getOptionInfo(optionId: number): string` — READ One-shot status snapshot used by options marketplace UIs. Returns an underscore-delimited string with the key fields. Parameters: - `optionId` (`number`): The option to inspect. Returns: `string` — pool:_targetFee:_premium:_token:_end:_exercised:<0|1>_status: Notes: Split on '_' and then on ':' to extract each field. Status decodes as 0=listed, 1=active, 2=expired, 3=cancelled, 4=released. ```js const raw = await readContract("saturnfeeopts", "getOptionInfo", [optionId]); const parts = Object.fromEntries(raw.split("_").map(kv => kv.split(":"))); // parts.pool, parts.targetFee, parts.premium, parts.token, parts.end, parts.exercised, parts.status ``` ##### `getOptionPoolId(optionId: number): number` — READ Returns the poolId the option is written on. Parameters: - `optionId` (`number`): The option to inspect. Returns: `number` — Underlying pool ID. Notes: Pair with SaturnRouter.getPoolFullInfo() to show current reserves alongside the option. ```js const poolId = await readContract("saturnfeeopts", "getOptionPoolId", [optionId]); ``` ##### `getOptionWriter(optionId: number): address` — READ Returns the pool provider who wrote (sold) the option. Parameters: - `optionId` (`number`): The option to inspect. Returns: `address` — Option writer. Notes: Use this to permission-gate the cancelListing and expireOption buttons in the provider dashboard. ```js const writer = await readContract("saturnfeeopts", "getOptionWriter", [optionId]); ``` ##### `getOptionBuyer(optionId: number): address` — READ Returns the address holding the option, or @null while still in listed state. Parameters: - `optionId` (`number`): The option to inspect. Returns: `address` — Current option holder, or @null if unbought. Notes: This is the only address authorized to call exerciseOption and releaseOption. ```js const buyer = await readContract("saturnfeeopts", "getOptionBuyer", [optionId]); ``` ##### `getOptionTargetFee(optionId: number): number` — READ Returns the fee rate (per 10,000) that exerciseOption will set on the pool. Parameters: - `optionId` (`number`): The option to inspect. Returns: `number` — Target fee, per 10k. Notes: Show alongside the pool's current fee so users can see what exercising would do. ```js const target = await readContract("saturnfeeopts", "getOptionTargetFee", [optionId]); const pct = (target / 100).toFixed(2) + "%"; ``` ##### `getOptionPremium(optionId: number): number` — READ Returns the premium price, in raw units of premiumToken. Parameters: - `optionId` (`number`): The option to inspect. Returns: `number` — Premium amount (raw). Notes: Call getOptionInfo to pull the token symbol; use SaturnPools scale helpers to format human-readable. ```js const premium = await readContract("saturnfeeopts", "getOptionPremium", [optionId]); ``` ##### `getOptionStatus(optionId: number): number` — READ Returns the lifecycle status code. Parameters: - `optionId` (`number`): The option to inspect. Returns: `number` — 0=listed, 1=active, 2=expired, 3=cancelled, 4=released. Notes: Filter getAllOptionIds() on status 0 for the open marketplace, status 1 for the active book. ```js const status = await readContract("saturnfeeopts", "getOptionStatus", [optionId]); ``` ##### `getOptionEndTime(optionId: number): number` — READ Returns the Unix timestamp when the option expires. Before buy, this field holds the duration in seconds — only meaningful once status = 1. Parameters: - `optionId` (`number`): The option to inspect. Returns: `number` — Unix seconds (while active) or duration (while listed). Notes: Once active, render a live countdown to expiry so buyers know how long they still have to exercise. ```js const end = await readContract("saturnfeeopts", "getOptionEndTime", [optionId]); const secondsLeft = end - Math.floor(Date.now() / 1000); ``` ##### `getNextOptionId(): number` — READ Returns the optionId that will be assigned to the next writeOption call. Parameters: none Returns: `number` — Next option ID (starts at 1). Notes: Total options ever written = getNextOptionId() - 1. ```js const next = await readContract("saturnfeeopts", "getNextOptionId", []); ``` ##### `getAllOptionIds(): number*` — READ Generator yielding every optionId ever created. Parameters: none Returns: `number*` — Iterable of option IDs. Notes: Combine with getOptionStatus() to partition into listed / active / expired / cancelled / released buckets. ```js const script = ScriptBuilder .begin() .callContract("saturnfeeopts", "getAllOptionIds", []) .endScript(); ``` ### SaturnSyndicate — `saturnsyndicate` *Liquidity Syndicate* — Saturn DEX v4 · Financial Products Pool crowdfunding. Anyone can open a syndicate that names a token pair, target amounts, and a pool fee rate. Contributors deposit both tokens (up to each remaining target); once the creator is satisfied, they call activateSyndicate to deploy a real pool from the pooled capital. The syndicate contract itself becomes the pool provider, and each member's share is computed from their tokenA contribution ratio. Members claim their proportional cut of swap fees as long as the syndicate is active, and claim back proportional reserves after the creator dissolves it. During the funding phase or after cancellation, contributors can pull their tokens back with withdrawContribution. #### Syndicate Lifecycle ##### `createSyndicate(from: address, tokenA: string, tokenB: string, targetA: number, targetB: number, feePer10k: number)` — WRITE Anyone can open a new syndicate funding round for a given token pair. Sets the fundraising targets and the eventual pool's fee rate. Parameters: - `from` (`address`): Creator — the address that will be allowed to activate, cancel, and dissolve the syndicate. Must be a witness. - `tokenA` (`string`): First token symbol. Validated by saturnpools. - `tokenB` (`string`): Second token symbol. Must be different from tokenA. - `targetA` (`number`): Raw funding goal for tokenA. Must be > 0. - `targetB` (`number`): Raw funding goal for tokenB. Must be > 0. - `feePer10k` (`number`): Fee rate of the resulting pool, per 10,000. Must be within SaturnAdmin.getPoolFeeRange. Notes: A new syndicateId is created in status 0 (funding). No tokens are moved yet — contributors call contribute() next. Creator retains permission to cancelSyndicate, activateSyndicate, and dissolveSyndicate. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnsyndicate", "createSyndicate", [ from, "SOUL", "KCAL", 100000_00000000, // target 100000 SOUL raw 500000_00000000, // target 500000 KCAL raw 30 // 0.30% pool fee ]) .spendGas(from) .endScript(); ``` ##### `cancelSyndicate(from: address, syndicateId: number)` — WRITE Creator cancels a syndicate before it has been activated. Contributors must then call withdrawContribution to pull their tokens back. Parameters: - `from` (`address`): Must be the syndicate creator. - `syndicateId` (`number`): A syndicate in status 0 (funding). Notes: Status flips to 3 (cancelled). No tokens are returned automatically — every contributor calls withdrawContribution individually to reclaim their deposits. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnsyndicate", "cancelSyndicate", [from, syndicateId]) .spendGas(from) .endScript(); ``` ##### `contribute(from: address, syndicateId: number, amountA: number, amountB: number)` — WRITE Investor deposits both tokens into an open syndicate. Each contribution may not exceed the remaining distance to its target; repeated contributions from the same address are aggregated. Parameters: - `from` (`address`): Contributor. Must be a witness. - `syndicateId` (`number`): A syndicate in status 0 (funding). - `amountA` (`number`): Raw tokenA to deposit. Must be > 0 and <= remaining targetA. - `amountB` (`number`): Raw tokenB to deposit. Must be > 0 and <= remaining targetB. Notes: Both tokens are transferred from you to the syndicate contract in two Token.transfer calls. memberContribA/B grow, raisedA/B grow, and memberCount increments on your first contribution. Your final share of fees is proportional to your tokenA contribution vs totalRaisedA. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnsyndicate", "contribute", [ from, syndicateId, amountARaw, amountBRaw ]) .spendGas(from) .endScript(); ``` ##### `withdrawContribution(from: address, syndicateId: number)` — WRITE Contributor reclaims their deposit. Works while the syndicate is still in funding (status 0) or after it has been cancelled (status 3). Withdraws the entire outstanding contribution in one call. Parameters: - `from` (`address`): Must be a member with a non-zero recorded contribution. - `syndicateId` (`number`): A syndicate in status 0 or 3. Notes: Your full contribA and contribB are transferred back, your member entry is zeroed out, the syndicate's raisedA/B and memberCount decrement. Reverts if you have nothing to withdraw. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnsyndicate", "withdrawContribution", [from, syndicateId]) .spendGas(from) .endScript(); ``` ##### `activateSyndicate(from: address, syndicateId: number)` — WRITE Creator converts the pooled capital into a real pool. The syndicate contract itself becomes the pool provider, and the pool is immediately financial-locked so nobody can accidentally remove it. Parameters: - `from` (`address`): Must be the syndicate creator. - `syndicateId` (`number`): A syndicate in status 0 with raisedA > 0 AND raisedB > 0. Notes: saturnpools.registerPool is called with $THIS_ADDRESS as the provider; the returned poolId is stored. Status becomes 1 (active) and the pool's financialLockCount increments by 1. From now on members can call claimSyndicateReward to harvest their share of swap fees. The creator does NOT have to fully hit targets — whatever has been raised becomes the initial reserves. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnsyndicate", "activateSyndicate", [from, syndicateId]) .spendGas(from) .endScript(); ``` ##### `claimSyndicateReward(from: address, syndicateId: number)` — WRITE Member harvests their proportional share of fees accrued on the syndicate pool. Share is (contribA * 10000) / totalRaisedA basis points. Parameters: - `from` (`address`): Must be a member of the syndicate. - `syndicateId` (`number`): An active syndicate (status 1). Notes: The syndicate pulls all pending provider fees for the pool (in both tokens), scales them down to raw units, and transfers your sharePer10k / 10000 slice to you in both tokens. Callable repeatedly as more fees accrue. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnsyndicate", "claimSyndicateReward", [from, syndicateId]) .spendGas(from) .endScript(); ``` ##### `dissolveSyndicate(from: address, syndicateId: number)` — WRITE Creator winds the syndicate down. Pool's financial lock is released, any pending fees are swept into the syndicate contract, and the pool reserves are cleared. Members then call claimDissolution to get their share of what remains. Parameters: - `from` (`address`): Must be the syndicate creator. - `syndicateId` (`number`): An active syndicate (status 1). Notes: Pool financialLockCount decrements, remaining provider fees are claimed, pool reserves are cleared (pool becomes inactive), and status becomes 2 (dissolved). Reserves now sit as the syndicate contract's token balance awaiting individual claimDissolution calls. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnsyndicate", "dissolveSyndicate", [from, syndicateId]) .spendGas(from) .endScript(); ``` ##### `claimDissolution(from: address, syndicateId: number)` — WRITE After a syndicate has been dissolved, each member claims their proportional share of the remaining reserves in both tokens. Single-claim only — the member entry is zeroed on success. Parameters: - `from` (`address`): Must be a member with a non-zero recorded contribution. - `syndicateId` (`number`): A dissolved syndicate (status 2). Notes: Your slice = (syndicateContractBalance * sharePer10k) / 10000 of each token. Your contribA/B record is cleared to prevent double-claim. Any rounding dust is left in the contract balance. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnsyndicate", "claimDissolution", [from, syndicateId]) .spendGas(from) .endScript(); ``` #### Syndicate Views ##### `getSyndicateInfo(syndicateId: number): string` — READ One-shot status snapshot for marketplace UIs. Returns an underscore-delimited string with the key fields. Parameters: - `syndicateId` (`number`): The syndicate to inspect. Returns: `string` — tokenA:_tokenB:_targetA:_targetB:_raisedA:_raisedB:_fee:_pool:_status:_members: Notes: Split on '_' and then on ':' to decode. Status: 0=funding, 1=active, 2=dissolved, 3=cancelled. Pool field is 0 until activation. ```js const raw = await readContract("saturnsyndicate", "getSyndicateInfo", [syndicateId]); const parts = Object.fromEntries(raw.split("_").map(kv => kv.split(":"))); ``` ##### `getSyndicateTokenA(syndicateId: number): string` — READ Returns tokenA symbol for this syndicate. Parameters: - `syndicateId` (`number`): The syndicate to inspect. Returns: `string` — tokenA symbol. Notes: Pair with getSyndicateRaisedA and getSyndicateTokenB for contribution UIs. ```js const tA = await readContract("saturnsyndicate", "getSyndicateTokenA", [syndicateId]); ``` ##### `getSyndicateTokenB(syndicateId: number): string` — READ Returns tokenB symbol for this syndicate. Parameters: - `syndicateId` (`number`): The syndicate to inspect. Returns: `string` — tokenB symbol. Notes: Used for the second side of contribute() input validation. ```js const tB = await readContract("saturnsyndicate", "getSyndicateTokenB", [syndicateId]); ``` ##### `getSyndicatePoolId(syndicateId: number): number` — READ Returns the poolId created by activateSyndicate, or 0 if not yet active. Parameters: - `syndicateId` (`number`): The syndicate to inspect. Returns: `number` — Underlying pool ID or 0. Notes: Once non-zero, chain to SaturnRouter.getPoolFullInfo to show live pool stats. ```js const poolId = await readContract("saturnsyndicate", "getSyndicatePoolId", [syndicateId]); ``` ##### `getSyndicateStatus(syndicateId: number): number` — READ Returns the lifecycle status code. Parameters: - `syndicateId` (`number`): The syndicate to inspect. Returns: `number` — 0=funding, 1=active, 2=dissolved, 3=cancelled. Notes: Drive different UI states — funding shows contribute button, active shows claim rewards, dissolved shows claim dissolution. ```js const status = await readContract("saturnsyndicate", "getSyndicateStatus", [syndicateId]); ``` ##### `getSyndicateRaisedA(syndicateId: number): number` — READ Returns total raw tokenA raised so far. Parameters: - `syndicateId` (`number`): The syndicate to inspect. Returns: `number` — Raised tokenA (raw). Notes: Render raisedA / targetA as a progress bar; use to compute your personal share ratio for fee estimates. ```js const raisedA = await readContract("saturnsyndicate", "getSyndicateRaisedA", [syndicateId]); ``` ##### `getSyndicateRaisedB(syndicateId: number): number` — READ Returns total raw tokenB raised so far. Parameters: - `syndicateId` (`number`): The syndicate to inspect. Returns: `number` — Raised tokenB (raw). Notes: Render raisedB / targetB as a progress bar. ```js const raisedB = await readContract("saturnsyndicate", "getSyndicateRaisedB", [syndicateId]); ``` ##### `getSyndicateMemberCount(syndicateId: number): number` — READ Returns the number of distinct contributors with currently non-zero recorded contributions. Parameters: - `syndicateId` (`number`): The syndicate to inspect. Returns: `number` — Active member count. Notes: Decrements when a member fully withdraws; goes to zero if everyone withdraws from a cancelled syndicate. ```js const members = await readContract("saturnsyndicate", "getSyndicateMemberCount", [syndicateId]); ``` ##### `getNextSyndicateId(): number` — READ Returns the syndicateId that will be assigned to the next createSyndicate call. Parameters: none Returns: `number` — Next syndicate ID (starts at 1). Notes: Total syndicates ever created = getNextSyndicateId() - 1. ```js const next = await readContract("saturnsyndicate", "getNextSyndicateId", []); ``` ##### `getMemberContribution(syndicateId: number, member: address): string` — READ Returns a single member's outstanding recorded contribution in both tokens. Used by the 'My contribution' panel. Parameters: - `syndicateId` (`number`): The syndicate to inspect. - `member` (`address`): The member address to look up. Returns: `string` — contribA:_contribB: Notes: Both fields are zero after a member fully withdraws or after claimDissolution. Compute share = contribA * 10000 / totalRaisedA for reward estimates. ```js const raw = await readContract("saturnsyndicate", "getMemberContribution", [syndicateId, member]); const [a, b] = raw.split("_").map(p => p.split(":")[1]); ``` ##### `getAllSyndicateIds(): number*` — READ Generator yielding every syndicateId ever created. Parameters: none Returns: `number*` — Iterable of syndicate IDs. Notes: Combine with getSyndicateStatus() to build funding / active / dissolved / cancelled tabs in the explorer. ```js const script = ScriptBuilder .begin() .callContract("saturnsyndicate", "getAllSyndicateIds", []) .endScript(); ``` ### SaturnLaunchpad — `saturnlaunchpad` *Fixed-Price Token Launches* — Saturn DEX v4 · Financial Products SaturnLaunchpad lets any developer or project creator run a fixed-price token sale (launchpad) that, on success, bootstraps a Saturn DEX liquidity pool from the raised funds. Buyers commit quote tokens at a fixed price during a capped funding window; when the minimum fill threshold is met the creator activates the launch, the proceeds seed the pool, and all participants earn trading fees as co-liquidity-providers. If the launch fails or buyers vote to dissolve, every participant can reclaim their funds through dedicated refund paths. #### Create & Configure Launch ##### `createLaunchpad(from: address, tokenA: string, tokenQuote: string, tokensForSale: number, quotePerA: number, poolFeePer10k: number, minFillPer10k: number, minCommitQuote: number, endTime: number)` — WRITE Creates a new fixed-price launchpad. The creator deposits the full `tokensForSale` amount of `tokenA` into escrow at creation time. The sale runs until `endTime` (max 14 days from now) or until the allocation sells out. On activation the sale proceeds and unsold token A seed a Saturn pool at the fee tier set by `poolFeePer10k`. Call `saturnadmin.getMinPoolFeePer10k()` and `getMaxPoolFeePer10k()` to clamp the fee input before submitting. `minFillPer10k` controls the minimum fill required to activate (1000 = 10%, 10000 = 100% sellout required). `minCommitQuote` is the smallest individual commitment accepted. Parameters: - `from` (`address`): Creator's address; must be the transaction witness. Receives unsold tokenA back at activation. - `tokenA` (`string`): Symbol of the token being sold. - `tokenQuote` (`string`): Symbol of the token buyers pay with (e.g. SOUL, KCAL, USDC). - `tokensForSale` (`number`): Total raw units of tokenA being offered. Must be > 0 and available in creator's balance. - `quotePerA` (`number`): Fixed price: how many raw units of tokenQuote buy exactly 1 raw unit of tokenA. Must be > 0. - `poolFeePer10k` (`number`): Trading fee for the post-launch pool, in basis points per 10,000 (e.g. 30 = 0.3%). Must be within admin-configured min/max. - `minFillPer10k` (`number`): Minimum fill needed to activate, per 10,000 of tokensForSale. Range: 1000–10000 (10%–100%). - `minCommitQuote` (`number`): Minimum quote amount per individual commitment. Must be > 0 and a multiple of quotePerA makes clean allocations. - `endTime` (`number`): Unix timestamp when the funding window closes. Must be in the future and at most 14 days (1209600 s) from now. Notes: Reverts if: creator lacks `tokensForSale` of tokenA; endTime is not in the future or exceeds the 14-day cap; minFillPer10k is outside [1000, 10000]; poolFeePer10k is outside admin range; tokenA == tokenQuote. Emits LaunchpadCreated. Returns the new launchpad ID implicitly — read `getNextLaunchpadId()` before calling to know the upcoming ID, or watch the LaunchpadCreated event. ```js const from = "S3myAddress..."; const tx = sb.begin() .allowGas(from) .callContract("saturnlaunchpad", "createLaunchpad", [ from, "MYTOKEN", // tokenA "SOUL", // tokenQuote 1000000, // 1,000,000 MYTOKEN for sale 100, // 100 SOUL per 1 MYTOKEN 30, // 0.3% pool fee 5000, // 50% minimum fill to activate 1000, // min 1,000 SOUL per commit Math.floor(Date.now() / 1000) + 86400 * 7 // 7-day window ]) .spendGas(from) .endScript(); ``` ##### `cancelLaunchpad(from: address, launchpadId: number)` — WRITE Allows the creator to abort their launchpad before any buyers have committed. Immediately returns all escrowed tokenA to the creator and sets the launchpad status to Cancelled (3). Buyers can still call `withdrawCommit` after cancellation, but because no one can commit once the sale ends this is primarily a pre-buyer cleanup path. If buyers have already committed use `proposeDissolve` / `executeDissolve` instead. Parameters: - `from` (`address`): Creator's address; must be the transaction witness. - `launchpadId` (`number`): ID of the launchpad to cancel. Notes: Reverts if: `from` is not the creator; launchpad is not in funding state (status 0); buyer count > 0. Emits LaunchpadCancelled. After this call `getLaunchpadStatus` returns 3 and `getLaunchpadCreatorReclaimed` returns 1. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnlaunchpad", "cancelLaunchpad", [from, launchpadId]) .spendGas(from) .endScript(); ``` #### Contribute ##### `commit(from: address, launchpadId: number, amountQuote: number)` — WRITE Commits quote tokens to a live launchpad, reserving the equivalent tokenA allocation at the fixed price. `amountQuote` must be a multiple of `quotePerA` so the allocation is exact. Multiple calls from the same address accumulate; the buyer's total commitment determines their pool share weight if the launch activates. Buyers cannot commit after `endTime` even if the launchpad status is still 0. Parameters: - `from` (`address`): Buyer's address; must be the transaction witness. Cannot be the launchpad creator. - `launchpadId` (`number`): ID of the launchpad to commit to. - `amountQuote` (`number`): Raw units of tokenQuote to commit. Must be >= minCommitQuote and a multiple of quotePerA. Must not push soldA beyond tokensForSale. Notes: Reverts if: launchpad status != 0; now >= endTime; from == creator; amountQuote < minCommitQuote; amountQuote % quotePerA != 0; commit would oversell the allocation; buyer lacks sufficient tokenQuote balance. Emits CommitMade with the updated total raisedQuote. ```js const from = "S3buyerAddress..."; const tx = sb.begin() .allowGas(from) .callContract("saturnlaunchpad", "commit", [from, 3, 5000]) .spendGas(from) .endScript(); ``` ##### `withdrawCommit(from: address, launchpadId: number)` — WRITE Withdraws the caller's entire committed quote amount and re-opens their reserved tokenA allocation for other buyers. Can be called while the launchpad is in funding state (status 0) — allowing buyers to back out before activation — or after cancellation (status 3). Clears vote state and reward debt so the address is fully reset. If the launchpad is already active (status 1) use `proposeDissolve` instead. Parameters: - `from` (`address`): Buyer's address; must be the transaction witness. - `launchpadId` (`number`): ID of the launchpad to withdraw from. Notes: Reverts if: launchpad status is not 0 (funding) or 3 (cancelled); the caller has no committed quote. Returns the full committed quote token amount. Emits CommitWithdrawn. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnlaunchpad", "withdrawCommit", [from, launchpadId]) .spendGas(from) .endScript(); ``` #### Finalize / Lifecycle ##### `activateLaunchpad(from: address, launchpadId: number)` — WRITE Called by the creator to finalize a successful launch. Transfers the raised quote tokens and the sold tokenA into a new Saturn pool, registers the pool, and locks it under the launchpad's financial lock. The creator's share weight is set to equal the total raised quote (matching buyers 1:1 so the creator holds ~50% of pool fees). Any unsold tokenA is returned to the creator immediately. After activation all participants earn trading fees proportional to their share weight. Parameters: - `from` (`address`): Creator's address; must be the transaction witness. - `launchpadId` (`number`): ID of the launchpad to activate. Notes: Reverts if: from is not the creator; status != 0; soldA == 0; fill ratio soldA/tokensForSale < minFillPer10k/10000 (and it is not a full sellout); either soldA or raisedQuote is below the protocol's minimum pool size. Emits LaunchpadActivated with poolId, soldA, raisedQuote, and unsoldReturned. After success `getLaunchpadStatus` returns 1 and `getLaunchpadPoolId` returns the new pool ID. ```js const tx = sb.begin() .allowGas(creatorAddress) .callContract("saturnlaunchpad", "activateLaunchpad", [creatorAddress, launchpadId]) .spendGas(creatorAddress) .endScript(); ``` ##### `dissolveViaTimeout(from: address, launchpadId: number)` — WRITE Anyone can call this once `endTime` has passed and the launchpad is still in funding state (status 0) without having been activated. Marks the launchpad as dissolved (status 2) and triggers the refund path. Useful when the creator is absent or a buyer wants to recover their funds immediately after the window expires. Does not require a proposal or vote since the timeout is the objective trigger. Parameters: - `from` (`address`): Any witness address — typically a buyer triggering the cleanup. - `launchpadId` (`number`): ID of the launchpad to dissolve. Notes: Reverts if: launchpad status != 0; now < endTime. Emits LaunchpadDissolved with reasonCode 2. After this call participants use `claimFundingRefund` to recover funds. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnlaunchpad", "dissolveViaTimeout", [from, launchpadId]) .spendGas(from) .endScript(); ``` ##### `proposeDissolve(from: address, launchpadId: number)` — WRITE Opens a dissolution proposal for an active or funding launchpad. The proposer must be a buyer (committed quote > 0); the creator cannot propose. The proposer's committed quote is automatically counted as the first vote. A 72-hour timelock (259,200 seconds) starts from proposal time before `executeDissolve` can be called. Only one proposal can be open at a time. Parameters: - `from` (`address`): A buyer's address; must be the transaction witness. Creator is excluded. - `launchpadId` (`number`): ID of the launchpad to target. Notes: Reverts if: status is not 0 or 1; a proposal is already open; from is the creator; from has no committed quote. Emits LaunchpadDissolveProposed with the earliest execution timestamp (proposedAt + 259200). ```js const tx = sb.begin() .allowGas(buyerAddress) .callContract("saturnlaunchpad", "proposeDissolve", [buyerAddress, launchpadId]) .spendGas(buyerAddress) .endScript(); ``` ##### `voteDissolve(from: address, launchpadId: number)` — WRITE Adds the caller's committed quote weight to the open dissolution vote. Each buyer can vote once. Votes are weighted by the buyer's committed quote amount so larger positions carry more weight. A strict majority of total buyer stake (> 50%) is needed for `executeDissolve` to succeed. Parameters: - `from` (`address`): A buyer who has not yet voted; must be the transaction witness. - `launchpadId` (`number`): ID of the launchpad with the open proposal. Notes: Reverts if: status is not 0 or 1; no proposal is open; from is the creator; from has no committed quote; from has already voted. Emits LaunchpadDissolveVoted with the new cumulative vote total. ```js const tx = sb.begin() .allowGas(buyerAddress) .callContract("saturnlaunchpad", "voteDissolve", [buyerAddress, launchpadId]) .spendGas(buyerAddress) .endScript(); ``` ##### `executeDissolve(from: address, launchpadId: number)` — WRITE Executes an approved dissolution proposal once the 72-hour timelock has elapsed and a strict majority of buyer stake has voted yes. For an active launchpad (status 1) this harvests any pending fees, removes pool reserves, and makes the funds claimable via `claimDissolution`. For a funding-state launchpad it simply marks it dissolved, and buyers use `claimFundingRefund`. Parameters: - `from` (`address`): Any buyer (not the creator); must be the transaction witness. - `launchpadId` (`number`): ID of the launchpad to dissolve. Notes: Reverts if: status is not 0 or 1; no proposal is open; now < proposedAt + 259200 (72h timelock); from is the creator; from has no committed quote; cumulative votes * 2 <= totalBuyerStake (majority not reached). If the launchpad is active, additionally reverts if the pool has more than one financial lock or any active reward campaign. Emits LaunchpadDissolved with reasonCode 1. ```js // Check readiness first const earliestExec = await readContract("saturnlaunchpad", "getDissolveEarliestExecute", [launchpadId]); const votes = await readContract("saturnlaunchpad", "getDissolveVotes", [launchpadId]); const raised = await readContract("saturnlaunchpad", "getLaunchpadRaisedQuote", [launchpadId]); // votes * 2 > raised => majority confirmed const tx = sb.begin() .allowGas(buyerAddress) .callContract("saturnlaunchpad", "executeDissolve", [buyerAddress, launchpadId]) .spendGas(buyerAddress) .endScript(); ``` #### Claim & Refund ##### `claimLaunchpadReward(from: address, launchpadId: number)` — WRITE Harvests accumulated trading-fee rewards for a participant in an active launchpad (status 1). Both the creator and buyers earn fees proportional to their share weight (buyers: their committed quote; creator: an equal weight to the total raised quote, set at activation). Triggers a fee harvest from the underlying pool before computing pending amounts, so the payout reflects fees earned up to this block. Can be called as often as desired. Parameters: - `from` (`address`): Participant's address (creator or buyer); must be the transaction witness. - `launchpadId` (`number`): ID of the active launchpad. Notes: Reverts if: launchpad status != 1; from has no share weight (not a participant). Transfers pending tokenA and tokenQuote rewards. Emits LaunchpadRewardsClaimed. Pending amounts may be 0 if no new fees have accrued since the last claim. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnlaunchpad", "claimLaunchpadReward", [from, launchpadId]) .spendGas(from) .endScript(); ``` ##### `claimDissolution(from: address, launchpadId: number)` — WRITE Claims a participant's share of the pool reserves after a post-activation dissolution (status 2 with creatorShareWeight > 0). Each participant receives their proportional share of dissolved tokenA and tokenQuote reserves plus any uncollected fee rewards. The creator and each buyer call this independently. This path is only valid for launchpads that were activated before being dissolved; use `claimFundingRefund` for pre-activation dissolutions. Parameters: - `from` (`address`): Participant's address (creator or buyer); must be the transaction witness. - `launchpadId` (`number`): ID of the dissolved launchpad. Notes: Reverts if: status != 2; creatorShareWeight == 0 (pre-activation dissolve — use claimFundingRefund instead); from has no share weight or has already claimed. Emits LaunchpadDissolutionClaimed with payoutA and payoutB amounts. ```js const tx = sb.begin() .allowGas(from) .callContract("saturnlaunchpad", "claimDissolution", [from, launchpadId]) .spendGas(from) .endScript(); ``` ##### `claimFundingRefund(from: address, launchpadId: number)` — WRITE Refunds participants after a pre-activation dissolution (status 2, creatorShareWeight == 0) — i.e. the launchpad was dissolved by timeout or vote before `activateLaunchpad` was ever called. The creator reclaims their full escrowed tokenA; each buyer reclaims their full committed tokenQuote. The original `tokensForSale` value is preserved for indexers (v4.1.1 audit fix) via a dedicated reclaim flag rather than zeroing the field. Parameters: - `from` (`address`): Creator or buyer address; must be the transaction witness. - `launchpadId` (`number`): ID of the dissolved launchpad. Notes: Reverts if: status != 2; creatorShareWeight > 0 (launchpad was activated — use claimDissolution); caller is the creator and has already reclaimed (launchpadCreatorReclaimed == 1); caller is a buyer with no committed quote. Emits LaunchpadDissolutionClaimed. ```js // Creator reclaims their tokenA: const creatorTx = sb.begin() .allowGas(creatorAddress) .callContract("saturnlaunchpad", "claimFundingRefund", [creatorAddress, launchpadId]) .spendGas(creatorAddress) .endScript(); // Buyer reclaims their tokenQuote: const buyerTx = sb.begin() .allowGas(buyerAddress) .callContract("saturnlaunchpad", "claimFundingRefund", [buyerAddress, launchpadId]) .spendGas(buyerAddress) .endScript(); ``` #### Views — Launchpad Info ##### `getContractVersion(): string` — READ Returns the contract version string. Use to verify which deployment you are talking to. Parameters: none Returns: `string` — e.g. "saturnlaunchpad-4.1.2" ```js const version = await readContract("saturnlaunchpad", "getContractVersion", []); ``` ##### `getLaunchpadInfo(launchpadId: number): string` — READ Returns a packed string summary of a launchpad's key parameters in one call, avoiding N individual round-trips. Format: `tokenA:_tokenQuote:_forSale:_price:_sold:_raised:_status:_pool:_buyers:`. Status codes: 0 = Funding, 1 = Active, 2 = Dissolved, 3 = Cancelled. Parameters: - `launchpadId` (`number`): ID of the launchpad to query. Returns: `string` — Underscore-delimited key:value summary string. ```js const info = await readContract("saturnlaunchpad", "getLaunchpadInfo", [3]); // "tokenA:MYTOKEN_tokenQuote:SOUL_forSale:1000000_price:100_sold:600000_raised:60000000_status:0_pool:0_buyers:4" ``` ##### `getLaunchpadCreator(launchpadId: number): address` — READ Returns the address that created the launchpad. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `address` — Creator's address. ##### `getLaunchpadTokenA(launchpadId: number): string` — READ Returns the symbol of the token being sold. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `string` — Token symbol (e.g. "MYTOKEN"). ##### `getLaunchpadTokenQuote(launchpadId: number): string` — READ Returns the symbol of the quote token buyers pay with. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `string` — Quote token symbol (e.g. "SOUL"). ##### `getLaunchpadTokensForSale(launchpadId: number): number` — READ Returns the original total tokenA amount offered. This field retains its original value even after dissolution (v4.1.1 audit fix). Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Raw units of tokenA originally for sale. ##### `getLaunchpadQuotePerA(launchpadId: number): number` — READ Returns the fixed price: how many raw units of tokenQuote purchase one raw unit of tokenA. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Quote units per tokenA unit. ##### `getLaunchpadPoolFeePer10k(launchpadId: number): number` — READ Returns the trading fee rate (basis points per 10,000) configured for the post-launch pool. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Fee in bps/10k (e.g. 30 = 0.3%). ##### `getLaunchpadMinFillPer10k(launchpadId: number): number` — READ Returns the minimum fill threshold per 10,000 of tokensForSale required to activate. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Min fill ratio (1000–10000). ##### `getLaunchpadMinCommitQuote(launchpadId: number): number` — READ Returns the minimum quote amount accepted per individual commit call. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Raw units of tokenQuote. ##### `getLaunchpadEndTime(launchpadId: number): number` — READ Returns the Unix timestamp at which the funding window closes. Commits are rejected at or after this time. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Unix timestamp (seconds). ##### `getLaunchpadSoldA(launchpadId: number): number` — READ Returns how many raw units of tokenA have been reserved by buyers so far. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Raw units of tokenA sold/reserved. ##### `getLaunchpadRaisedQuote(launchpadId: number): number` — READ Returns the total raw units of tokenQuote committed by all buyers. This doubles as the total buyer share weight. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Total tokenQuote committed. ##### `getLaunchpadBuyerCount(launchpadId: number): number` — READ Returns the number of distinct buyer addresses that have an active commitment. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Number of unique buyers with non-zero commitments. ##### `getLaunchpadStatus(launchpadId: number): number` — READ Returns the launchpad lifecycle status. 0 = Funding, 1 = Active (pool live), 2 = Dissolved, 3 = Cancelled. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Status code: 0 Funding | 1 Active | 2 Dissolved | 3 Cancelled. ##### `getLaunchpadPoolId(launchpadId: number): number` — READ Returns the Saturn pool ID that was created when the launchpad activated. Returns 0 if not yet activated. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Pool ID, or 0 if not yet active. ##### `getLaunchpadCreatorShareWeight(launchpadId: number): number` — READ Returns the creator's fee-sharing weight. Set to `raisedQuote` at activation (equal to total buyer stake) and zeroed when the creator claims dissolution. A value of 0 on a dissolved launchpad means the creator has either already claimed or the launchpad dissolved pre-activation. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Creator share weight in quote units. ##### `getLaunchpadCreatorReclaimed(launchpadId: number): number` — READ Returns 1 if the creator has already reclaimed their escrowed tokenA after a pre-activation dissolution or cancellation, 0 if not yet reclaimed. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — 0 = not yet reclaimed; 1 = already reclaimed. ##### `getNextLaunchpadId(): number` — READ Returns the ID that will be assigned to the next launchpad. Read this before `createLaunchpad` to predict the upcoming ID for event matching. Parameters: none Returns: `number` — Next launchpad ID (starts at 1, increments by 1). ```js const nextId = await readContract("saturnlaunchpad", "getNextLaunchpadId", []); ``` ##### `getBuyerCommittedQuote(launchpadId: number, buyer: address): number` — READ Returns the total raw tokenQuote amount currently committed by a specific buyer in a launchpad. Returns 0 if the buyer has not committed or has fully withdrawn. Parameters: - `launchpadId` (`number`): ID of the launchpad. - `buyer` (`address`): Address to query. Returns: `number` — Committed tokenQuote amount, or 0. ```js const committed = await readContract("saturnlaunchpad", "getBuyerCommittedQuote", [3, "S3buyerAddress..."]); ``` ##### `getLaunchpadAccFeePerShareA(launchpadId: number): number` — READ Returns the accumulated fee-per-share accumulator for tokenA (scaled by 1e12). Used internally to compute pending rewards; expose in UIs for advanced fee accounting. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Accumulated tokenA fee per share * 1e12. ##### `getLaunchpadAccFeePerShareB(launchpadId: number): number` — READ Returns the accumulated fee-per-share accumulator for tokenQuote (tokenB), scaled by 1e12. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Accumulated tokenQuote fee per share * 1e12. ##### `getLaunchpadDissolvedResA(launchpadId: number): number` — READ Returns the raw tokenA reserves captured at dissolution. Used to compute each participant's claimDissolution payout. Parameters: - `launchpadId` (`number`): ID of the dissolved launchpad. Returns: `number` — Raw tokenA dissolved reserve; 0 if not dissolved or pre-activation dissolve. ##### `getLaunchpadDissolvedResB(launchpadId: number): number` — READ Returns the raw tokenQuote reserves captured at dissolution. Parameters: - `launchpadId` (`number`): ID of the dissolved launchpad. Returns: `number` — Raw tokenQuote dissolved reserve; 0 if not dissolved or pre-activation dissolve. #### Views — Dissolution & Governance ##### `getDissolveProposed(launchpadId: number): number` — READ Returns 1 if a dissolution proposal is currently open, 0 otherwise. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — 1 = proposal open; 0 = none. ##### `getDissolveVotes(launchpadId: number): number` — READ Returns the total committed-quote weight of all votes cast for the current dissolution proposal. Compare to `getLaunchpadRaisedQuote` to gauge majority: votes * 2 > raisedQuote means the vote has passed. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Cumulative vote weight in raw tokenQuote units. ```js const votes = await readContract("saturnlaunchpad", "getDissolveVotes", [launchpadId]); const raised = await readContract("saturnlaunchpad", "getLaunchpadRaisedQuote", [launchpadId]); const majorityReached = votes * 2 > raised; ``` ##### `getDissolveProposedAt(launchpadId: number): number` — READ Returns the Unix timestamp when the current dissolution proposal was opened. Returns 0 if no proposal exists. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Unix timestamp of proposal, or 0. ##### `getDissolveEarliestExecute(launchpadId: number): number` — READ Returns the earliest Unix timestamp at which `executeDissolve` can be called (proposedAt + 72 hours). Returns 0 if no proposal exists. Use this to show a countdown timer in your UI. Parameters: - `launchpadId` (`number`): ID of the launchpad. Returns: `number` — Earliest executable timestamp, or 0 if no proposal. ```js const earliest = await readContract("saturnlaunchpad", "getDissolveEarliestExecute", [launchpadId]); const secondsRemaining = Math.max(0, earliest - Math.floor(Date.now() / 1000)); ``` ##### `getBuyerHasVoted(launchpadId: number, buyer: address): number` — READ Returns 1 if the given buyer has already cast their dissolution vote for the open proposal, 0 otherwise. Also returns 0 after the buyer's commitment is cleared (e.g. after withdrawCommit). Parameters: - `launchpadId` (`number`): ID of the launchpad. - `buyer` (`address`): Buyer address to check. Returns: `number` — 1 = voted; 0 = not voted. ```js const hasVoted = await readContract("saturnlaunchpad", "getBuyerHasVoted", [launchpadId, "S3buyerAddress..."]); ``` #### Views — Lists & Batch Data ##### `getAllLaunchpadIds(): number*` — READ Returns all launchpad IDs ever created, in creation order. The return type `number*` is a Tomb generator/stream; the Phantasma SDK deserializes it as an array. Use to build a full launchpad registry. Parameters: none Returns: `number*` — Stream of all launchpad IDs. ```js const ids = await readContract("saturnlaunchpad", "getAllLaunchpadIds", []); ``` ##### `getActiveLaunchpadIds(): number*` — READ Returns only IDs of launchpads in Active state (status 1 — pool is live). Useful for listing post-launch pools that are still earning fees. Parameters: none Returns: `number*` — Stream of active launchpad IDs. ```js const activeIds = await readContract("saturnlaunchpad", "getActiveLaunchpadIds", []); ``` ##### `getFundingLaunchpadIds(): number*` — READ Returns only IDs of launchpads currently in Funding state (status 0). Use to populate an "open sales" listing. Parameters: none Returns: `number*` — Stream of funding launchpad IDs. ```js const fundingIds = await readContract("saturnlaunchpad", "getFundingLaunchpadIds", []); ``` ##### `getActiveLaunchpadsData(): string*` — READ Returns a stream of encoded rows for all active launchpads (status 1) in one call, eliminating N per-field round-trips. Each row is pipe-delimited: launchpadId|tokenA|tokenQuote|tokensForSale|quotePerA|soldA|raisedQuote|buyerCount|status|poolId|endTime. Parameters: none Returns: `string*` — Stream of pipe-delimited launchpad summary rows. ```js const rows = await readContract("saturnlaunchpad", "getActiveLaunchpadsData", []); // rows[0] => "3|MYTOKEN|SOUL|1000000|100|1000000|100000000|12|1|42|1704067200" ``` ##### `getUserLaunchpadsData(user: address): string*` — READ Returns a stream of encoded launchpad rows for every launchpad in which the given user address currently has a non-zero committed quote. Same row format as getActiveLaunchpadsData. Use to show a user's portfolio of active commitments. Parameters: - `user` (`address`): Address to look up. Returns: `string*` — Stream of pipe-delimited rows for launchpads the user has committed to. ```js const myLaunchpads = await readContract("saturnlaunchpad", "getUserLaunchpadsData", ["S3myAddress..."]); ``` ## Agent Automation On-chain primitives built for AI agents: flash arbitrage, limit orders, prediction markets, strategy vaults, and stake-arbitrage. ### SaturnArbitrage — `saturnarb` *Flash Arbitrage Engine* — Saturn DEX v4 · Agent Automation Atomic cross-pool arbitrage for agents. No flash loans needed — the executor provides the starting capital in tokenStart, the contract routes through two pools that share the same pair (buy cheap on one, sell expensive on the other), and the net gain is split between the executor and the protocol treasury. If the round-trip does not produce strictly more tokenStart than it started with, or if the profit is below minProfit, the entire transaction reverts — there is no downside beyond gas. Agents monitor on-chain prices, compute opportunities off-chain, and fire executeArbitrage when they find a profitable path. #### Arbitrage Execution ##### `executeArbitrage(from: address, poolIdBuy: number, poolIdSell: number, tokenStart: string, amountIn: number, minProfit: number)` — WRITE Atomic two-hop arbitrage. Swaps amountIn of tokenStart into the intermediate token on poolIdBuy (the cheaper pool), then swaps the intermediate back to tokenStart on poolIdSell (the more expensive pool). The intermediate token is inferred from poolIdBuy — whichever side isn't tokenStart. Both pools must contain the exact same pair. On success you receive your original capital plus your share of the profit in a single transfer; the protocol's share goes to the admin treasury. Parameters: - `from` (`address`): Executor address — must be a witness and must hold amountIn of tokenStart. - `poolIdBuy` (`number`): Pool where tokenStart is cheap — the first hop swaps tokenStart into the intermediate token here. - `poolIdSell` (`number`): Pool where tokenStart is expensive — the second hop swaps the intermediate back to tokenStart here. Must share the same pair as poolIdBuy and must be different from it. - `tokenStart` (`string`): Token you provide and receive back. Must be one of the two tokens in both pools. - `amountIn` (`number`): Raw amount of tokenStart to commit to the arb. Must be > 0 and <= your wallet balance. - `minProfit` (`number`): Minimum acceptable profit, in raw tokenStart units (pre-split). Set to your gas cost + desired buffer; the transaction reverts with 'Profit below minimum' if not met. Notes: amountIn tokenStart is moved from your wallet to the SwapEngine, then routed through two swaps against the SwapEngine. If finalAmount > amountIn and profit >= minProfit, you receive (amountIn + executorSharePer100% of profit) back and the protocol receives the remainder — both in a single tx. If either condition fails the whole call reverts and nothing moves. Your personal stats (executorArbCount, executorTotalProfit) update on success. ```js // Off-chain: compare prices and pick the direction const executorShare = await readContract("saturnarb", "getExecutorSharePer100", []); const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnarb", "executeArbitrage", [ from, cheapPoolId, // buy side richPoolId, // sell side "SOUL", // tokenStart amountInRaw, minProfitRaw // bail if profit < this ]) .spendGas(from) .endScript(); ``` #### Arbitrage Stats ##### `getExecutorSharePer100(): number` — READ Returns the percentage of each arb's profit that goes to the executor (default 80). The remainder goes to the protocol treasury. Parameters: none Returns: `number` — Executor share as a whole-number percent (50..95). Notes: Read before firing an arb to compute your expected take-home profit: executorProfit = rawProfit * share / 100. ```js const share = await readContract("saturnarb", "getExecutorSharePer100", []); // net expected = rawProfit * share / 100 ``` ##### `getTotalArbsExecuted(): number` — READ Returns the total number of successful arbitrage executions across all executors since deploy. Parameters: none Returns: `number` — Cumulative successful arbs. Notes: Use for protocol activity dashboards — increments once per successful executeArbitrage call. ```js const total = await readContract("saturnarb", "getTotalArbsExecuted", []); ``` ##### `getTotalProfitGenerated(): number` — READ Returns the total gross profit (pre-split) generated by arbitrage, summed across all tokens in raw units. Parameters: none Returns: `number` — Cumulative raw profit. Notes: Note: this is summed across whatever tokenStart each arb used, so it mixes units and is only meaningful as a momentum metric, not a USD figure. ```js const raw = await readContract("saturnarb", "getTotalProfitGenerated", []); ``` ##### `getExecutorArbCount(executor: address): number` — READ Returns the number of successful arbs executed by a specific executor address. Parameters: - `executor` (`address`): The agent address to look up. Returns: `number` — Per-executor successful arb count. Notes: Drive a leaderboard of top agents, or gate additional agent features behind a minimum count. ```js const mine = await readContract("saturnarb", "getExecutorArbCount", [me]); ``` ##### `getExecutorTotalProfit(executor: address): number` — READ Returns the total profit (post-split, i.e. the executor's take-home) earned by a specific executor. Parameters: - `executor` (`address`): The agent address to look up. Returns: `number` — Cumulative executor-take-home profit, raw. Notes: Like getTotalProfitGenerated, this sums across whatever tokenStart was used per arb, so it's a momentum metric. ```js const earned = await readContract("saturnarb", "getExecutorTotalProfit", [me]); ``` ### SaturnLimit — `saturnlimit` *On-Chain Limit Orders* — Saturn DEX v4 · Agent Automation Set-and-forget limit orders. A user deposits the token they want to sell, names the pool, the token they want back, the minimum acceptable output (their limit price), a bounty percentage the executor agent will earn, and an optional expiry. The order sits on-chain until an agent notices the pool's price has crossed the limit and calls executeOrder — the swap fires atomically, the user receives tokenOut minus the bounty, the agent pockets the bounty as their reward for finding the trigger. Users can cancelOrder at any time for a full refund, and anyone can mark expired orders so the tokens flow back to the owner. #### Order Lifecycle ##### `placeOrder(from: address, poolId: number, tokenIn: string, tokenOut: string, amountIn: number, minAmountOut: number, bountyPer10k: number, expiryTime: number)` — WRITE Deposit tokenIn and create a limit order. The swap will fire when an agent triggers executeOrder and the pool's current output meets minAmountOut (the constant-product slippage check enforces this). Parameters: - `from` (`address`): Order owner — must be a witness and must hold amountIn of tokenIn. - `poolId` (`number`): The pool the eventual swap will route through. Must be active. - `tokenIn` (`string`): Token you are selling. Must differ from tokenOut. - `tokenOut` (`string`): Token you want in return. - `amountIn` (`number`): Raw amount of tokenIn to deposit. Must be > 0. - `minAmountOut` (`number`): Minimum acceptable raw tokenOut — this is your limit price. Must be > 0. - `bountyPer10k` (`number`): Agent bounty in basis points of tokenOut. Must be between 10 (0.10%) and 500 (5%). - `expiryTime` (`number`): Unix timestamp when the order auto-expires. Pass 0 for no expiry. If non-zero, must be in the future. Notes: amountIn tokenIn is pulled from your wallet into the limit-order escrow. A new orderId is assigned in status 0 (active) and added to activeOrderIds. totalOrdersPlaced increments. No swap happens yet — you wait for an agent. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnlimit", "placeOrder", [ from, poolId, "SOUL", "KCAL", amountInRaw, minOutRaw, // your limit price 50, // 0.50% bounty for the executor agent 0 // no expiry ]) .spendGas(from) .endScript(); ``` ##### `cancelOrder(from: address, orderId: number)` — WRITE Order owner cancels an active order and gets the full deposit refunded. Only works while status = 0 (active). Parameters: - `from` (`address`): Must be the order owner. - `orderId` (`number`): An active order. Notes: Status flips to 2 (cancelled) and your full amountIn of tokenIn is transferred back to you in a single Token.transfer. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnlimit", "cancelOrder", [from, orderId]) .spendGas(from) .endScript(); ``` ##### `executeOrder(from: address, orderId: number)` — WRITE Agent triggers an active order. The contract forwards tokenIn to the SwapEngine with minAmountOut enforced — if the current pool output is below the limit, the swap reverts and the whole transaction rolls back so nothing is lost. On success the owner is paid (amountOut - bounty) and the executor pockets the bounty. Parameters: - `from` (`address`): Executor agent — any witness. Does not need to own the order. - `orderId` (`number`): An active order whose expiryTime has not passed. Notes: The limit-order escrow sends amountIn to the SwapEngine, swapFromContract returns amountOut. bounty = amountOut * bountyPer10k / 10000 goes to the executor; ownerPayout = amountOut - bounty goes to the owner. Status becomes 1 (executed) and totalOrdersExecuted increments. Reverts cleanly if the pool can't honor minAmountOut. ```js // Off-chain agent loop: // - fetch getAllActiveOrderIds // - for each, simulate the swap and check if output >= minAmountOut // - fire executeOrder only when profitable const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnlimit", "executeOrder", [from, orderId]) .spendGas(from) .endScript(); ``` ##### `expireOrder(from: address, orderId: number)` — WRITE Anyone can mark an order as expired once its expiryTime has passed. The deposit is refunded to the owner in the same call — a convenience for long-tail cleanup. Parameters: - `from` (`address`): Any caller — no witness check on from. - `orderId` (`number`): An active order with expiry > 0 and now >= expiry. Notes: Status flips to 3 (expired) and the owner's full tokenIn deposit is returned in one transfer. Reverts if the order has no expiry set or if it hasn't actually expired yet. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnlimit", "expireOrder", [from, orderId]) .spendGas(from) .endScript(); ``` #### Order Views ##### `getOrderInfo(orderId: number): string` — READ One-shot status snapshot. Returns an underscore-delimited string with the key fields. Parameters: - `orderId` (`number`): The order to inspect. Returns: `string` — pool:_in:_out:_amtIn:_minOut:_bounty:_expiry:_status: Notes: Split on '_' and then on ':' to decode. Status: 0=active, 1=executed, 2=cancelled, 3=expired. ```js const raw = await readContract("saturnlimit", "getOrderInfo", [orderId]); const parts = Object.fromEntries(raw.split("_").map(kv => kv.split(":"))); ``` ##### `getOrderOwner(orderId: number): address` — READ Returns the address that placed the order. Parameters: - `orderId` (`number`): The order to inspect. Returns: `address` — Order owner. Notes: Only this address can call cancelOrder. ```js const owner = await readContract("saturnlimit", "getOrderOwner", [orderId]); ``` ##### `getOrderPoolId(orderId: number): number` — READ Returns the poolId the order will route through. Parameters: - `orderId` (`number`): The order to inspect. Returns: `number` — Target pool ID. Notes: Use SaturnRouter.getPoolFullInfo to show the pool's current state next to the order. ```js const poolId = await readContract("saturnlimit", "getOrderPoolId", [orderId]); ``` ##### `getOrderTokenIn(orderId: number): string` — READ Returns the symbol of the token the owner deposited. Parameters: - `orderId` (`number`): The order to inspect. Returns: `string` — tokenIn symbol. Notes: Used to label the 'selling' side of the order row. ```js const sym = await readContract("saturnlimit", "getOrderTokenIn", [orderId]); ``` ##### `getOrderTokenOut(orderId: number): string` — READ Returns the symbol of the token the owner wants to receive. Parameters: - `orderId` (`number`): The order to inspect. Returns: `string` — tokenOut symbol. Notes: Used to label the 'buying' side of the order row. ```js const sym = await readContract("saturnlimit", "getOrderTokenOut", [orderId]); ``` ##### `getOrderAmountIn(orderId: number): number` — READ Returns the raw amount of tokenIn locked in the order. Parameters: - `orderId` (`number`): The order to inspect. Returns: `number` — Raw deposited amount. Notes: Pair with SaturnPools scaling helpers to format human-readable. ```js const raw = await readContract("saturnlimit", "getOrderAmountIn", [orderId]); ``` ##### `getOrderMinAmountOut(orderId: number): number` — READ Returns the limit price as a raw minimum tokenOut amount. Parameters: - `orderId` (`number`): The order to inspect. Returns: `number` — Raw minAmountOut. Notes: Divide by getOrderAmountIn (after scaling both sides) to display the effective price. ```js const minOut = await readContract("saturnlimit", "getOrderMinAmountOut", [orderId]); ``` ##### `getOrderStatus(orderId: number): number` — READ Returns the lifecycle status code. Parameters: - `orderId` (`number`): The order to inspect. Returns: `number` — 0=active, 1=executed, 2=cancelled, 3=expired. Notes: Active orders are the only ones worth scanning for execution triggers. ```js const status = await readContract("saturnlimit", "getOrderStatus", [orderId]); ``` ##### `getNextOrderId(): number` — READ Returns the orderId that will be assigned to the next placeOrder call. Parameters: none Returns: `number` — Next order ID (starts at 1). Notes: Total orders ever placed = getNextOrderId() - 1 (matches getTotalOrdersPlaced). ```js const next = await readContract("saturnlimit", "getNextOrderId", []); ``` ##### `getTotalOrdersPlaced(): number` — READ Returns the cumulative number of orders ever placed. Parameters: none Returns: `number` — Cumulative placed count. Notes: Use for activity / volume dashboards. ```js const placed = await readContract("saturnlimit", "getTotalOrdersPlaced", []); ``` ##### `getTotalOrdersExecuted(): number` — READ Returns the cumulative number of orders that have been successfully executed. Parameters: none Returns: `number` — Cumulative executed count. Notes: Fill ratio = getTotalOrdersExecuted() / getTotalOrdersPlaced(). ```js const executed = await readContract("saturnlimit", "getTotalOrdersExecuted", []); ``` ##### `getAllActiveOrderIds(): number*` — READ Generator yielding every orderId ever added to the active list. Note: once an order transitions out of active, this list is not pruned — filter by getOrderStatus() to see only status 0. Parameters: none Returns: `number*` — Iterable of order IDs. Notes: Agent loops read this, filter on status 0, simulate the swap, and fire executeOrder when profitable. UI 'My orders' views filter additionally by getOrderOwner. ```js const script = ScriptBuilder .begin() .callContract("saturnlimit", "getAllActiveOrderIds", []) .endScript(); ``` ### SaturnPredict — `saturnpredict` *Pool Performance Prediction Market* — Saturn DEX v4 · Agent Automation Bet on whether a pool's on-chain metric will exceed a threshold by a set deadline. At market creation the contract snapshots the current metric value (either accumulated provider fees across both tokens, or reserveA + reserveB). Users bet OVER or UNDER with a token of the market's choosing. After the endTime, the first caller of claimWinnings triggers self-resolution — the contract re-reads the metric, compares delta vs threshold, marks the winning side, and pays every caller their proportional share of the pot minus the protocol fee. No oracles, no keepers. If one side of the market ends up empty (no opponents), the populated side uses claimRefund to pull their bets back. #### Market Lifecycle ##### `createMarket(from: address, poolId: number, metricType: number, threshold: number, endTime: number, betToken: string, minBet: number)` — WRITE Open a new prediction market on a pool metric. The current metric value is snapshotted into storage so resolution at endTime can compute the delta. Parameters: - `from` (`address`): Market creator — must be a witness. Has permission to cancelMarket before bets are placed. - `poolId` (`number`): The active pool whose metric is being forecast. - `metricType` (`number`): 1 = pool's accumulated provider fees (getAccruedFees summed across both tokens). 2 = reserve growth (reserveA + reserveB). - `threshold` (`number`): How much the metric must INCREASE over the snapshot for OVER to win. Must be > 0 and is in raw metric units. - `endTime` (`number`): Unix timestamp when betting closes and resolution is allowed. Must be at least 3600 seconds in the future. - `betToken` (`string`): Token symbol used for all bets and payouts. Must be a validated symbol. - `minBet` (`number`): Minimum raw bet amount any single wager must meet. Notes: A new marketId is assigned in status 0 (open). The metric is read and stored in marketSnapshotValue. totalMarketsCreated increments. Now both betOver and betUnder are open until endTime. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnpredict", "createMarket", [ from, poolId, 2, // 1=fees, 2=reserves thresholdRaw, // must grow by at least this much Math.floor(Date.now()/1000) + 86400, // 24h "SOUL", 1_00000000 // min bet 1 SOUL raw ]) .spendGas(from) .endScript(); ``` ##### `betOver(from: address, marketId: number, amount: number)` — WRITE Place (or add to) a bet that the pool's metric delta will meet or exceed the threshold by endTime. Parameters: - `from` (`address`): Bettor. Must be a witness and hold amount of betToken. - `marketId` (`number`): An open market (status 0). - `amount` (`number`): Raw betToken to wager. Must be >= marketMinBet. Notes: amount of betToken is pulled from you into the market escrow. Your overBetAmount entry grows (supports adding to an existing bet), marketTotalOver grows, totalBetsPlaced increments. Reverts if now >= endTime. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnpredict", "betOver", [from, marketId, amountRaw]) .spendGas(from) .endScript(); ``` ##### `betUnder(from: address, marketId: number, amount: number)` — WRITE Place (or add to) a bet that the pool's metric delta will NOT meet the threshold by endTime. Parameters: - `from` (`address`): Bettor. Must be a witness and hold amount of betToken. - `marketId` (`number`): An open market (status 0). - `amount` (`number`): Raw betToken to wager. Must be >= marketMinBet. Notes: Symmetric to betOver — your underBetAmount grows, marketTotalUnder grows, totalBetsPlaced increments. Reverts if now >= endTime. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnpredict", "betUnder", [from, marketId, amountRaw]) .spendGas(from) .endScript(); ``` ##### `claimWinnings(from: address, marketId: number)` — WRITE After endTime, claim your share of the pot. The very first caller on a still-open market triggers self-resolution — the metric is re-read, delta computed, status flipped to resolved_over (1) or resolved_under (2). Every caller (first or later) then receives their proportional slice of the total pot if they bet on the winning side. Parameters: - `from` (`address`): Caller. Must be a witness and must not have claimed this market before. - `marketId` (`number`): A market whose endTime has passed and which is not cancelled (status 3). Notes: hasClaimed is flipped so you can only claim once. If you bet on the losing side the call still completes — it just pays nothing. If you won: grossPayout = yourBet * totalPot / winningSideTotal, a protocolFeePer10k slice is sent to the admin treasury, and the net reaches your wallet in one transfer. ```js // Check readiness first const endT = await readContract("saturnpredict", "getMarketEndTime", [marketId]); if (Date.now()/1000 < endT) throw new Error("Not ended yet"); const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnpredict", "claimWinnings", [from, marketId]) .spendGas(from) .endScript(); ``` ##### `cancelMarket(from: address, marketId: number)` — WRITE Creator cancels a market, but only before anyone has placed a bet. Once there's money on either side, cancelling is blocked — use the natural flow instead. Parameters: - `from` (`address`): Must be the market creator. - `marketId` (`number`): An open market with zero totalOver AND zero totalUnder. Notes: Status flips to 3 (cancelled). Nothing is transferred because no funds had been collected. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnpredict", "cancelMarket", [from, marketId]) .spendGas(from) .endScript(); ``` ##### `claimRefund(from: address, marketId: number)` — WRITE If one side of the book has zero bets at expiry, there is no opponent to play against — the populated side can simply withdraw everything they put in. This path bypasses resolution and protocol fees. Parameters: - `from` (`address`): Bettor with non-zero bets on the populated side. - `marketId` (`number`): A market whose endTime has passed where at least one of totalOver / totalUnder is zero. Notes: Your full (overBet + underBet) is transferred back to you. hasClaimed is flipped to prevent double-refund. Reverts with 'Both sides have bets' if you should be calling claimWinnings instead. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnpredict", "claimRefund", [from, marketId]) .spendGas(from) .endScript(); ``` #### Market Views ##### `getMarketInfo(marketId: number): string` — READ One-shot snapshot for the market page. Returns an underscore-delimited string with the key fields. Parameters: - `marketId` (`number`): The market to inspect. Returns: `string` — pool:_metric:<1|2>_threshold:_end:_snapshot:_over:_under:_status: Notes: Split on '_' and then on ':' to decode. Status: 0=open, 1=resolved_over, 2=resolved_under, 3=cancelled. Compute implied odds with totalOver vs totalUnder. ```js const raw = await readContract("saturnpredict", "getMarketInfo", [marketId]); const parts = Object.fromEntries(raw.split("_").map(kv => kv.split(":"))); ``` ##### `getMarketPoolId(marketId: number): number` — READ Returns the poolId being forecasted. Parameters: - `marketId` (`number`): The market to inspect. Returns: `number` — Underlying pool ID. Notes: Pair with SaturnRouter.getPoolFullInfo to render pool context next to the bet slip. ```js const poolId = await readContract("saturnpredict", "getMarketPoolId", [marketId]); ``` ##### `getMarketMetricType(marketId: number): number` — READ Returns the metric this market is tracking. Parameters: - `marketId` (`number`): The market to inspect. Returns: `number` — 1 = provider fee earnings, 2 = reserve sum growth. Notes: Use this to label the bet question: '... fees grow by at least X?' vs '... reserves grow by at least X?'. ```js const kind = await readContract("saturnpredict", "getMarketMetricType", [marketId]); ``` ##### `getMarketThreshold(marketId: number): number` — READ Returns the required delta (in raw metric units) for OVER to win. Parameters: - `marketId` (`number`): The market to inspect. Returns: `number` — Threshold value. Notes: OVER wins iff (current metric - snapshot) >= threshold. ```js const t = await readContract("saturnpredict", "getMarketThreshold", [marketId]); ``` ##### `getMarketEndTime(marketId: number): number` — READ Returns the Unix timestamp when betting closes and resolution unlocks. Parameters: - `marketId` (`number`): The market to inspect. Returns: `number` — Unix seconds. Notes: Drives the countdown and the enable/disable state of the bet buttons. ```js const endT = await readContract("saturnpredict", "getMarketEndTime", [marketId]); ``` ##### `getMarketSnapshotValue(marketId: number): number` — READ Returns the raw metric value captured at market creation. Parameters: - `marketId` (`number`): The market to inspect. Returns: `number` — Snapshot value. Notes: Subtract from the current live metric value to display 'delta so far' progress. ```js const snap = await readContract("saturnpredict", "getMarketSnapshotValue", [marketId]); ``` ##### `getMarketTotalOver(marketId: number): number` — READ Returns the total amount wagered on OVER. Parameters: - `marketId` (`number`): The market to inspect. Returns: `number` — Raw total. Notes: Combine with getMarketTotalUnder for implied odds: p(over) = over / (over + under). ```js const over = await readContract("saturnpredict", "getMarketTotalOver", [marketId]); ``` ##### `getMarketTotalUnder(marketId: number): number` — READ Returns the total amount wagered on UNDER. Parameters: - `marketId` (`number`): The market to inspect. Returns: `number` — Raw total. Notes: If either side is zero once the market ends, the populated side claims via claimRefund. ```js const under = await readContract("saturnpredict", "getMarketTotalUnder", [marketId]); ``` ##### `getMarketStatus(marketId: number): number` — READ Returns the lifecycle status code. Parameters: - `marketId` (`number`): The market to inspect. Returns: `number` — 0=open, 1=resolved_over, 2=resolved_under, 3=cancelled. Notes: 0 means bets still accepted (if before endTime), 1 / 2 mean claim winnings now. ```js const status = await readContract("saturnpredict", "getMarketStatus", [marketId]); ``` ##### `getNextMarketId(): number` — READ Returns the marketId that will be assigned to the next createMarket call. Parameters: none Returns: `number` — Next market ID (starts at 1). Notes: Total markets ever created = getNextMarketId() - 1. ```js const next = await readContract("saturnpredict", "getNextMarketId", []); ``` ##### `getTotalMarketsCreated(): number` — READ Returns the cumulative number of markets created. Parameters: none Returns: `number` — Cumulative market count. Notes: Use for activity dashboards. ```js const n = await readContract("saturnpredict", "getTotalMarketsCreated", []); ``` ##### `getTotalBetsPlaced(): number` — READ Returns the cumulative number of betOver + betUnder calls across all markets. Parameters: none Returns: `number` — Cumulative bet count. Notes: Note this counts calls, not distinct addresses — a user adding to a bet increments the counter again. ```js const total = await readContract("saturnpredict", "getTotalBetsPlaced", []); ``` ##### `getProtocolFeePer10k(): number` — READ Returns the protocol fee taken from winning payouts, in basis points (default 200 = 2%). Parameters: none Returns: `number` — Fee, per 10,000. Notes: Use to estimate net payout: netPayout = grossPayout * (10000 - feePer10k) / 10000. ```js const fee = await readContract("saturnpredict", "getProtocolFeePer10k", []); ``` ##### `getUserOverBet(marketId: number, user: address): number` — READ Returns a user's total raw wager on OVER for a specific market. Parameters: - `marketId` (`number`): The market to inspect. - `user` (`address`): The address to look up. Returns: `number` — Raw OVER bet. Notes: Use for the 'My position' panel and to compute the user's expected payout. ```js const mine = await readContract("saturnpredict", "getUserOverBet", [marketId, me]); ``` ##### `getUserUnderBet(marketId: number, user: address): number` — READ Returns a user's total raw wager on UNDER for a specific market. Parameters: - `marketId` (`number`): The market to inspect. - `user` (`address`): The address to look up. Returns: `number` — Raw UNDER bet. Notes: Use for the 'My position' panel and to compute the user's expected payout. ```js const mine = await readContract("saturnpredict", "getUserUnderBet", [marketId, me]); ``` ##### `getUserHasClaimed(marketId: number, user: address): number` — READ Returns 1 if the user has already called claimWinnings or claimRefund for this market, 0 otherwise. Parameters: - `marketId` (`number`): The market to inspect. - `user` (`address`): The address to look up. Returns: `number` — 0 or 1. Notes: Gate the claim button in the UI: show 'Claimed' if 1, 'Claim winnings' / 'Claim refund' if 0. ```js const claimed = await readContract("saturnpredict", "getUserHasClaimed", [marketId, me]); ``` ### SaturnVaults — `saturnvaults` *Agent Strategy Vaults* — Saturn DEX v4 · Agent Automation Robo-advisor vaults for DeFi. An AI agent creates a vault denominated in a single base token and publishes a performance fee. Any user can deposit that base token and receive proportional shares of the vault's NAV. The agent — and only the agent — routes vault funds through the SwapEngine to execute its strategy; when the agent swaps back to base, the contract automatically marks-to-market, cheks the high-water mark, and pays the agent a performance fee only on NEW all-time-high NAV per share (no double-dipping). Depositors can withdraw their proportional slice at any time. Agent (or admin in an emergency) can close a vault; withdrawals still work after closing. #### Vault Lifecycle ##### `createVault(from: address, baseToken: string, perfFeePer10k: number, minDeposit: number)` — WRITE Agent creates a new vault. Locks in the base token, the performance fee taken from high-water-mark profits, and the minimum deposit size. Parameters: - `from` (`address`): Agent address — becomes the vault's exclusive manager. Must be a witness. - `baseToken` (`string`): Token the vault is denominated in. Deposits, withdrawals, and NAV are all measured in this token. Must be a validated symbol. - `perfFeePer10k` (`number`): Performance fee in basis points, taken from gains above the high-water mark. Must be between 100 (1%) and 3000 (30%). - `minDeposit` (`number`): Minimum raw deposit size per deposit() call. Must be > 0. Notes: A new vaultId is assigned in status 0 (active). HWM is initialized to 10000 (== 1.0000 NAV per share, scaled by 10000). totalVaultsCreated increments and the vaultId is pushed to allVaultIds. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnvaults", "createVault", [ from, "SOUL", 1500, // 15% performance fee 10_00000000 // min 10 SOUL deposit ]) .spendGas(from) .endScript(); ``` ##### `deposit(from: address, vaultId: number, amount: number)` — WRITE User deposits baseToken into an active vault and receives freshly minted shares proportional to the vault's current NAV. First deposit: 1 token = 10,000 shares (precision scaling). Subsequent deposits: amount * totalShares / totalDeposits. Parameters: - `from` (`address`): Depositor. Must be a witness and hold amount of baseToken. - `vaultId` (`number`): An active vault (status 0). - `amount` (`number`): Raw baseToken to deposit. Must be >= minDeposit. Notes: amount baseToken is pulled into the vault. newShares are minted and credited to your userShares entry; vaultTotalShares and vaultTotalDeposits both grow. Your userDepositAmount tracks your lifetime cost basis for display purposes (not used for withdrawal math). Reverts if the share calculation rounds to zero. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnvaults", "deposit", [from, vaultId, amountRaw]) .spendGas(from) .endScript(); ``` ##### `withdraw(from: address, vaultId: number, sharesToRedeem: number)` — WRITE User burns shares for a proportional slice of the vault's current baseToken balance. Works in any vault status — even closed vaults let users pull out. Parameters: - `from` (`address`): Must hold at least sharesToRedeem in userShares for this vault. - `vaultId` (`number`): Target vault. - `sharesToRedeem` (`number`): Number of shares to burn. Must be > 0 and <= your balance. Notes: payout = sharesToRedeem * totalDeposits / totalShares. That many baseToken are transferred to you, your userShares and vaultTotalShares decrement, vaultTotalDeposits decrement. Reverts if the payout calculation rounds to zero. ```js // Full exit: const myShares = await readContract("saturnvaults", "getUserShares", [vaultId, me]); const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnvaults", "withdraw", [from, vaultId, myShares]) .spendGas(from) .endScript(); ``` ##### `agentSwap(from: address, vaultId: number, poolId: number, amountIn: number, tokenIn: string, tokenOut: string, minAmountOut: number)` — WRITE Only the vault's agent can call this. Routes vault-held tokenIn through a specific pool on the SwapEngine with standard minAmountOut slippage protection. When the agent swaps back into the base token, the contract marks-to-market: vaultTotalDeposits is updated to reflect the realized delta, and if new NAV/share is strictly above the high-water mark, a performance fee is auto-sent to the agent and the high-water mark is raised to the post-fee NAV. Parameters: - `from` (`address`): Must be the vault agent. - `vaultId` (`number`): An active vault (status 0). - `poolId` (`number`): The pool to route through. - `amountIn` (`number`): Raw amount of tokenIn the vault holds to swap in. - `tokenIn` (`string`): Token the vault is selling. Vault must hold >= amountIn of this token. - `tokenOut` (`string`): Token the vault is buying. - `minAmountOut` (`number`): Standard slippage guard. Swap reverts if the pool can't honor this. Notes: amountIn tokenIn is transferred from the vault to the SwapEngine and swapped; the output tokenOut is received back by the vault. When tokenOut == baseToken the contract runs the mark-to-market + HWM + performance fee cycle. When tokenOut != baseToken the vault simply holds the new non-base token until a later agentSwap cycles it back. Note: with the vault holding non-base tokens, getVaultTotalDeposits does NOT reflect the full market value until the next conversion back to base. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnvaults", "agentSwap", [ from, vaultId, poolId, amountInRaw, "SOUL", // base, going out "KCAL", // target minOutRaw ]) .spendGas(from) .endScript(); ``` ##### `closeVault(from: address, vaultId: number)` — WRITE Agent marks the vault as closed. No new deposits accepted and no further agentSwap calls, but existing depositors can still withdraw their proportional slice. Parameters: - `from` (`address`): Must be the vault agent. - `vaultId` (`number`): An active vault. Notes: Status flips to 1 (closed). Users holding shares should call withdraw to exit. ```js const script = ScriptBuilder .begin() .allowGas(from, null, gasPrice, gasLimit) .callContract("saturnvaults", "closeVault", [from, vaultId]) .spendGas(from) .endScript(); ``` #### Vault Views ##### `getVaultInfo(vaultId: number): string` — READ One-shot dashboard snapshot. Returns an underscore-delimited string with the key fields. Parameters: - `vaultId` (`number`): The vault to inspect. Returns: `string` — base:_deposits:_shares:_hwm:_perfFee:_status:<0|1> Notes: Split on '_' and then on ':' to decode. Current NAV per share = deposits * 10000 / shares. Compare against hwm to know how close the agent is to earning fees. ```js const raw = await readContract("saturnvaults", "getVaultInfo", [vaultId]); const parts = Object.fromEntries(raw.split("_").map(kv => kv.split(":"))); ``` ##### `getVaultAgent(vaultId: number): address` — READ Returns the agent address authorized to call agentSwap and closeVault. Parameters: - `vaultId` (`number`): The vault to inspect. Returns: `address` — Managing agent. Notes: Use for permission gating and to render the 'managed by' badge. ```js const agent = await readContract("saturnvaults", "getVaultAgent", [vaultId]); ``` ##### `getVaultBaseToken(vaultId: number): string` — READ Returns the base token symbol the vault is denominated in. Parameters: - `vaultId` (`number`): The vault to inspect. Returns: `string` — Base token symbol. Notes: All deposit/withdraw/NAV calculations use this token. ```js const base = await readContract("saturnvaults", "getVaultBaseToken", [vaultId]); ``` ##### `getVaultTotalDeposits(vaultId: number): number` — READ Returns the vault's current base-token-denominated NAV as tracked by the contract (does not include the raw value of non-base tokens the vault may temporarily hold between agent swaps). Parameters: - `vaultId` (`number`): The vault to inspect. Returns: `number` — Raw NAV in base token. Notes: Divide by getVaultTotalShares to get the accounting NAV per share. Note: between the two legs of an agent swap, this lags reality — the definitive mark happens only when tokenOut == baseToken. ```js const nav = await readContract("saturnvaults", "getVaultTotalDeposits", [vaultId]); ``` ##### `getVaultTotalShares(vaultId: number): number` — READ Returns the total shares outstanding across all depositors. Parameters: - `vaultId` (`number`): The vault to inspect. Returns: `number` — Total share supply. Notes: Used as the denominator when computing a depositor's ownership percentage: mine / total. ```js const total = await readContract("saturnvaults", "getVaultTotalShares", [vaultId]); ``` ##### `getVaultHighWaterMark(vaultId: number): number` — READ Returns the vault's high-water mark as NAV per share * 10000. Initialized to 10000 (= 1.0000). Parameters: - `vaultId` (`number`): The vault to inspect. Returns: `number` — HWM * 10000. Notes: The agent earns performance fees only when current NAV per share strictly exceeds this value. Divide by 10000 to display as a decimal. ```js const hwm = await readContract("saturnvaults", "getVaultHighWaterMark", [vaultId]); const display = (hwm / 10000).toFixed(4); ``` ##### `getVaultPerfFeePer10k(vaultId: number): number` — READ Returns the performance fee charged against new high-water-mark profits, in basis points. Parameters: - `vaultId` (`number`): The vault to inspect. Returns: `number` — Performance fee, per 10,000. Notes: Show as a percent in the vault card: (fee / 100) + '%'. ```js const fee = await readContract("saturnvaults", "getVaultPerfFeePer10k", [vaultId]); ``` ##### `getVaultStatus(vaultId: number): number` — READ Returns the vault's lifecycle status. Parameters: - `vaultId` (`number`): The vault to inspect. Returns: `number` — 0=active, 1=closed. Notes: Gate the deposit button on status 0. Withdraw remains available in both statuses. ```js const status = await readContract("saturnvaults", "getVaultStatus", [vaultId]); ``` ##### `getNextVaultId(): number` — READ Returns the vaultId that will be assigned to the next createVault call. Parameters: none Returns: `number` — Next vault ID (starts at 1). Notes: Total vaults ever created = getNextVaultId() - 1 (matches getTotalVaultsCreated). ```js const next = await readContract("saturnvaults", "getNextVaultId", []); ``` ##### `getTotalVaultsCreated(): number` — READ Returns the cumulative number of vaults ever created. Parameters: none Returns: `number` — Cumulative vault count. Notes: Use for protocol growth dashboards. ```js const n = await readContract("saturnvaults", "getTotalVaultsCreated", []); ``` ##### `getUserShares(vaultId: number, user: address): number` — READ Returns a specific user's share balance in a vault. Parameters: - `vaultId` (`number`): The vault to inspect. - `user` (`address`): The depositor address to look up. Returns: `number` — Raw shares held. Notes: Pass to withdraw() for a full exit. Compute user NAV = shares * totalDeposits / totalShares. ```js const mine = await readContract("saturnvaults", "getUserShares", [vaultId, me]); ``` ##### `getUserDepositAmount(vaultId: number, user: address): number` — READ Returns the user's lifetime gross deposit amount into a vault. This is a display-only cost basis — it is NOT decremented on withdraw. Parameters: - `vaultId` (`number`): The vault to inspect. - `user` (`address`): The depositor address to look up. Returns: `number` — Raw cumulative deposit amount. Notes: Use for the 'total invested' field. To compute realized PnL you'll need to track withdrawals off-chain separately. ```js const invested = await readContract("saturnvaults", "getUserDepositAmount", [vaultId, me]); ``` ##### `getAllVaultIds(): number*` — READ Generator yielding every vaultId ever created. Parameters: none Returns: `number*` — Iterable of vault IDs. Notes: Combine with getVaultStatus() to partition the explorer into active and closed tabs. ```js const script = ScriptBuilder .begin() .callContract("saturnvaults", "getAllVaultIds", []) .endScript(); ``` ### SaturnStakeArb — `saturnstakearb` *Permissionless Stake-Arbitrage* — Saturn DEX v4 · Agent Automation Permissionless arbitrage engine that lets any bot atomically borrow idle capital from saturnholders' staked pool, round-trip it across two Saturn pools (token → riskToken → token), return 100% of the borrowed principal, and split the net profit 50/50 between the executing bot and all stakers of that token. Stakers earn arbitrage yield on top of their normal swap-fee share from the exact same deposit, with zero additional capital required. If the round-trip is unprofitable the entire transaction reverts — the stake is never decreased. This contract is designed for MEV bots and AI agents. #### Arbitrage Execution ##### `executeArb(from: address, tokenSymbol: string, amountIn: number, riskToken: string, poolBuy: number, poolSell: number): number` — WRITE Executes an atomic stake-arbitrage round-trip. Borrows amountIn of tokenSymbol from saturnholders (via flashLendStake), executes leg 1 (tokenSymbol → riskToken via poolBuy) and leg 2 (riskToken → tokenSymbol via poolSell), requires back > amountIn, then returns the principal to saturnholders, banks 50% of the gross profit at saturnliquidity as the holders' share (accrued into the same MasterChef accumulator stakers already use), and pays the remaining 50% to from. The trade reverts atomically if it is unprofitable — the staked principal is mathematically guaranteed to be intact before the transaction commits, because settleArbLoan re-asserts saturnholders' balance >= totalStaked. Parameters: - `from` (`address`): Executor's address. Must be the transaction witness and receives 50% of gross profit. - `tokenSymbol` (`string`): Symbol of the token to borrow and denominate profit in; must be staked in saturnholders. - `amountIn` (`number`): Raw-unit borrow amount. Must be > 0 and ≤ totalStaked for tokenSymbol. - `riskToken` (`string`): Intermediate token for the round-trip. Must differ from tokenSymbol and appear in both poolBuy and poolSell. - `poolBuy` (`number`): Pool ID for leg 1 (tokenSymbol → riskToken). Must be active and contain both tokenSymbol and riskToken. - `poolSell` (`number`): Pool ID for leg 2 (riskToken → tokenSymbol). Must be active and contain both riskToken and tokenSymbol. Must differ from poolBuy. Returns: `number` — The holders' share (50% of gross profit) in raw units of tokenSymbol banked at saturnliquidity. Notes: Reverts if from is not the witness, amountIn == 0, poolBuy == poolSell, riskToken == tokenSymbol, either pool is inactive, token pairs do not match, amountIn exceeds totalStaked, or back ≤ amountIn (no profit). On revert the staked principal is untouched and only gas is spent. ```js const from = "S3...botAddress"; const tx = sb.begin() .allowGas(from) .callContract("saturnstakearb", "executeArb", [from, "SOUL", amountIn, "KCAL", poolBuy, poolSell]) .spendGas(from) .endScript(); // returns holderShare — 50% of (back - amountIn) in raw SOUL units ``` #### Statistics Views ##### `getTotalArbs(): number` — READ Returns the cumulative count of successful stake-arbitrage executions since deployment. Parameters: none Returns: `number` — Total successful executeArb calls. ```js const total = await readContract("saturnstakearb", "getTotalArbs", []); ``` ##### `getTotalHolderProfit(tokenSymbol: string): number` — READ Returns the lifetime raw-unit amount of tokenSymbol banked at saturnliquidity as holders' profit share from all successful executeArb calls. Use this to display total arb yield earned by stakers of a given token. Parameters: - `tokenSymbol` (`string`): Token symbol to query. Returns: `number` — Lifetime holder profit in raw units of tokenSymbol. ```js const holderProfit = await readContract("saturnstakearb", "getTotalHolderProfit", ["SOUL"]); ``` ##### `getExecutorArbCount(executor: address): number` — READ Returns the number of successful executeArb calls made by a specific executor address. Use this on leaderboards or for per-bot performance tracking. Parameters: - `executor` (`address`): The executor address to query. Returns: `number` — Number of successful executeArb calls made by this executor. ```js const count = await readContract("saturnstakearb", "getExecutorArbCount", [botAddr]); ``` ##### `getContractVersion(): string` — READ Returns the deployed version string for this contract. Parameters: none Returns: `string` — Version identifier, e.g. "saturnstakearb-4.4.0". ```js const ver = await readContract("saturnstakearb", "getContractVersion", []); ``` ## Lending Protocol A full lending protocol on Saturn — credit scores, LP-backed collateral, the P2P loan market, dual-DEX pricing, and TAZ rewards. ### SaturnLendCfg — `saturnlendcfg` *Lending Protocol Configuration* — Saturn DEX v4 · Lending Protocol The single source-of-truth for every tunable parameter in the Saturn Lending protocol, which is built on top of Saturn DEX v3 and v4. All other lending contracts (saturncredit, saturnvault, saturnloans, saturnauto, saturnmarket) read their limits, thresholds, and fee rates directly from this contract — making it the first place to query before rendering any loan form or dashboard. Collateral and loan token prices are always denominated against RA-paired pools; pools that do not include RA are never read by the lending system. #### Ownership & Access ##### `getAdmin(): address` — READ Returns the current protocol admin address for the lending layer. Use this to verify upgrade authority or to display governance ownership in your UI. Parameters: none Returns: `address` — Current lending config admin address. Notes: Always a valid non-null address. Never reverts. ```js const admin = await readContract("saturnlendcfg", "getAdmin", []); ``` #### Loan Term & Rate Limits ##### `getMinLoanDuration(): number` — READ Minimum loan duration in seconds. Any P2P quote or auto-loan request with a shorter duration will be rejected. Default is 604,800 (7 days). Parameters: none Returns: `number` — Minimum loan duration in seconds (default: 604800 = 7 days). Notes: Always ≥ 86400 (1 day). Always < getMaxLoanDuration(). ```js const minDur = await readContract("saturnlendcfg", "getMinLoanDuration", []); // 604800 → 7 days ``` ##### `getMaxLoanDuration(): number` — READ Maximum loan duration in seconds. Default is 31,536,000 (365 days). Show this as the upper bound on your loan-term slider. Parameters: none Returns: `number` — Maximum loan duration in seconds (default: 31536000 = 365 days). Notes: Always ≤ 63,072,000 (2 years). Always > getMinLoanDuration(). ```js const maxDur = await readContract("saturnlendcfg", "getMaxLoanDuration", []); // 31536000 → 365 days ``` ##### `getGracePeriod(): number` — READ Extra time in seconds granted to a borrower after a missed installment before the loan enters default. Default is 259,200 (3 days). Display this so borrowers know their window to cure a late payment. Parameters: none Returns: `number` — Grace period in seconds (default: 259200 = 3 days). Notes: Always in range 0–604800 (7 days). ```js const grace = await readContract("saturnlendcfg", "getGracePeriod", []); // 259200 → 3 days ``` ##### `getInstallmentInterval(): number` — READ Interval in seconds between installment payment due-dates. Default is 2,592,000 (30 days). Used by getInstallmentCount() to compute how many payments a loan will have. Parameters: none Returns: `number` — Installment interval in seconds (default: 2592000 = 30 days). Notes: Positive. Divides loan duration to determine payment count. ```js const interval = await readContract("saturnlendcfg", "getInstallmentInterval", []); ``` ##### `getAutoBaseDuration(): number` — READ Base loan duration (seconds) used in the auto-lending algorithm before credit-score extensions are added. Default is 2,592,000 (30 days). Parameters: none Returns: `number` — Auto-lending base duration in seconds (default: 2592000 = 30 days). Notes: Positive. Used only by the auto-lending path (disabled in v1.0). ```js const base = await readContract("saturnlendcfg", "getAutoBaseDuration", []); ``` ##### `getAutoMaxExtension(): number` — READ Maximum additional seconds a perfect credit score (1000) can add to the auto-lending base duration. Default is 12,960,000 (150 days). The full formula is autoBaseDuration + (creditScore * autoMaxExtension / maxScore). Parameters: none Returns: `number` — Maximum duration extension in seconds for auto-lending (default: 12960000). Notes: Used only by the auto-lending path (disabled in v1.0). ```js const ext = await readContract("saturnlendcfg", "getAutoMaxExtension", []); ``` ##### `getBaseInterestRate(): number` — READ The starting annual interest rate in basis points (per 10,000) before any credit-score discount is applied. Default is 2,000 (20% APR). A borrower with zero credit score pays this rate (clamped to maxInterestRate). Parameters: none Returns: `number` — Base annual interest rate in bps/10000 (default: 2000 = 20%). Notes: Always ≥ 100. Always ≤ 5000. ```js const baseRate = await readContract("saturnlendcfg", "getBaseInterestRate", []); // 2000 → 20% APR ``` ##### `getCreditRateDiscount(): number` — READ Per-unit discount applied per credit score point. The effective rate is: baseInterestRate − (creditScore × creditRateDiscount / 1000), clamped to [minInterestRate, maxInterestRate]. Default is 15. Parameters: none Returns: `number` — Rate discount factor (default: 15; applied as discount = creditScore * 15 / 1000). Notes: Combined with getInterestRateForScore() to compute the borrower-specific rate. ```js const discount = await readContract("saturnlendcfg", "getCreditRateDiscount", []); ``` ##### `getMinInterestRate(): number` — READ Floor for the computed annual interest rate regardless of how high a borrower's credit score is. Default is 300 (3% APR). Show this as the best-case rate achievable. Parameters: none Returns: `number` — Minimum annual interest rate in bps/10000 (default: 300 = 3%). Notes: Always ≥ 100 and < getMaxInterestRate(). ```js const minRate = await readContract("saturnlendcfg", "getMinInterestRate", []); // 300 → 3% APR ``` ##### `getMaxInterestRate(): number` — READ Ceiling for the computed annual interest rate. Default is 3,000 (30% APR). Show this as the worst-case rate a borrower can be assigned. Parameters: none Returns: `number` — Maximum annual interest rate in bps/10000 (default: 3000 = 30%). Notes: Always ≤ 5000 and > getMinInterestRate(). ```js const maxRate = await readContract("saturnlendcfg", "getMaxInterestRate", []); // 3000 → 30% APR ``` ##### `getSecondsPerYear(): number` — READ Returns 31,536,000 — the constant used by calculateInterest() to annualise the rate. Use when reproducing the interest formula client-side. Parameters: none Returns: `number` — Seconds per year constant (31536000). Notes: Always 31536000. Never changes. ```js const spy = await readContract("saturnlendcfg", "getSecondsPerYear", []); ``` ##### `getSecondsPerDay(): number` — READ Returns 86,400 — the constant used by the credit score time-bonus accrual. Use when computing daily time-bonus increments client-side. Parameters: none Returns: `number` — Seconds per day constant (86400). Notes: Always 86400. Never changes. ```js const spd = await readContract("saturnlendcfg", "getSecondsPerDay", []); ``` #### Collateral & LTV ##### `getLtvTier1(): number` — READ Maximum LTV (in bps per 10,000) for borrowers with a credit score of 0–199. Default is 2,500 (25%). This is the most restrictive tier. Parameters: none Returns: `number` — LTV for score 0–199 in bps/10000 (default: 2500 = 25%). Notes: Always ≥ 1000 and < getLtvTier2(). ```js const ltv1 = await readContract("saturnlendcfg", "getLtvTier1", []); // 2500 → 25% LTV ``` ##### `getLtvTier2(): number` — READ Maximum LTV for credit score 200–399. Default is 3,500 (35%). Parameters: none Returns: `number` — LTV for score 200–399 in bps/10000 (default: 3500 = 35%). Notes: Always > getLtvTier1() and < getLtvTier3(). ```js const ltv2 = await readContract("saturnlendcfg", "getLtvTier2", []); ``` ##### `getLtvTier3(): number` — READ Maximum LTV for credit score 400–599. Default is 5,000 (50%). Parameters: none Returns: `number` — LTV for score 400–599 in bps/10000 (default: 5000 = 50%). Notes: Always > getLtvTier2() and < getLtvTier4(). ```js const ltv3 = await readContract("saturnlendcfg", "getLtvTier3", []); ``` ##### `getLtvTier4(): number` — READ Maximum LTV for credit score 600–799. Default is 6,500 (65%). Parameters: none Returns: `number` — LTV for score 600–799 in bps/10000 (default: 6500 = 65%). Notes: Always > getLtvTier3() and < getLtvTier5(). ```js const ltv4 = await readContract("saturnlendcfg", "getLtvTier4", []); ``` ##### `getLtvTier5(): number` — READ Maximum LTV for credit score 800–1000. Default is 8,000 (80%). This is the best LTV tier, reserved for borrowers with an established repayment history. Parameters: none Returns: `number` — LTV for score 800–1000 in bps/10000 (default: 8000 = 80%). Notes: Always ≤ 9000 and > getLtvTier4(). ```js const ltv5 = await readContract("saturnlendcfg", "getLtvTier5", []); // 8000 → 80% LTV ``` ##### `getMinCollateralValue(): number` — READ Minimum RA-denominated value (in the protocol's internal scaled units) that collateral must be worth before a loan can be created. Default is 100,000,000. Display this as the minimum collateral requirement on your loan form. Parameters: none Returns: `number` — Minimum collateral value in RA-anchor scaled units (default: 100000000). Notes: Positive. Enforced at loan-creation time by saturnloans. ```js const minColl = await readContract("saturnlendcfg", "getMinCollateralValue", []); ``` ##### `getLiquidationThreshold(): number` — READ The LTV ratio (bps per 10,000) above which collateral becomes eligible for liquidation. When the current loan-to-value exceeds this threshold the position can be liquidated. Default is 9,000 (90%). Parameters: none Returns: `number` — Liquidation LTV threshold in bps/10000 (default: 9000 = 90%). Notes: Always in range (5000, 9800]. Always > any ltvTier value. ```js const liqThresh = await readContract("saturnlendcfg", "getLiquidationThreshold", []); // 9000 → collateral liquidatable when position reaches 90% LTV ``` ##### `getMaxLoansPerUser(): number` — READ Maximum number of active loans a single borrower address may hold simultaneously. Default is 5. Check this before showing a borrow button to a user who may already be at capacity. Parameters: none Returns: `number` — Maximum concurrent active loans per address (default: 5). Notes: Always in range [1, 20]. ```js const maxLoans = await readContract("saturnlendcfg", "getMaxLoansPerUser", []); ``` #### Credit Score Params ##### `getBaseScore(): number` — READ The credit score assigned to a brand-new borrower with no history. Default is 200. New users start in Tier 2 LTV. Display this when onboarding first-time borrowers. Parameters: none Returns: `number` — Starting credit score for new borrowers (default: 200). Notes: Always ≥ 0 and ≤ getMaxScore(). ```js const base = await readContract("saturnlendcfg", "getBaseScore", []); // 200 → qualifies for Tier 2 LTV (35%) ``` ##### `getMaxScore(): number` — READ The maximum achievable credit score. Default is 1,000. Use this as the upper bound when rendering a score progress bar. Parameters: none Returns: `number` — Maximum credit score (default: 1000). Notes: Always in range [100, 10000]. Default: 1000. ```js const max = await readContract("saturnlendcfg", "getMaxScore", []); ``` ##### `getTimeBonusPerDay(): number` — READ Credit score points added per day of account age. Default is 1 point/day. A wallet that has existed for 100 days earns up to 100 points from this alone (capped by getTimeBonusCap()). Parameters: none Returns: `number` — Score points accrued per day of wallet age (default: 1). Notes: Positive. Accumulation is capped by getTimeBonusCap(). ```js const tpd = await readContract("saturnlendcfg", "getTimeBonusPerDay", []); ``` ##### `getTimeBonusCap(): number` — READ Maximum credit score points that wallet-age time bonuses can contribute. Default is 150. Even a very old wallet cannot earn more than 150 points from age alone. Parameters: none Returns: `number` — Maximum time-bonus contribution (default: 150). Notes: Always ≥ 0. ```js const tcap = await readContract("saturnlendcfg", "getTimeBonusCap", []); ``` ##### `getOnTimeRepayBonus(): number` — READ Credit score points awarded per on-time installment or full repayment. Default is 50. Display this in the incentive copy next to the repayment button. Parameters: none Returns: `number` — Score points awarded per on-time repayment (default: 50). Notes: Positive. Accumulation is capped by getOnTimeRepayCap(). ```js const otrb = await readContract("saturnlendcfg", "getOnTimeRepayBonus", []); ``` ##### `getOnTimeRepayCap(): number` — READ Maximum credit score points that on-time repayments can contribute in total. Default is 350. Parameters: none Returns: `number` — Maximum on-time repayment bonus contribution (default: 350). Notes: Always ≥ 0. ```js const otrcap = await readContract("saturnlendcfg", "getOnTimeRepayCap", []); ``` ##### `getLateRepayPenalty(): number` — READ Credit score points deducted when a payment is made during the grace period (late, but not defaulted). Default is 30. Parameters: none Returns: `number` — Score penalty for a late (but not defaulted) payment (default: 30). Notes: Non-negative. ```js const lrp = await readContract("saturnlendcfg", "getLateRepayPenalty", []); ``` ##### `getDefaultPenalty(): number` — READ Credit score points deducted when a loan defaults (grace period exhausted without payment). Default is 150. Show this prominently on loan health dashboards. Parameters: none Returns: `number` — Score penalty for a loan default (default: 150). Notes: Non-negative. Larger than getLateRepayPenalty(). ```js const dp = await readContract("saturnlendcfg", "getDefaultPenalty", []); ``` ##### `getStreakBonus(): number` — READ Bonus credit score points added per consecutive on-time repayment in a streak. Default is 10. Parameters: none Returns: `number` — Score bonus per streak increment (default: 10). Notes: Non-negative. Accumulation is capped by getStreakBonusCap(). ```js const sb = await readContract("saturnlendcfg", "getStreakBonus", []); ``` ##### `getStreakBonusCap(): number` — READ Maximum credit score contribution from repayment streaks. Default is 100. Parameters: none Returns: `number` — Maximum streak bonus contribution (default: 100). Notes: Always ≥ 0. ```js const scap = await readContract("saturnlendcfg", "getStreakBonusCap", []); ``` ##### `getPartialRepayBonus(): number` — READ Credit score points awarded for making a partial early repayment before an installment due-date. Default is 15. Use this to encourage pro-active debt reduction in your UX. Parameters: none Returns: `number` — Score bonus for a partial early repayment (default: 15). Notes: Non-negative. ```js const prb = await readContract("saturnlendcfg", "getPartialRepayBonus", []); ``` #### Fees & Computed Values ##### `getOriginationFeeBps(): number` — READ Origination fee in basis points (per 10,000) charged on the loan principal at creation. Default is 100 (1%). Use getOriginationFee() for the actual computed amount. Parameters: none Returns: `number` — Origination fee rate in bps/10000 (default: 100 = 1%). Notes: Always ≤ 500 (5%). ```js const origFee = await readContract("saturnlendcfg", "getOriginationFeeBps", []); // 100 → 1% of principal ``` ##### `getLiquidationPenaltyBps(): number` — READ Liquidation penalty in basis points (per 10,000) applied to outstanding debt when a position is liquidated. Default is 500 (5%). Use getLiquidationPenalty() for the computed amount. Parameters: none Returns: `number` — Liquidation penalty rate in bps/10000 (default: 500 = 5%). Notes: Always ≤ 2000 (20%). ```js const liqPen = await readContract("saturnlendcfg", "getLiquidationPenaltyBps", []); ``` ##### `getProtocolFeeShare(): number` — READ The share of interest/fee revenue (in bps per 10,000) routed to the protocol treasury rather than the lender. Default is 1,000 (10%). Useful for computing a lender's net yield. Parameters: none Returns: `number` — Protocol's share of fee revenue in bps/10000 (default: 1000 = 10%). Notes: Always ≤ 3000 (30%). ```js const protoShare = await readContract("saturnlendcfg", "getProtocolFeeShare", []); ``` ##### `getMaxLtvForScore(creditScore: number): number` — READ Returns the maximum LTV ratio (bps per 10,000) for a given credit score by mapping it to the correct tier. This is the primary helper used by saturnloans and saturnmarket; call it before presenting a borrow amount cap to the user. Parameters: - `creditScore` (`number`): Borrower's current credit score (0–1000). Returns: `number` — Max LTV in bps/10000 (e.g. 5000 = 50%) for the given score. Notes: Score 0–199 → tier1; 200–399 → tier2; 400–599 → tier3; 600–799 → tier4; 800–1000 → tier5. Never reverts. ```js const ltv = await readContract("saturnlendcfg", "getMaxLtvForScore", [400]); // 5000 → 50% LTV cap for a score of 400 ``` ##### `getInterestRateForScore(creditScore: number): number` — READ Computes the annual interest rate (bps per 10,000) for a given credit score using the formula: baseInterestRate − (creditScore × creditRateDiscount / 1000), clamped to [minInterestRate, maxInterestRate]. Use this to preview the APR on the loan form before the borrower submits. Parameters: - `creditScore` (`number`): Borrower's current credit score (0–1000). Returns: `number` — Annual interest rate in bps/10000 (e.g. 1200 = 12% APR). Notes: Result always in [getMinInterestRate(), getMaxInterestRate()]. Never reverts. ```js const rate = await readContract("saturnlendcfg", "getInterestRateForScore", [600]); // e.g. 1100 → 11% APR for a score of 600 ``` ##### `getAutoDurationForScore(creditScore: number): number` — READ Computes the auto-lending loan duration (seconds) for a given credit score: autoBaseDuration + (creditScore × autoMaxExtension / maxScore), clamped to [minLoanDuration, maxLoanDuration]. Used by the auto-lending algorithm (disabled in v1.0). Parameters: - `creditScore` (`number`): Borrower's credit score (0–1000). Returns: `number` — Loan duration in seconds for the auto-lending path. Notes: Always in [getMinLoanDuration(), getMaxLoanDuration()]. Never reverts. ```js const dur = await readContract("saturnlendcfg", "getAutoDurationForScore", [500]); ``` ##### `calculateInterest(principal: number, ratePer10k: number, durationSeconds: number): number` — READ Computes total simple interest owed on a loan: (principal × ratePer10k / 10000) × durationSeconds / secondsPerYear. Use this to show the total cost of a loan before the borrower confirms. Parameters: - `principal` (`number`): Loan principal in the token's raw units. - `ratePer10k` (`number`): Annual interest rate in bps per 10,000 (e.g. 1500 = 15%). - `durationSeconds` (`number`): Loan duration in seconds. Returns: `number` — Total interest amount in the same raw units as principal. Notes: Truncated integer arithmetic. For small principals or short durations the result may be 0. Never reverts. ```js // 1 000 000 units, 20% APR, 30 days const interest = await readContract("saturnlendcfg", "calculateInterest", [ 1000000, 2000, 2592000 ]); ``` ##### `getInstallmentCount(durationSeconds: number): number` — READ Returns the number of installments for a loan of a given duration by dividing by installmentInterval, rounding up. Minimum 1. Use this to compute the payment schedule grid. Parameters: - `durationSeconds` (`number`): Loan duration in seconds. Returns: `number` — Number of installment payments (always ≥ 1). Notes: Always ≥ 1. A 30-day loan = 1 installment; a 60-day loan = 2 installments. ```js const count = await readContract("saturnlendcfg", "getInstallmentCount", [5184000]); // 2 installments for a 60-day loan ``` ##### `getInstallmentAmount(totalOwed: number, installmentCount: number): number` — READ Computes the per-installment payment amount from total owed divided by count, rounded up to prevent underpayment from truncation. Pair with calculateInterest() and getInstallmentCount() to build a full repayment schedule. Parameters: - `totalOwed` (`number`): Total amount owed (principal + interest) in raw units. - `installmentCount` (`number`): Number of installments (from getInstallmentCount()). Returns: `number` — Per-installment payment amount, rounded up. Notes: installmentCount must be ≥ 1. Always returns a value that covers at least totalOwed / installmentCount. ```js const perPayment = await readContract("saturnlendcfg", "getInstallmentAmount", [ 1050000, // total owed 3 // 3 installments ]); // 350000 per installment ``` ##### `getOriginationFee(principal: number): number` — READ Computes the origination fee amount for a given principal: principal × originationFeeBps / 10000. Display this as an upfront cost on the borrow confirmation screen. Parameters: - `principal` (`number`): Loan principal in raw token units. Returns: `number` — Origination fee in raw token units. Notes: Result is ≥ 0. Truncated integer arithmetic. ```js const fee = await readContract("saturnlendcfg", "getOriginationFee", [5000000]); // 50000 → 1% origination on a 5 000 000 unit loan ``` ##### `getLiquidationPenalty(outstandingDebt: number): number` — READ Computes the liquidation penalty amount for a given outstanding debt: outstandingDebt × liquidationPenaltyBps / 10000. Use this to show liquidators their expected profit and borrowers the cost of liquidation. Parameters: - `outstandingDebt` (`number`): Outstanding debt amount in raw token units. Returns: `number` — Liquidation penalty amount in raw token units. Notes: Result ≥ 0. Truncated integer arithmetic. ```js const pen = await readContract("saturnlendcfg", "getLiquidationPenalty", [2000000]); ``` #### Reentrancy Guard (Read) ##### `getGuardLocked(user: address): number` — READ Returns 1 if the lending reentrancy guard is currently locked for the given user address, 0 otherwise. Use for debugging hung transactions; under normal conditions this should always return 0 between transactions. Parameters: - `user` (`address`): User address to check. Returns: `number` — 1 if guard is active (locked), 0 if free. Notes: Should always be 0 outside an active lending transaction. A non-zero value indicates an incomplete or stuck transaction. ```js const locked = await readContract("saturnlendcfg", "getGuardLocked", [userAddress]); if (locked) console.warn("Guard locked — pending tx in flight"); ``` ##### `getGuardOwner(user: address): string` — READ Returns the name of the lending contract that currently holds the reentrancy lock for a given user (e.g. "saturnloans", "saturnmarket"). Returns an empty string when the guard is free. Parameters: - `user` (`address`): User address to check. Returns: `string` — Contract name that owns the lock, or empty string if unlocked. Notes: Non-empty only when getGuardLocked() returns 1. ```js const owner = await readContract("saturnlendcfg", "getGuardOwner", [userAddress]); // "" → unlocked; "saturnloans" → loan creation in progress ``` #### Deprecated / Removed ##### `getSOULfeeLoan(): number` — READ Always returns 0. The SOUL loan fee was removed in v4-04x; lending entrypoints no longer charge SOUL. The storage key is retained at zero for ABI compatibility with older integrations. Do not use this value for any pricing calculation. Parameters: none Returns: `number` — Always 0. Notes: Always 0. Key is frozen. ```js const fee = await readContract("saturnlendcfg", "getSOULfeeLoan", []); // Always 0 ``` ##### `getStorageFee(): number` — READ Always returns 0. Gen3 storage is funded by the transaction's SOUL data escrow (maxData on the payer), not by any staking or SOUL charge through this contract. Signature retained for ABI/upgrade compatibility only. Parameters: none Returns: `number` — Always 0. Notes: Always 0. Funding model changed in v4-03x. ```js const sf = await readContract("saturnlendcfg", "getStorageFee", []); // Always 0 ``` ### SaturnCredit — `saturncredit` *Borrower Credit Score (0–1000)* — Saturn DEX v4 · Lending Protocol Tracks every borrower's on-chain credit profile — a composite score from 0 to 1000 built from time in the system, on-time repayments, late payments, defaults, streak bonuses, and partial-repayment incentives. Scores are cached on every loan event and can be recomputed on demand. Integrate this contract to gate loan eligibility, display credit tiers in your UI, or build credit-aware analytics dashboards — no off-chain oracle required. #### User Registration ##### `registerUser(from: address): void` — WRITE Registers a wallet as a borrower, locking in the credit-history start time. The score clock starts here: time-based bonuses accrue from the registration timestamp. Call this before a user's first loan application so their time-in-system points build up. Reverts if the user is already registered. The protocol also auto-registers users on first loan attempt via an internal path, but explicit registration lets users build tenure earlier. Parameters: - `from` (`address`): The borrower's wallet address. Must be the transaction signer. Notes: Reverts if `from` is already registered. Reverts if the reentrancy guard (saturnlendcfg.acquireGuard) fires. Initializes all counters to 0 and sets cachedScore to the protocol base score. ```js const tx = sb.begin() .allowGas(from) .callContract("saturncredit", "registerUser", [from]) .spendGas(from) .endScript(); ``` #### Score Computation ##### `computeScore(user: address): number` — WRITE Recomputes and persists the user's credit score on-chain, then returns it. The formula: baseScore + timeBonusCap(daysInSystem × timeBonusPerDay) + onTimeRepayCap(onTimeRepays × onTimeRepayBonus) + streakBonusCap(bestStreak × streakBonus) + min(partialRepays × partialRepayBonus, 50) − (lateRepays × lateRepayPenalty) − (defaults × defaultPenalty), clamped to [0, maxScore]. Call this after significant credit events to force a fresh calculation, or call it as a write-then-read to surface the latest score in your dApp. The result is also stored in userCachedScore and timestamped. Parameters: - `user` (`address`): The borrower whose score to recompute. Returns: `number` — Updated credit score in the range [0, 1000]. Notes: Reverts if the user is not registered. Score is always clamped to [0, maxScore] (typically 1000). Each component is individually capped before summing. ```js // Force a score refresh, then read back the cached value const tx = sb.begin() .allowGas(from) .callContract("saturncredit", "computeScore", [userAddr]) .spendGas(from) .endScript(); // After confirmation, read the fresh score: const score = await readContract("saturncredit", "getUserCachedScore", [userAddr]); ``` #### Credit Score & Status Reads ##### `getUserCachedScore(user: address): number` — READ Returns the most recently persisted credit score for this user. Updated on every loan lifecycle event (origination, repayment, default) and by explicit computeScore calls. Use this for low-latency reads — it costs no gas and reflects the last event. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Cached score in [0, 1000]. Returns 0 if the user is unregistered. Notes: Returns 0 for unregistered users (no storage entry exists). Never exceeds maxScore. ```js const score = await readContract("saturncredit", "getUserCachedScore", [userAddr]); // e.g. 720 ``` ##### `getUserScoreTimestamp(user: address): number` — READ Returns the Unix timestamp (seconds) when the cached score was last written. Use this alongside getUserCachedScore to tell users how fresh the displayed score is, and to decide whether to call computeScore for an up-to-date value. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Unix timestamp of the last score update. Notes: Returns 0 for unregistered users. ##### `getUserRegistered(user: address): number` — READ Returns 1 if the address is registered in the credit system, 0 otherwise. Gate any credit-dependent UI on this check before displaying a score. Parameters: - `user` (`address`): The address to check. Returns: `number` — 1 = registered, 0 = not registered. ```js const isReg = await readContract("saturncredit", "getUserRegistered", [userAddr]); if (isReg === 1) { /* show score */ } ``` ##### `getUserRegisteredAt(user: address): number` — READ Returns the Unix timestamp when the user first registered. The time-bonus component of the credit score accrues from this date, so earlier registration means higher potential score. Parameters: - `user` (`address`): The registered borrower. Returns: `number` — Unix timestamp of registration. #### Repayment History Reads ##### `getUserOnTimeRepays(user: address): number` — READ Count of installments or full repayments made on time. Each contributes onTimeRepayBonus points to the credit score, capped at onTimeRepayCap. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Cumulative on-time repayment count. ##### `getUserLateRepays(user: address): number` — READ Count of late payments received. Each deducts lateRepayPenalty points from the credit score (uncapped penalty). Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Cumulative late repayment count. ##### `getUserDefaults(user: address): number` — READ Count of loan defaults. Defaults carry the heaviest penalty in the scoring formula and permanently reset the consecutive-repayment streak to zero. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Cumulative default count. ##### `getUserPartialRepays(user: address): number` — READ Count of early or partial repayment events. Each adds partialRepayBonus points, capped at 50 total across all partials. Encourages borrowers to pay ahead of schedule. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Cumulative early/partial repayment count. ##### `getUserTotalRepaid(user: address): number` — READ Total principal repaid by this user across all loans, in scaled (8-decimal) units. Useful for displaying lifetime repayment volume on a borrower dashboard. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Sum of all repaid principal in 8-decimal scaled units. ##### `getUserLastActivity(user: address): number` — READ Unix timestamp of the most recent loan lifecycle event (loan created, repayment, default). Use this to show how recently a borrower was active. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Unix timestamp of last event. #### Streak & Loan Count Reads ##### `getUserConsecutiveOnTime(user: address): number` — READ The current unbroken run of on-time repayments. Resets to 0 on a late payment or default. Useful for showing a user their active streak as a retention / gamification signal. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Active consecutive on-time repayment count. ##### `getUserBestStreak(user: address): number` — READ The all-time longest consecutive on-time repayment streak for this user. This is the streak value that feeds the credit score formula (not the current streak), so it is never reset by a missed payment. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Best-ever on-time repayment streak. ```js const best = await readContract("saturncredit", "getUserBestStreak", [userAddr]); const current = await readContract("saturncredit", "getUserConsecutiveOnTime", [userAddr]); ``` ##### `getUserTotalLoans(user: address): number` — READ Lifetime count of loans ever originated by this borrower, regardless of outcome. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Total loans originated. ##### `getUserActiveLoans(user: address): number` — READ Count of currently open (unpaid) loans. Increments on loan creation, decrements on full repayment or close. Lending contracts may gate new loan requests while this exceeds a threshold. Parameters: - `user` (`address`): The borrower's address. Returns: `number` — Number of currently active loans. #### Protocol-Level Stats ##### `getTotalRegisteredUsers(): number` — READ Total number of distinct addresses that have ever registered in the credit system. Useful for protocol analytics and growth dashboards. Parameters: none Returns: `number` — Cumulative registered-user count. ```js const total = await readContract("saturncredit", "getTotalRegisteredUsers", []); ``` ##### `getTotalLoansIssued(): number` — READ Cumulative count of all loans ever created across the protocol. Incremented by markLoanCreated on every loan origination. Parameters: none Returns: `number` — Total loans ever issued. ##### `getTotalDefaults(): number` — READ Cumulative default count across all borrowers. Track this alongside getTotalLoansIssued to compute the protocol-wide default rate. Parameters: none Returns: `number` — Cumulative default events. #### Credit Report ##### `getCreditReport(user: address): string` — READ Returns a packed summary string of the user's full credit profile in a single call — score, repayment counts, streak, and loan totals. Format: `score:N_onTime:N_late:N_defaults:N_bestStreak:N_totalLoans:N_active:N`. Ideal for displaying a compact credit card in lending UIs without making seven individual read calls. Parameters: - `user` (`address`): The registered borrower's address. Returns: `string` — Packed credit profile string, e.g. "score:720_onTime:14_late:1_defaults:0_bestStreak:12_totalLoans:15_active:1". Notes: Reverts with 'User not registered' if the address has not registered. Score shown is the cached value — call computeScore first to force a refresh. ```js const report = await readContract("saturncredit", "getCreditReport", [userAddr]); // "score:720_onTime:14_late:1_defaults:0_bestStreak:12_totalLoans:15_active:1" const parts = Object.fromEntries(report.split("_").map(p => p.split(":"))); console.log(`Score: ${parts.score}, Defaults: ${parts.defaults}`); ``` ### SaturnVault — `saturnvault` *Collateral Vault (LP-backed)* — Saturn DEX v4 · Lending Protocol Custodian for all loan collateral on the Saturn lending protocol. Accepts two active collateral types: v4 DEX LP pools (locked in-place via FinancialLock so swaps continue accruing fees while the loan is live) and v3 SATRN LP NFTs (held in vault custody). Every collateral position is assigned a numeric ID and priced on demand in any RA-paired token via the saturndexadapt routing layer. Integrators read this contract to render collateral cards, check valuations, and build liquidation monitors. #### Collateral Views — Position Info ##### `getCollateralType(colId: number): number` — READ Returns the collateral type flag for a given position: 1 = single token (disabled), 2 = v4 LP pool, 3 = v3 LP NFT. Parameters: - `colId` (`number`): Collateral position ID. Returns: `number` — 1 = token (disabled), 2 = v4 pool, 3 = v3 LP NFT. ```js const cType = await readContract("saturnvault", "getCollateralType", [colId]); // 2 = v4 pool, 3 = v3 LP NFT ``` ##### `getCollateralOwner(colId: number): address` — READ Returns the borrower address that deposited this collateral position. Parameters: - `colId` (`number`): Collateral position ID. Returns: `address` — Address of the depositing borrower. ##### `getCollateralLoanId(colId: number): number` — READ Returns the loan ID this collateral is linked to, or 0 if the position is not yet linked to any loan. A position may be deposited before a loan is formally opened. Parameters: - `colId` (`number`): Collateral position ID. Returns: `number` — Linked loan ID, or 0 if unlinked. ##### `getCollateralStatus(colId: number): number` — READ Returns the lifecycle status of the collateral: 1 = locked (active), 2 = released (returned to borrower on repayment), 3 = liquidated (transferred to lender after default). Parameters: - `colId` (`number`): Collateral position ID. Returns: `number` — 1 = locked, 2 = released, 3 = liquidated. ```js const status = await readContract("saturnvault", "getCollateralStatus", [colId]); const labels = { 1: "Locked", 2: "Released", 3: "Liquidated" }; console.log(labels[status]); ``` ##### `getCollateralDexVersion(colId: number): number` — READ Returns which DEX version underpins this collateral: 1 = v3 (SATRN), 2 = v4 (saturnpools). For type-2 collateral this is always 2; for type-3 always 1. Parameters: - `colId` (`number`): Collateral position ID. Returns: `number` — 1 = v3 SATRN, 2 = v4 saturnpools. ##### `getCollateralSummary(colId: number): string` — READ Returns a packed single-string summary of any collateral position — type, DEX version, status, linked loan, and the key asset identifiers. Format varies by type: token positions include symbol/amount; v4 pool positions include poolId and pair; v3 NFT positions include nftId and pair key. Use this for concise collateral cards without multiple round-trips. Parameters: - `colId` (`number`): Collateral position ID. Returns: `string` — Packed string, e.g. "type:v4pool_dex:2_status:1_loan:7_poolId:3_pair:KCAL_RA" or "type:v3lpnft_dex:1_status:1_loan:7_nftId:42_pair:SOUL_RA". ```js const summary = await readContract("saturnvault", "getCollateralSummary", [colId]); const fields = Object.fromEntries(summary.split("_").map(p => p.split(":"))); ``` ##### `getNextCollateralId(): number` — READ Returns the ID that will be assigned to the next deposited collateral position. Collateral IDs are auto-incrementing from 1. Use this to predict the incoming ID before a deposit, or to iterate all positions from 1 to nextCollateralId - 1. Parameters: none Returns: `number` — Next collateral ID (1-based, auto-incremented). ```js const nextId = await readContract("saturnvault", "getNextCollateralId", []); ``` #### Collateral Views — User Index ##### `getUserCollateralCount(user: address): number` — READ Returns how many collateral positions a user has ever deposited (all statuses: locked, released, and liquidated). Use this as the loop bound when calling getUserCollateralAtIndex to enumerate a borrower's full collateral history. Parameters: - `user` (`address`): Borrower's address. Returns: `number` — Total collateral positions registered for this user. ```js const count = await readContract("saturnvault", "getUserCollateralCount", [borrower]); for (let i = 0; i < count; i++) { const colId = await readContract("saturnvault", "getUserCollateralAtIndex", [borrower, i]); const summary = await readContract("saturnvault", "getCollateralSummary", [colId]); } ``` ##### `getUserCollateralAtIndex(user: address, index: number): number` — READ Returns the collateral ID at a specific index in a user's collateral list. Indexes are 0-based and ordered by deposit time. Combine with getUserCollateralCount to page through a borrower's complete collateral history. Parameters: - `user` (`address`): Borrower's address. - `index` (`number`): 0-based index into the user's collateral list. Returns: `number` — Collateral position ID at the given index. Notes: Returns 0 (default map value) if index is out of range — check against getUserCollateralCount first. #### Collateral Views — V4 Pool Fields ##### `getCollateralPoolId(colId: number): number` — READ Returns the v4 DEX pool ID locked as collateral. Use with saturnpools to look up live reserves and pool state. Parameters: - `colId` (`number`): Collateral position ID (must be type 2). Returns: `number` — v4 pool ID. ##### `getCollateralPoolTokenA(colId: number): string` — READ Returns the symbol of token A in the locked v4 pool. Parameters: - `colId` (`number`): Collateral position ID (type 2). Returns: `string` — Token A symbol (e.g. "KCAL"). ##### `getCollateralPoolTokenB(colId: number): string` — READ Returns the symbol of token B in the locked v4 pool. Parameters: - `colId` (`number`): Collateral position ID (type 2). Returns: `string` — Token B symbol (e.g. "RA"). ##### `getCollateralPoolReserveAAtLock(colId: number): number` — READ Returns the snapshot of token A's reserve recorded at the moment the pool was locked. Compare against current reserves to quantify fee accrual and impermanent loss since collateral was posted. Parameters: - `colId` (`number`): Collateral position ID (type 2). Returns: `number` — Token A reserve at lock time (scaled units). ##### `getCollateralPoolReserveBAtLock(colId: number): number` — READ Returns the snapshot of token B's reserve at lock time. Pair with getCollateralPoolReserveAAtLock to reconstruct the pool's price and depth at the time collateral was posted. Parameters: - `colId` (`number`): Collateral position ID (type 2). Returns: `number` — Token B reserve at lock time (scaled units). #### Collateral Views — V3 LP NFT Fields ##### `getCollateralNftId(colId: number): number` — READ Returns the SATRN NFT ID held in vault custody for a v3 LP NFT collateral position. Parameters: - `colId` (`number`): Collateral position ID (must be type 3). Returns: `number` — SATRN LP NFT ID. ##### `getCollateralNftPairKey(colId: number): string` — READ Returns the cached pair key string for the v3 LP NFT (format: "TOKENA_TOKENB"). Cached at deposit time from saturndexadapt. Parameters: - `colId` (`number`): Collateral position ID (type 3). Returns: `string` — Pair key string, e.g. "SOUL_RA". ##### `getCollateralNftTokenA(colId: number): string` — READ Returns the symbol of token A in the v3 LP NFT pair. Parameters: - `colId` (`number`): Collateral position ID (type 3). Returns: `string` — Token A symbol. ##### `getCollateralNftTokenB(colId: number): string` — READ Returns the symbol of token B in the v3 LP NFT pair. Parameters: - `colId` (`number`): Collateral position ID (type 3). Returns: `string` — Token B symbol. ##### `getCollateralNftLiquidityAtLock(colId: number): number` — READ Returns the liquidity snapshot recorded when the SATRN LP NFT was deposited into the vault. Use this as the baseline to assess how the pool's depth has changed since the NFT was locked. Parameters: - `colId` (`number`): Collateral position ID (type 3). Returns: `number` — NFT liquidity at deposit time. #### Collateral Valuation ##### `getCollateralValue(colId: number, baseToken: string, baseDex: number): number` — READ Generic valuation dispatcher: reads the collateral type and routes to the correct type-specific valuation. Returns the position's current value denominated in baseToken, scaled to 8 decimals. The baseToken must be RA-paired on the chosen baseDex. This is the primary call for LTV monitoring — use it to compute the collateral-to-debt ratio at any time. Parameters: - `colId` (`number`): Collateral position ID. - `baseToken` (`string`): Token symbol to denominate the value in (e.g. "RA"). - `baseDex` (`number`): DEX version (1 = v3, 2 = v4) hosting the RA pricing pool for baseToken. Returns: `number` — Current collateral value in baseToken, 8-decimal scaled. Notes: Reverts with 'Unknown collateral type' if the stored type is not 1/2/3. Reverts if the underlying pool lookup fails. For type-1 (disabled) collateral, routes through getTokenCollateralValue which reads live DEX state. ```js // Get current value of collateral position #5 denominated in RA (v4 DEX) const value = await readContract("saturnvault", "getCollateralValue", [5, "RA", 2]); // Scaled 8-decimal number — divide by 1e8 for display ``` ##### `getV4PoolCollateralValue(colId: number, baseToken: string, baseDex: number): number` — READ Directly values a type-2 (v4 pool) collateral position by calling saturndexadapt.v4PoolValueInBase with the locked pool ID. Use when you already know the position is a v4 pool and want to skip the type-dispatch overhead. Parameters: - `colId` (`number`): Collateral position ID (type 2). - `baseToken` (`string`): Token symbol to denominate the value in. - `baseDex` (`number`): DEX version for baseToken's RA pricing pool. Returns: `number` — Pool value in baseToken, 8-decimal scaled. Notes: Reverts with 'Not v4 pool collateral' if colId is not type 2. ```js const poolVal = await readContract("saturnvault", "getV4PoolCollateralValue", [colId, "RA", 2]); ``` ##### `getV3LpNftCollateralValue(colId: number, baseToken: string, baseDex: number): number` — READ Values a type-3 (v3 LP NFT) collateral position by delegating to saturndexadapt.v3LpNftValueInBase using the stored NFT ID. Reflects live reserve state of the v3 pool as fee accrual continues while the NFT is in custody. Parameters: - `colId` (`number`): Collateral position ID (type 3). - `baseToken` (`string`): Token symbol to denominate the value in. - `baseDex` (`number`): DEX version for baseToken's RA pricing pool. Returns: `number` — NFT LP value in baseToken, 8-decimal scaled. Notes: Reverts with 'Not v3 LP NFT collateral' if colId is not type 3. ```js const nftVal = await readContract("saturnvault", "getV3LpNftCollateralValue", [colId, "RA", 1]); ``` ##### `getTokenCollateralValue(colId: number, baseToken: string, baseDex: number): number` — READ Values a type-1 (single token) collateral position. Returns the stored raw amount scaled up and converted to baseToken via the RA-pair pricing path. Although depositTokenCollateral is disabled, this read path remains active for positions created before the v1.0 cutoff. Parameters: - `colId` (`number`): Collateral position ID (type 1). - `baseToken` (`string`): Token symbol to denominate the value in. - `baseDex` (`number`): DEX version for baseToken's RA pricing pool. Returns: `number` — Token value in baseToken, 8-decimal scaled. Notes: Reverts with 'Not token collateral' if colId is not type 1. #### Single-Token Collateral (Disabled in v1.0) ##### `depositTokenCollateral(from: address, tokenSymbol: string, amount: number, dexVersion: number): number` — WRITE Deposits a single ERC-20-style token as loan collateral. The token must be RA itself or have an RA-paired pool on the chosen DEX for LTV pricing. Disabled in v1.0 — the body immediately reverts with "Token collateral disabled in v1.0 - use v4 LP pool or v3 LP NFT". Use depositV4PoolCollateral (via saturnmarket) for an active v4 LP pool, or depositV3LpNftCollateral (via saturnmarket) for a v3 LP NFT. The ABI and storage layout are preserved on-chain so in-flight pre-upgrade positions retain their full lifecycle. Parameters: - `from` (`address`): Borrower's address and transaction signer. - `tokenSymbol` (`string`): Symbol of the token to post as collateral. - `amount` (`number`): Raw (unscaled) token amount to deposit. - `dexVersion` (`number`): DEX version (1 = v3, 2 = v4) hosting the token's RA pricing pool. Returns: `number` — Would return the new collateral ID — always reverts in v1.0. Notes: Always reverts in v1.0. Use LP-backed collateral via saturnmarket instead. ### SaturnLoans — `saturnloans` *Loan Ledger & Lifecycle* — Saturn DEX v4 · Lending Protocol Central on-chain ledger for every loan in the Saturn lending protocol. Stores principal, interest rate, total owed, repayment progress, installment schedule, collateral link, and a four-state status machine (active → repaid / defaulted → liquidated). Borrowers call makePayment() directly; anyone can permissionlessly enforce defaults or LTV-based liquidations via triggerDefault() and triggerLiquidation(). A rich set of pure view methods lets integrators reconstruct full loan state in a single pass. Loan creation is driven internally by saturnmarket (P2P) and saturnauto (algorithmic) — not by end-user calls. #### Loan Repayment ##### `makePayment(from: address, loanId: number, paymentAmount: number)` — WRITE Borrower repays part or all of their loan. The payment is split between the lender and the protocol fee treasury based on the interest portion. Installment tracking and the next-due timestamp are advanced automatically. If the payment fully clears the outstanding balance (totalRepaid >= totalOwed) the loan status moves to 2 (repaid), collateral is released back to the borrower, the credit score is updated, and TAZ rewards are distributed. Overpayments are silently clamped to the remaining balance — never reverting due to rounding. Parameters: - `from` (`address`): Borrower's address — must be the transaction witness and the loan's registered borrower. - `loanId` (`number`): ID of the active loan to pay against. - `paymentAmount` (`number`): Raw-unit amount of the loan token to transfer. Clamped to remaining balance if larger. Notes: Reverts if: loan status != 1 (active); caller is not the borrower; borrower token balance < paymentAmount; re-entrancy guard active. paymentAmount must be > 0. ```js // Repay 50 KCAL towards loan #7 const from = "S...myWallet"; const tx = sb.begin() .allowGas(from) .callContract("saturnloans", "makePayment", [from, 7, 5000000000]) // 50 KCAL (8-decimal scaled) .spendGas(from) .endScript(); ``` #### Default & Liquidation Enforcement ##### `triggerDefault(loanId: number)` — WRITE Permissionlessly marks a loan as defaulted once the due date plus the grace period has passed. Moves status from 1 (active) → 3 (defaulted) → 4 (liquidated) within the same call, dispatches collateral liquidation by type (token → lender, V4 pool → admin resolution, V3 LP NFT → lender), and updates the borrower's credit score. Anyone — bots, lenders, or keepers — can call this; no witness check is required beyond the loan's time constraints. Parameters: - `loanId` (`number`): ID of the active loan to default. Notes: Reverts if loan status != 1 (active), or if the current timestamp is not past dueDate + gracePeriod. Idempotent per loan — once status != 1 the check at the top reverts. ```js // Keeper bot enforcing a stale loan const tx = sb.begin() .allowGas(keeperAddr) .callContract("saturnloans", "triggerDefault", [loanId]) .spendGas(keeperAddr) .endScript(); ``` ##### `triggerLiquidation(loanId: number)` — WRITE Permissionlessly liquidates a loan whose current LTV has risen above the liquidation threshold configured in saturnlendcfg. Moves status to 4 (liquidated), dispatches collateral transfer, and impacts the borrower's credit score as a default. Use getCurrentLtv() first to check whether the threshold is breached before submitting. Anyone can call — useful for liquidation bots monitoring under-collateralised positions. Parameters: - `loanId` (`number`): ID of the active loan to liquidate. Notes: Reverts if loan status != 1 (active), or if getCurrentLtv(loanId) <= liquidationThreshold. Threshold is typically expressed per 10,000 (e.g., 8,000 = 80% LTV). ```js // Check LTV then liquidate if eligible const ltv = await readContract("saturnloans", "getCurrentLtv", [loanId]); const threshold = await readContract("saturnlendcfg", "getLiquidationThreshold", []); if (ltv > threshold) { const tx = sb.begin() .allowGas(botAddr) .callContract("saturnloans", "triggerLiquidation", [loanId]) .spendGas(botAddr) .endScript(); } ``` #### Loan Status Checks ##### `installmentOverdue(loanId: number): number` — READ Returns 1 if the current installment is overdue (past its due timestamp plus the grace period), 0 otherwise. Returns 0 immediately for any non-active loan, so it is safe to call on any loan ID without checking status first. Use this to power overdue-payment warnings in your UI or to decide whether to flag a credit score degradation. Parameters: - `loanId` (`number`): ID of the loan to inspect. Returns: `number` — 1 if current installment is overdue, 0 if on-time or loan is not active. Notes: Never reverts. Returns 0 for non-existent or closed loans. ```js const overdue = await readContract("saturnloans", "installmentOverdue", [loanId]); if (overdue === "1") showWarningBanner("Installment overdue — pay now to protect your credit score."); ``` ##### `getCurrentLtv(loanId: number): number` — READ Returns the live loan-to-value ratio of the position, expressed per 10,000 (e.g., 7,500 = 75% LTV). Computes the remaining balance divided by the current collateral value in loan-token terms, using the RA anchor pool for pricing. If collateral value is zero — e.g., a de-listed token — returns 10,000 (100% LTV) to signal immediate liquidation eligibility. Parameters: - `loanId` (`number`): ID of the loan to inspect. Returns: `number` — LTV per 10,000. 0 means fully repaid; 10,000 means collateral is worthless. Notes: Returns 0 if remaining balance is 0 (fully repaid). Returns 10,000 if collateral price is 0. Calls saturnvault and saturndexadapt — reverts if those contracts revert. ```js const ltv = await readContract("saturnloans", "getCurrentLtv", [loanId]); // ltv=7500 → 75% LTV; threshold is typically 8000 → safe ``` #### Loan Core Data ##### `getLoanBorrower(loanId: number): address` — READ Returns the borrower address for the given loan ID. Parameters: - `loanId` (`number`): Loan ID. Returns: `address` — Borrower's wallet address. ##### `getLoanLender(loanId: number): address` — READ Returns the lender address. For auto loans this is the saturnauto contract address; for P2P loans via saturnmarket it is the individual lender's wallet. Parameters: - `loanId` (`number`): Loan ID. Returns: `address` — Lender address. ##### `getLoanToken(loanId: number): string` — READ Returns the symbol of the token that was lent (e.g., "KCAL"). Parameters: - `loanId` (`number`): Loan ID. Returns: `string` — Token symbol of the loan currency. ##### `getLoanTokenDex(loanId: number): number` — READ Returns the DEX version used to price this loan token via the RA anchor pool. 1 = Saturn V3 (SATRN string-keyed pools); 2 = Saturn V4 (saturnpools numeric IDs). Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — 1 for V3, 2 for V4. ##### `getLoanPrincipal(loanId: number): number` — READ Returns the original scaled principal amount at origination. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Original principal in scaled (8-decimal) units. ##### `getLoanInterestRate(loanId: number): number` — READ Returns the annual interest rate for this loan, expressed per 10,000 (e.g., 500 = 5%). Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Annual rate per 10,000. ##### `getLoanTotalOwed(loanId: number): number` — READ Returns the total amount owed (principal + interest) fixed at origination in scaled units. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Total owed in scaled units. ##### `getLoanTotalRepaid(loanId: number): number` — READ Returns the cumulative amount already repaid in scaled units. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Total repaid so far in scaled units. ##### `getLoanRemaining(loanId: number): number` — READ Convenience view — returns totalOwed minus totalRepaid, floored at 0. Use this for the "amount still owed" figure rather than computing it client-side. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Remaining balance in scaled units; 0 if fully repaid. ```js const remaining = await readContract("saturnloans", "getLoanRemaining", [loanId]); ``` ##### `getLoanCollateralId(loanId: number): number` — READ Returns the collateral ID in saturnvault linked to this loan. Pass this ID to saturnvault view methods to inspect collateral type, value, and status. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Collateral entry ID in saturnvault. ##### `getLoanStatus(loanId: number): number` — READ Returns the current status code: 1 = active, 2 = repaid, 3 = defaulted, 4 = liquidated. Note: triggerDefault() moves through 3 → 4 atomically, so 3 is transient and rarely observed by polling. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — 1 active | 2 repaid | 3 defaulted | 4 liquidated. ```js const status = await readContract("saturnloans", "getLoanStatus", [loanId]); const labels = { "1": "Active", "2": "Repaid", "3": "Defaulted", "4": "Liquidated" }; ``` ##### `getLoanOrigin(loanId: number): number` — READ Returns 1 if the loan was originated by saturnauto (algorithmic), 2 if originated by saturnmarket (P2P). Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — 1 = auto, 2 = P2P market. ##### `getLoanOriginationFee(loanId: number): number` — READ Returns the one-time origination fee charged at loan creation in scaled units. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Origination fee in scaled units (typically 1% of principal). ##### `getLoanSummary(loanId: number): string` — READ Returns a single packed string with all headline loan fields — status, token, principal, total owed, total repaid, interest rate, origin, and installment progress. Formatted as "status:N_token:SYM_principal:N_owed:N_repaid:N_rate:N_origin:N_installments:paid/total". Ideal for single-call loan cards or log entries. Parameters: - `loanId` (`number`): Loan ID. Returns: `string` — Packed summary string. ```js const summary = await readContract("saturnloans", "getLoanSummary", [loanId]); // "status:1_token:KCAL_principal:500000000000_owed:525000000000_repaid:100000000000_rate:500_origin:2_installments:1/6" ``` #### Loan Timing ##### `getLoanCreatedAt(loanId: number): number` — READ Returns the Unix timestamp of loan origination. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Origination timestamp in seconds. ##### `getLoanDuration(loanId: number): number` — READ Returns the total loan term in seconds (e.g., 2,592,000 = 30 days). Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Loan duration in seconds. ##### `getLoanDueDate(loanId: number): number` — READ Returns the Unix timestamp when the full balance is due (createdAt + duration). After this date plus the grace period, triggerDefault() may be called. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Due-date timestamp in seconds. ```js const due = await readContract("saturnloans", "getLoanDueDate", [loanId]); const daysLeft = Math.floor((due - Date.now() / 1000) / 86400); ``` ##### `getLoanNextInstallmentDue(loanId: number): number` — READ Returns the Unix timestamp when the next installment payment is due. Updated by makePayment() after each payment. Compare to the current time plus the grace period to determine if installmentOverdue() will return 1. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Next installment due timestamp in seconds. #### Installment Details ##### `getLoanInstallmentCount(loanId: number): number` — READ Returns the total number of installments scheduled for this loan (typically ceil(duration / 30 days)). Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Total scheduled installment count. ##### `getLoanInstallmentAmount(loanId: number): number` — READ Returns the equal per-installment amount in scaled units. Multiply by installmentCount to verify it equals totalOwed (modulo rounding). Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Per-installment amount in scaled units. ##### `getLoanInstallmentsPaid(loanId: number): number` — READ Returns how many full installments have been satisfied so far. Derived from totalRepaid divided by installmentAmount — not a separate counter — so it reflects partial overpayments accurately. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Number of installments fully paid to date. #### Borrower Loan Listing ##### `getUserLoanCount(user: address): number` — READ Returns the total number of loans ever opened by this borrower (including closed and defaulted). Use as the upper bound when iterating getUserLoanAtIndex(). Parameters: - `user` (`address`): Borrower address to query. Returns: `number` — Total loan count for this borrower. ##### `getUserLoanAtIndex(user: address, index: number): number` — READ Returns the loan ID at a zero-based index in the borrower's personal loan list. Iterate from 0 to getUserLoanCount(user) - 1 to enumerate all loans for a borrower. Parameters: - `user` (`address`): Borrower address. - `index` (`number`): Zero-based index into the borrower's loan list. Returns: `number` — Loan ID at that index. ```js const count = await readContract("saturnloans", "getUserLoanCount", [borrowerAddr]); const loanIds = await Promise.all( Array.from({ length: Number(count) }, (_, i) => readContract("saturnloans", "getUserLoanAtIndex", [borrowerAddr, i]) ) ); ``` #### Lender Loan Listing ##### `getLenderLoanCount(lender: address): number` — READ Returns the total number of loans associated with a lender address, including both active and completed positions. Parameters: - `lender` (`address`): Lender address to query. Returns: `number` — Total loan count for this lender. ##### `getLenderLoanAtIndex(lender: address, index: number): number` — READ Returns the loan ID at a zero-based index in the lender's loan list. Iterate from 0 to getLenderLoanCount(lender) - 1 to build a lender portfolio view. Parameters: - `lender` (`address`): Lender address. - `index` (`number`): Zero-based index into the lender's loan list. Returns: `number` — Loan ID at that index. ```js const count = await readContract("saturnloans", "getLenderLoanCount", [lenderAddr]); for (let i = 0; i < Number(count); i++) { const loanId = await readContract("saturnloans", "getLenderLoanAtIndex", [lenderAddr, i]); // then getLoanSummary(loanId) } ``` #### Protocol Stats ##### `getNextLoanId(): number` — READ Returns the ID that will be assigned to the next loan created. Loan IDs are monotonically incremented from 1, so this equals totalLoansEverCreated + 1. Parameters: none Returns: `number` — Next loan ID to be issued. ##### `getTotalActiveLoans(): number` — READ Returns the count of currently active (status = 1) loans across the entire protocol. Parameters: none Returns: `number` — Number of active loans. ##### `getTotalCompletedLoans(): number` — READ Returns the count of loans that have been fully repaid (status = 2). Parameters: none Returns: `number` — Number of fully-repaid loans. ##### `getTotalDefaultedLoans(): number` — READ Returns the count of loans that ended in default or liquidation (status 3 or 4). Note: triggerDefault() moves atomically to 4, so this counter reflects final liquidations as well. Parameters: none Returns: `number` — Number of defaulted/liquidated loans. ##### `getTotalPrincipalLent(): number` — READ Returns the cumulative principal ever lent across all loans, in scaled units. Useful for protocol TVL displays and risk dashboards. Parameters: none Returns: `number` — Cumulative principal lent in scaled units. ##### `getTotalInterestEarned(): number` — READ Returns the cumulative interest collected on all fully-repaid loans, in scaled units. Note: defaulted/liquidated loans do not contribute to this counter. Parameters: none Returns: `number` — Cumulative interest earned in scaled units. ### SaturnAuto — `saturnauto` *Automatic Lending (Disabled in v1.0)* — Saturn DEX v4 · Lending Protocol Protocol-managed lending reserve with algorithmic loan origination. Lenders deposit tokens into a shared pool; borrowers receive auto-approved loans sized and priced by credit score (LTV 25–80%, rate 3–30%, term 7–365 days). LP collateral earns a 5% rate discount and 10% duration bonus. IMPORTANT: All state-mutating methods in this contract revert in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". The full ABI and storage layout are preserved on-chain for a future in-place upgrade — integrators should target saturnmarket for all active loan origination. View methods and previewLoanTerms() remain fully functional. #### Loan Term Preview ##### `previewLoanTerms(user: address, loanTokenSymbol: string, loanAmount: number): string` — READ Computes and returns the full set of algorithmic loan terms that would apply to a given borrower and loan request — without creating a loan. Returns a packed string with all key parameters: credit score, max LTV, interest rate, duration, total interest, total owed, installment count and per-installment amount, and origination fee. Use this as a quote widget even while auto-lending is disabled — the calculation logic reads live credit scores and config, so it reflects current parameters. Parameters: - `user` (`address`): The prospective borrower whose credit score drives the terms. - `loanTokenSymbol` (`string`): Symbol of the token to borrow (e.g., "KCAL"). - `loanAmount` (`number`): Raw-unit borrow amount before scaling. Returns: `string` — Packed string: "score:N_maxLTV:N_rate:N_duration:N_interest:N_totalOwed:N_installments:N_perInstallment:N_origFee:N". Notes: Reverts if the token is not approved in the saturnauto registry. Rate per 10,000 (500 = 5%). Duration in seconds. All amounts in scaled (8-decimal) units. ```js const terms = await readContract("saturnauto", "previewLoanTerms", ["S...borrowerAddr", "KCAL", 100]); // "score:720_maxLTV:6500_rate:450_duration:5184000_interest:10430..._totalOwed:110430..._installments:2_perInstallment:55215..._origFee:1000..." ``` #### Reserve Views ##### `getReserveBalance(tokenSymbol: string): number` — READ Returns the current available (unlent) scaled balance of a given token in the protocol reserve. Even though deposits are disabled in v1.0, the reserve may hold tokens from pre-launch testing; this call is safe. Parameters: - `tokenSymbol` (`string`): Token symbol to check (e.g., "KCAL"). Returns: `number` — Available reserve balance in scaled units. ##### `getReserveTotalDeposited(tokenSymbol: string): number` — READ Returns the cumulative total deposited into the reserve for a token (lifetime figure, not current balance). Parameters: - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Cumulative deposits in scaled units. ##### `getReserveTotalLent(tokenSymbol: string): number` — READ Returns the cumulative total lent out from the reserve for a token (lifetime figure). Parameters: - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Cumulative lent amount in scaled units. ##### `getUtilizationRate(tokenSymbol: string): number` — READ Returns the utilization rate of the reserve for a given token, expressed per 10,000 (e.g., 6,500 = 65% utilised). Computed as totalLent / totalDeposited. Returns 0 if nothing has ever been deposited. Parameters: - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Utilization rate per 10,000. ```js const util = await readContract("saturnauto", "getUtilizationRate", ["KCAL"]); // util = "6500" → 65% of reserve is currently lent out ``` #### Depositor Views ##### `getDepositorBalance(depositor: address, tokenSymbol: string): number` — READ Returns the scaled balance a specific depositor has in the reserve for a given token. Parameters: - `depositor` (`address`): Depositor address. - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Depositor's reserve balance in scaled units. ##### `getDepositorCount(tokenSymbol: string): number` — READ Returns the number of depositors who have funds in the reserve for a given token. Parameters: - `tokenSymbol` (`string`): Token symbol. Returns: `number` — Number of unique depositors for this token. #### Approved Token Registry Views ##### `getTokenApproved(tokenSymbol: string): number` — READ Returns 1 if the token is on the approved lending list, 0 otherwise. Even though lending is disabled in v1.0, the registry may be pre-populated for future use. Parameters: - `tokenSymbol` (`string`): Token symbol to check. Returns: `number` — 1 if approved, 0 if not. ##### `getApprovedTokenDex(tokenSymbol: string): number` — READ Returns the DEX version (1 = V3, 2 = V4) that hosts the RA pricing pool for this approved lending token. Parameters: - `tokenSymbol` (`string`): Approved lending token symbol. Returns: `number` — 1 for V3, 2 for V4. ##### `getApprovedTokenCount(): number` — READ Returns the total count of entries in the approved token list (including any that may have been revoked). Parameters: none Returns: `number` — Number of entries in the approved token list. ##### `getApprovedTokens(): string*` — READ Iterates the approved token list and yields each symbol whose approved flag is still set to 1. Returns a stream of strings (Tomb generator return type string*). Parameters: none Returns: `string*` — Stream of approved token symbols. ```js const tokens = await readContract("saturnauto", "getApprovedTokens", []); // ["KCAL", "SOUL", ...] ``` ##### `getAutoLendingEnabled(): number` — READ Returns the internal autoLendingEnabled flag (1 = enabled at the contract level, 0 = disabled). Note: in v1.0 all write paths unconditionally revert regardless of this flag — its value reflects the storage state but does not gate the disable. Use this to surface a status indicator in your UI. Parameters: none Returns: `number` — 1 if the auto-lending flag is set, 0 if unset. #### Reserve Deposits (Disabled in v1.0) ##### `deposit(from: address, tokenSymbol: string, amount: number)` — WRITE DISABLED in v1.0 — reverts with "Auto-lending disabled in v1.0 - use saturnmarket P2P". When re-enabled, this will allow lenders to deposit tokens into the protocol reserve. Use saturnmarket postLoanOffer() for P2P lending in the current release. Parameters: - `from` (`address`): Depositor's address (witness required). - `tokenSymbol` (`string`): Symbol of the token to deposit. - `amount` (`number`): Raw-unit amount to deposit. Notes: Always reverts in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". ##### `withdraw(from: address, tokenSymbol: string, amount: number)` — WRITE DISABLED in v1.0 — reverts with "Auto-lending disabled in v1.0 - use saturnmarket P2P". When re-enabled, lenders will call this to retrieve their deposited tokens from the reserve. Use saturnmarket for all lender actions in the current release. Parameters: - `from` (`address`): Depositor's address (witness required). - `tokenSymbol` (`string`): Symbol of the token to withdraw. - `amount` (`number`): Raw-unit amount to withdraw. Notes: Always reverts in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". #### Auto Loan Requests (Disabled in v1.0) ##### `requestLoanWithToken(from: address, loanTokenSymbol: string, loanAmount: number, collateralTokenSymbol: string, collateralAmount: number, collateralDexVersion: number)` — WRITE DISABLED in v1.0 — reverts with "Auto-lending disabled in v1.0 - use saturnmarket P2P". When re-enabled, borrowers will use this to request an auto loan backed by token collateral (single-token collateral is also disabled in v1.0 via saturnvault). For P2P borrowing, use saturnmarket postLoanRequest(). Parameters: - `from` (`address`): Borrower's address (witness required). - `loanTokenSymbol` (`string`): Symbol of the token to borrow. - `loanAmount` (`number`): Raw-unit amount to borrow. - `collateralTokenSymbol` (`string`): Symbol of the token pledged as collateral. - `collateralAmount` (`number`): Raw-unit amount of collateral. - `collateralDexVersion` (`number`): DEX version (1=V3, 2=V4) for collateral pricing. Notes: Always reverts in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". ##### `requestLoanWithV4Pool(from: address, loanTokenSymbol: string, loanAmount: number, poolId: number)` — WRITE DISABLED in v1.0 — reverts with "Auto-lending disabled in v1.0 - use saturnmarket P2P". When re-enabled, borrowers will pledge a V4 LP pool position as collateral. The pool is locked via FinancialLock and provider fees continue accruing to the borrower during the loan term. For V4-pool-collateral P2P loans, use saturnmarket. Parameters: - `from` (`address`): Borrower's address (witness required). - `loanTokenSymbol` (`string`): Symbol of the token to borrow. - `loanAmount` (`number`): Raw-unit amount to borrow. - `poolId` (`number`): V4 pool ID to pledge as collateral. Notes: Always reverts in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". ##### `requestLoanWithV3Lp(from: address, loanTokenSymbol: string, loanAmount: number, nftId: number)` — WRITE DISABLED in v1.0 — reverts with "Auto-lending disabled in v1.0 - use saturnmarket P2P". When re-enabled, borrowers will pledge a V3 LP NFT as collateral (held in saturnvault custody; swap-fee growth accumulates in reserves and is realised on release). For V3-LP-collateral P2P loans, use saturnmarket. Parameters: - `from` (`address`): Borrower's address (witness required). - `loanTokenSymbol` (`string`): Symbol of the token to borrow. - `loanAmount` (`number`): Raw-unit amount to borrow. - `nftId` (`number`): V3 LP NFT series/token ID to pledge. Notes: Always reverts in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". #### Admin Token Approval (Disabled in v1.0) ##### `approveTokenForLending(tokenSymbol: string, dexVersion: number)` — WRITE DISABLED in v1.0 — reverts with "Auto-lending disabled in v1.0 - use saturnmarket P2P". When re-enabled, allows the admin to whitelist a token and its RA-pricing DEX version for use in auto loans. Parameters: - `tokenSymbol` (`string`): Token symbol to approve. - `dexVersion` (`number`): DEX version (1=V3, 2=V4) for RA pricing. Notes: Always reverts in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". ##### `revokeTokenForLending(tokenSymbol: string)` — WRITE DISABLED in v1.0 — reverts with "Auto-lending disabled in v1.0 - use saturnmarket P2P". When re-enabled, removes a token from the approved lending list. Parameters: - `tokenSymbol` (`string`): Token symbol to revoke. Notes: Always reverts in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". ##### `toggleAutoLending(enabled: number)` — WRITE DISABLED in v1.0 — reverts with "Auto-lending disabled in v1.0 - use saturnmarket P2P". When re-enabled, allows the admin to flip the autoLendingEnabled flag (1 = on, 0 = off). Parameters: - `enabled` (`number`): 1 to enable auto-lending, 0 to disable. Notes: Always reverts in v1.0 with "Auto-lending disabled in v1.0 - use saturnmarket P2P". ### SaturnMarket — `saturnmarket` *P2P Lending Marketplace* — Saturn DEX v4 · Lending Protocol The primary developer entry-point for Saturn Lending v1.0. Borrowers post loan requests backed by LP collateral (v4 pool or v3 LP NFT — single-token collateral is rejected in v1.0); lenders browse requests, submit binding quotes with escrowed funds, and the borrower atomically accepts a quote to open the loan. Collateral is locked by saturnvault, funds disbursed minus the origination fee, and the live loan is tracked by saturnloans. Build quote browsers, borrower dashboards, and lending bots entirely through this contract's ~52 public methods. #### Borrower — Request a Loan ##### `postLoanRequest(from: address, loanTokenSymbol: string, loanDexVersion: number, loanAmount: number, collateralType: number, collateralTokenSymbol: string, collateralTokenAmount: number, collateralDexVersion: number, collateralPoolId: number, collateralNftId: number, preferredDuration: number, maxInterestRate: number, message: string, expiresInSeconds: number): none` — WRITE Publishes a new loan request to the P2P marketplace. The borrower declares what token and how much they want to borrow, which DEX prices that token against RA, and what LP collateral they are offering. collateralType must be 2 (v4 pool) or 3 (v3 LP NFT) — collateralType 1 (single token) is rejected in v1.0. In v1.0 only TAZ loans are accepted. Pass 0 for the fields that don't apply to your collateral type (e.g. collateralTokenSymbol/collateralTokenAmount/collateralDexVersion when using a pool). The borrower must already be registered in saturncredit. The request stays open until cancelled, accepted, or expiresInSeconds elapses (min 1 day, max 30 days). Parameters: - `from` (`address`): Borrower's address. Must be the transaction witness. - `loanTokenSymbol` (`string`): Token to borrow. Must be TAZ in v1.0 and must have an RA pricing pool on the chosen DEX. - `loanDexVersion` (`number`): DEX used to price the loan token against RA. 1 = Saturn V3, 2 = Saturn V4. - `loanAmount` (`number`): Raw-unit amount of loanTokenSymbol the borrower wants to receive. - `collateralType` (`number`): 2 = v4 LP pool (FinancialLock), 3 = v3 LP NFT. collateralType 1 reverts in v1.0. - `collateralTokenSymbol` (`string`): Token symbol for type-1 collateral. Pass empty string for types 2 and 3. - `collateralTokenAmount` (`number`): Raw-unit token amount for type-1 collateral. Pass 0 for types 2 and 3. - `collateralDexVersion` (`number`): DEX that prices the collateral token (type 1 only). Pass 0 for types 2 and 3. - `collateralPoolId` (`number`): v4 pool ID the borrower is pledging (type 2). Must be active, owned, unlocked, and RA-paired. Pass 0 for types 1 and 3. - `collateralNftId` (`number`): v3 LP NFT ID the borrower is pledging (type 3). Must be RA-paired and have non-zero liquidity. Pass 0 for types 1 and 2. - `preferredDuration` (`number`): Preferred loan duration in seconds. Advisory only — lenders may quote different durations. - `maxInterestRate` (`number`): Maximum per-period interest rate the borrower will accept, in basis points. Quotes above this rate will be visible but the borrower is expected to filter them. - `message` (`string`): Optional freeform note shown to lenders (e.g. reason, preferred terms). May be empty. - `expiresInSeconds` (`number`): Seconds from now until the request auto-expires. Range: 86400 (1 day) to 2592000 (30 days). Notes: Reverts if: market is disabled, loanAmount <= 0, loanDexVersion not 1 or 2, collateralType == 1 (disabled), collateralType outside 1-3, maxInterestRate <= 0, expiresInSeconds < 86400 or > 2592000, loanTokenSymbol != 'TAZ' (v1.0), loan token has no RA pool on chosen DEX, borrower not registered in saturncredit. For type 2: pool not active / not owned by from / already locked / not RA-paired. For type 3: NFT not RA-paired or has no liquidity. ```js // Offer v4 pool #42 as collateral, borrow 500 TAZ, max 20% rate, 7-day window const tx = sb.begin() .allowGas(borrower) .callContract("saturnmarket", "postLoanRequest", [ borrower, // from "TAZ", // loanTokenSymbol 2, // loanDexVersion (v4) 500_000_000,// loanAmount (500 TAZ @ 6 decimals) 2, // collateralType (v4 LP pool) "", // collateralTokenSymbol (unused) 0, // collateralTokenAmount (unused) 0, // collateralDexVersion (unused) 42, // collateralPoolId 0, // collateralNftId (unused) 604800, // preferredDuration (7 days) 2000, // maxInterestRate (20%, basis pts) "Collateral is TAZ/SOUL pool", 604800 // expiresInSeconds (7 days) ]) .spendGas(borrower) .endScript(); ``` ##### `cancelRequest(from: address, requestId: number): none` — WRITE Cancels an open loan request. Only the borrower who posted the request may cancel it, and only while status is 1 (open). Sets status to 3 (cancelled) and decrements totalOpenRequests. Outstanding quotes on the request remain visible but lenders should withdrawQuote to recover their escrowed funds. Parameters: - `from` (`address`): Borrower's address. Must match the request's stored borrower and be the transaction witness. - `requestId` (`number`): ID of the loan request to cancel. Notes: Reverts if: from is not the witness, from != reqBorrower[requestId], or request status != 1 (open). ```js const tx = sb.begin() .allowGas(borrower) .callContract("saturnmarket", "cancelRequest", [borrower, requestId]) .spendGas(borrower) .endScript(); ``` #### Lender — Quote a Loan ##### `submitQuote(from: address, requestId: number, interestRate: number, duration: number, offeredLoanAmount: number, colType: number, colTokenSymbol: string, colTokenAmount: number, colDexVersion: number, colPoolId: number, colNftId: number, message: string, expiresInSeconds: number): none` — WRITE Submits a lending quote against an open loan request. The lender specifies their rate, duration, exact loan amount, and the collateral they require from the borrower. The offered loan amount is immediately escrowed from the lender's wallet into the contract so that acceptQuote does not require a second lender signature. colType must be 2 or 3 (LP-backed) — colType 1 (single token) is rejected in v1.0. The lender must hold sufficient funds to cover offeredLoanAmount at time of submission. Parameters: - `from` (`address`): Lender's address. Must be the transaction witness. Cannot be the borrower of the target request. - `requestId` (`number`): The open loan request this quote is for. - `interestRate` (`number`): Per-period interest rate in basis points offered by the lender. Must be > 0. - `duration` (`number`): Loan term in seconds. Must be within saturnlendcfg's [minLoanDuration, maxLoanDuration] range. - `offeredLoanAmount` (`number`): Raw-unit amount of the request's loan token the lender is willing to disburse. Immediately escrowed. - `colType` (`number`): Collateral type the lender demands. 2 = v4 LP pool, 3 = v3 LP NFT. colType 1 reverts in v1.0. - `colTokenSymbol` (`string`): Required token symbol for type-1 collateral. Pass empty string for types 2 and 3. - `colTokenAmount` (`number`): Required token amount for type-1 collateral. Pass 0 for types 2 and 3. - `colDexVersion` (`number`): DEX version used to price type-1 collateral. Pass 0 for types 2 and 3. - `colPoolId` (`number`): Specific v4 pool ID required as collateral (type 2). Must be active and RA-paired. Pass 0 for types 1 and 3. - `colNftId` (`number`): Specific v3 LP NFT ID required as collateral (type 3). Must be RA-paired. Pass 0 for types 1 and 2. - `message` (`string`): Optional note to the borrower (terms, conditions). May be empty. - `expiresInSeconds` (`number`): Seconds until this quote auto-expires. Should be <= remaining lifetime of the target request. Notes: Reverts if: market disabled, request not open (status != 1), request expired, from == borrower, interestRate/duration/offeredLoanAmount <= 0, colType == 1 (disabled), duration outside protocol min/max, lender's balance < offeredLoanAmount. For colType 2: pool not active or not RA-paired. For colType 3: NFT not RA-paired. ```js // Lender quotes 500 TAZ, demands v4 pool #99 as collateral, 15% rate, 14-day term const tx = sb.begin() .allowGas(lender) .callContract("saturnmarket", "submitQuote", [ lender, requestId, 1500, // interestRate (15%) 1209600, // duration (14 days in seconds) 500_000_000, // offeredLoanAmount (escrowed immediately) 2, // colType (v4 LP pool) "", // colTokenSymbol (unused) 0, // colTokenAmount (unused) 0, // colDexVersion (unused) 99, // colPoolId 0, // colNftId (unused) "Standard 14-day TAZ loan", 1209600 // expiresInSeconds ]) .spendGas(lender) .endScript(); ``` ##### `withdrawQuote(from: address, quoteId: number): none` — WRITE Cancels a pending quote and refunds the escrowed loan amount to the lender. Only the lender who submitted the quote may withdraw it, and only while it is still pending (status 1). Sets status to 4 (withdrawn) and transfers the escrowed funds back to from. Call this if the underlying request was cancelled, your terms changed, or you need your funds back. Parameters: - `from` (`address`): Lender's address. Must match quoteLender[quoteId] and be the transaction witness. - `quoteId` (`number`): ID of the quote to withdraw. Notes: Reverts if: from is not the witness, from != quoteLender[quoteId], or quote status != 1 (pending). ```js const tx = sb.begin() .allowGas(lender) .callContract("saturnmarket", "withdrawQuote", [lender, quoteId]) .spendGas(lender) .endScript(); ``` #### Accept & Open ##### `acceptQuote(from: address, quoteId: number): none` — WRITE Atomically accepts a lender's quote and opens the loan. The borrower's collateral is locked in saturnvault (v4 pool via FinancialLock or v3 LP NFT transferred to vault custody), the loan record is created in saturnloans, and the disbursement (escrowed amount minus origination fee) is transferred to the borrower. The origination fee goes to the protocol admin. Any rounding dust is returned to the lender. Both the quote and the parent request are marked as accepted (status 2) atomically. The resulting loanId is stored in reqLoanId[requestId] for later retrieval. Single-token collateral quotes (colType 1) are rejected as a defense-in-depth guard even if one somehow existed. Parameters: - `from` (`address`): Borrower's address. Must own the request that the quote is for, and be the transaction witness. - `quoteId` (`number`): ID of the lender's quote to accept. Notes: Reverts if: from is not the witness, quote status != 1 (pending), quote expired, from != borrower on the associated request, request status != 1 (open), escrow balance insufficient, borrower already at max concurrent loans (saturnlendcfg.getMaxLoansPerUser), colType == 1 (disabled). For colType 2: borrower must still own the pool. Emits a loan creation event via saturnloans. ```js // Borrower accepts quote #7 — collateral locked, TAZ disbursed in the same tx const tx = sb.begin() .allowGas(borrower) .callContract("saturnmarket", "acceptQuote", [borrower, quoteId]) .spendGas(borrower) .endScript(); // After the tx confirms, fetch the new loan ID: const requestId = await readContract("saturnmarket", "getQuoteRequestId", [quoteId]); const loanId = await readContract("saturnmarket", "getRequestLoanId", [requestId]); ``` #### Market Views ##### `getBorrowerProfile(borrower: address): string` — READ Returns a packed credit summary for a borrower address, useful for lender UIs that need to show creditworthiness at a glance. The string is prefixed with the number of days the borrower has been in the system, then appended with the full credit report from saturncredit. Format: "daysInSystem:_" (e.g. "daysInSystem:42_score:780_..."). Reverts if the address is not registered. Parameters: - `borrower` (`address`): Address of the borrower whose profile to fetch. Returns: `string` — Packed string: "daysInSystem:_". Notes: Reverts if borrower is not registered in saturncredit. ```js const profile = await readContract("saturnmarket", "getBorrowerProfile", [borrowerAddr]); // "daysInSystem:42_score:780_repaid:5_defaulted:0_streak:5" ``` ##### `getMarketEnabled(): number` — READ Returns 1 if the P2P market is open for new requests and quotes, 0 if an admin has paused it. Check this before rendering the post-request UI. Parameters: none Returns: `number` — 1 = enabled, 0 = disabled. ```js const enabled = await readContract("saturnmarket", "getMarketEnabled", []); ``` ##### `getTotalOpenRequests(): number` — READ Live count of requests currently in status 1 (open). Use for marketplace summary stats. Parameters: none Returns: `number` — Number of open loan requests. ```js const open = await readContract("saturnmarket", "getTotalOpenRequests", []); ``` ##### `getTotalMatchedLoans(): number` — READ Cumulative count of loan requests that have been accepted (i.e. loans opened) through the marketplace since deployment. Parameters: none Returns: `number` — Total number of matched / accepted loans. ```js const matched = await readContract("saturnmarket", "getTotalMatchedLoans", []); ``` ##### `getNextRequestId(): number` — READ Returns the ID that will be assigned to the next loan request. Subtract 1 to get the most recently created request ID. Use to paginate all-time listings. Parameters: none Returns: `number` — Next request ID (starts at 1; increments by 1 for each postLoanRequest). ```js const nextId = await readContract("saturnmarket", "getNextRequestId", []); // Iterate requestIds 1 .. nextId-1 to enumerate all requests ``` ##### `getNextQuoteId(): number` — READ Returns the ID that will be assigned to the next submitted quote. Use alongside getNextRequestId to enumerate all market activity. Parameters: none Returns: `number` — Next quote ID (starts at 1; increments by 1 for each submitQuote). ```js const nextQId = await readContract("saturnmarket", "getNextQuoteId", []); ``` ##### `getRequestSummary(reqId: number): string` — READ One-call summary of a request's most-needed fields. Format: "token:_loanDex:_amount:_colType:_status:_quotes:_maxRate:". Use for marketplace listing cards where a single round-trip is preferable to seven. Parameters: - `reqId` (`number`): Loan request ID. Returns: `string` — Packed summary string. ```js const summary = await readContract("saturnmarket", "getRequestSummary", [reqId]); // "token:TAZ_loanDex:2_amount:500000000_colType:2_status:1_quotes:3_maxRate:2000" ``` ##### `getRequestBorrower(reqId: number): address` — READ Returns the borrower address that posted this loan request. Parameters: - `reqId` (`number`): Loan request ID. Returns: `address` — Borrower's wallet address. ##### `getRequestLoanToken(reqId: number): string` — READ Returns the token symbol the borrower wants to borrow (TAZ in v1.0). Parameters: - `reqId` (`number`): Loan request ID. Returns: `string` — Token symbol, e.g. "TAZ". ##### `getRequestLoanDexVersion(reqId: number): number` — READ Returns which DEX (1 = V3, 2 = V4) is used to price the loan token against RA. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — 1 = V3, 2 = V4. ##### `getRequestLoanAmount(reqId: number): number` — READ Returns the raw-unit amount of the loan token the borrower is requesting. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Requested loan amount in raw token units. ##### `getRequestCollateralType(reqId: number): number` — READ Returns the borrower's offered collateral type. In v1.0 this will be 2 (v4 pool) or 3 (v3 LP NFT) — type 1 is never stored since it is rejected at post time. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — 2 = v4 LP pool, 3 = v3 LP NFT. ##### `getRequestCollateralToken(reqId: number): string` — READ Returns the collateral token symbol (type 1 only). Empty string for types 2 and 3. Parameters: - `reqId` (`number`): Loan request ID. Returns: `string` — Token symbol for type-1 collateral; empty otherwise. ##### `getRequestCollateralAmount(reqId: number): number` — READ Returns the collateral token amount (type 1 only). 0 for types 2 and 3. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Raw-unit collateral token amount; 0 for LP collateral types. ##### `getRequestCollateralDexVersion(reqId: number): number` — READ Returns the DEX version used to price the type-1 collateral token. 0 for types 2 and 3. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — 1 = V3, 2 = V4, or 0 for LP collateral types. ##### `getRequestCollateralPoolId(reqId: number): number` — READ Returns the v4 pool ID offered as collateral (type 2). 0 for types 1 and 3. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — v4 pool ID, or 0 if not a pool-backed request. ##### `getRequestCollateralNftId(reqId: number): number` — READ Returns the v3 LP NFT ID offered as collateral (type 3). 0 for types 1 and 2. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — v3 LP NFT ID, or 0 if not an NFT-backed request. ##### `getRequestPreferredDuration(reqId: number): number` — READ Returns the borrower's preferred loan duration in seconds. Advisory — lenders may quote different durations. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Preferred duration in seconds. ##### `getRequestMaxInterestRate(reqId: number): number` — READ Returns the maximum interest rate (basis points) the borrower is willing to accept. Use to filter out quotes above this threshold in your lender UI. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Max interest rate in basis points. ##### `getRequestMessage(reqId: number): string` — READ Returns the borrower's optional freeform message attached to the request. Parameters: - `reqId` (`number`): Loan request ID. Returns: `string` — Freeform note from the borrower. May be empty. ##### `getRequestStatus(reqId: number): number` — READ Returns the current status of the request. 1 = open, 2 = accepted, 3 = cancelled, 4 = expired. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — 1 open | 2 accepted | 3 cancelled | 4 expired. ```js const status = await readContract("saturnmarket", "getRequestStatus", [reqId]); if (status === 1) { /* show quote button */ } ``` ##### `getRequestCreatedAt(reqId: number): number` — READ Returns the Unix timestamp (seconds) when the request was posted. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Unix timestamp of creation. ##### `getRequestExpiresAt(reqId: number): number` — READ Returns the Unix timestamp (seconds) when the request expires. Use for countdown timers in the marketplace UI. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Unix expiry timestamp. ```js const expiresAt = await readContract("saturnmarket", "getRequestExpiresAt", [reqId]); const secsLeft = expiresAt - Math.floor(Date.now() / 1000); ``` ##### `getRequestQuoteCount(reqId: number): number` — READ Returns how many quotes have been submitted for this request. Combine with getRequestQuoteAtIndex to iterate them. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Total quote count for this request (includes withdrawn quotes). ```js const count = await readContract("saturnmarket", "getRequestQuoteCount", [reqId]); for (let i = 0; i < count; i++) { const qId = await readContract("saturnmarket", "getRequestQuoteAtIndex", [reqId, i]); } ``` ##### `getRequestAcceptedQuoteId(reqId: number): number` — READ Returns the ID of the quote that was accepted to open the loan. Only meaningful when request status is 2 (accepted). Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Accepted quote ID, or 0 if no quote has been accepted yet. ##### `getRequestLoanId(reqId: number): number` — READ Returns the saturnloans loanId created when this request was accepted. Use to link from the marketplace entry to the live loan in saturnloans. Only set after status becomes 2. Parameters: - `reqId` (`number`): Loan request ID. Returns: `number` — Loan ID in saturnloans, or 0 if not yet accepted. ##### `getRequestQuoteAtIndex(requestId: number, index: number): number` — READ Returns the quoteId at a given index within the quote list for a specific request. Iterate from 0 to getRequestQuoteCount(requestId)-1 to enumerate all quotes on a request. Parameters: - `requestId` (`number`): Loan request ID. - `index` (`number`): Zero-based index into the request's quote list. Returns: `number` — Quote ID at the given index. ```js const count = await readContract("saturnmarket", "getRequestQuoteCount", [reqId]); const quoteIds = await Promise.all( Array.from({ length: count }, (_, i) => readContract("saturnmarket", "getRequestQuoteAtIndex", [reqId, i]) ) ); ``` ##### `getQuoteSummary(qId: number): string` — READ One-call summary of a quote's most-needed fields. Format: "rate:_duration:_amount:_colType:_status:". Use for the quote card in the borrower's decision UI. Parameters: - `qId` (`number`): Quote ID. Returns: `string` — Packed summary string. ```js const summary = await readContract("saturnmarket", "getQuoteSummary", [quoteId]); // "rate:1500_duration:1209600_amount:500000000_colType:2_status:1" ``` ##### `getQuoteLender(qId: number): address` — READ Returns the lender address that submitted this quote. Parameters: - `qId` (`number`): Quote ID. Returns: `address` — Lender's wallet address. ##### `getQuoteRequestId(qId: number): number` — READ Returns the loan request ID that this quote is responding to. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Parent request ID. ##### `getQuoteInterestRate(qId: number): number` — READ Returns the per-period interest rate offered by the lender, in basis points. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Interest rate in basis points. ##### `getQuoteDuration(qId: number): number` — READ Returns the loan term in seconds proposed by the lender. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Loan duration in seconds. ##### `getQuoteLoanAmount(qId: number): number` — READ Returns the raw-unit loan amount the lender is offering (and has escrowed). This amount minus the origination fee is what the borrower actually receives. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Escrowed loan amount in raw token units. ##### `getQuoteColType(qId: number): number` — READ Returns the collateral type the lender is demanding. In v1.0 this will be 2 (v4 pool) or 3 (v3 LP NFT). Parameters: - `qId` (`number`): Quote ID. Returns: `number` — 2 = v4 LP pool, 3 = v3 LP NFT. ##### `getQuoteColTokenSymbol(qId: number): string` — READ Returns the token symbol the lender requires as collateral (type 1 only). Empty for types 2 and 3. Parameters: - `qId` (`number`): Quote ID. Returns: `string` — Collateral token symbol for type-1 quotes; empty otherwise. ##### `getQuoteColTokenAmount(qId: number): number` — READ Returns the collateral token amount required (type 1 only). 0 for types 2 and 3. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Required collateral amount in raw token units; 0 for LP types. ##### `getQuoteColDexVersion(qId: number): number` — READ Returns the DEX version used to price the type-1 collateral. 0 for types 2 and 3. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — 1 = V3, 2 = V4, or 0 for LP collateral types. ##### `getQuoteColPoolId(qId: number): number` — READ Returns the specific v4 pool ID the lender requires as collateral (type 2). 0 for types 1 and 3. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Required v4 pool ID; 0 if not a pool-collateral quote. ##### `getQuoteColNftId(qId: number): number` — READ Returns the specific v3 LP NFT ID the lender requires as collateral (type 3). 0 for types 1 and 2. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Required v3 LP NFT ID; 0 if not an NFT-collateral quote. ##### `getQuoteMessage(qId: number): string` — READ Returns the lender's optional message attached to the quote. Parameters: - `qId` (`number`): Quote ID. Returns: `string` — Freeform note from the lender. May be empty. ##### `getQuoteStatus(qId: number): number` — READ Returns the current status of the quote. 1 = pending, 2 = accepted, 4 = withdrawn. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — 1 pending | 2 accepted | 4 withdrawn. ```js const status = await readContract("saturnmarket", "getQuoteStatus", [quoteId]); if (status === 1) { /* quote is still live, borrower can accept */ } ``` ##### `getQuoteCreatedAt(qId: number): number` — READ Returns the Unix timestamp (seconds) when the quote was submitted. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Unix timestamp of quote creation. ##### `getQuoteExpiresAt(qId: number): number` — READ Returns the Unix timestamp (seconds) when the quote expires. acceptQuote reverts after this time. Parameters: - `qId` (`number`): Quote ID. Returns: `number` — Unix expiry timestamp. ```js const expiresAt = await readContract("saturnmarket", "getQuoteExpiresAt", [quoteId]); const expired = Date.now() / 1000 > expiresAt; ``` ##### `getUserRequestCount(user: address): number` — READ Returns the total number of loan requests ever posted by this address (includes cancelled and accepted ones). Use as the upper bound when iterating getUserRequestAtIndex. Parameters: - `user` (`address`): Borrower address to look up. Returns: `number` — Total request count for this user. ```js const count = await readContract("saturnmarket", "getUserRequestCount", [userAddr]); ``` ##### `getUserRequestAtIndex(user: address, index: number): number` — READ Returns the requestId at the given zero-based index in the user's personal request list. Iterate from 0 to getUserRequestCount(user)-1 for a full user history. Parameters: - `user` (`address`): Borrower address. - `index` (`number`): Zero-based index. Returns: `number` — Loan request ID at the given index. ```js const count = await readContract("saturnmarket", "getUserRequestCount", [userAddr]); const ids = await Promise.all( Array.from({ length: count }, (_, i) => readContract("saturnmarket", "getUserRequestAtIndex", [userAddr, i]) ) ); ``` ##### `getUserQuoteCount(user: address): number` — READ Returns the total number of quotes ever submitted by this lender address (includes withdrawn ones). Use as the upper bound when iterating getUserQuoteAtIndex. Parameters: - `user` (`address`): Lender address to look up. Returns: `number` — Total quote count for this lender. ##### `getUserQuoteAtIndex(user: address, index: number): number` — READ Returns the quoteId at the given zero-based index in the lender's personal quote list. Iterate from 0 to getUserQuoteCount(user)-1 for a full lender quote history. Parameters: - `user` (`address`): Lender address. - `index` (`number`): Zero-based index. Returns: `number` — Quote ID at the given index. ```js const count = await readContract("saturnmarket", "getUserQuoteCount", [lenderAddr]); const qIds = await Promise.all( Array.from({ length: count }, (_, i) => readContract("saturnmarket", "getUserQuoteAtIndex", [lenderAddr, i]) ) ); ``` ### SaturnDexAdapt — `saturndexadapt` *Dual-DEX Pricing Adapter (v3 + v4)* — Saturn DEX v4 · Lending Protocol Unified pricing and pool-validation bridge between the Saturn Lending protocol and both DEX generations: Saturn DEX v3 (SATRN string-keyed pools, LP NFTs) and Saturn DEX v4 (saturnpools numeric pool IDs, FinancialLock). Every loan and collateral token is valued via its RA-paired pool — the "RA-anchor" model — so pricing is always grounded in the protocol's reference asset. Developers building integrations can use the public views to price tokens, value LP collateral positions, resolve pool keys, and convert amounts between token denominations without touching the DEX contracts directly. #### Anchor Configuration ##### `getAnchorToken(): string` — READ Returns the current anchor token symbol (default: "RA"). Every pricing lookup routes through a pool that includes this token on one side. Call this before any pricing query to confirm the anchor hasn't been changed by governance. Parameters: none Returns: `string` — Symbol of the anchor token, e.g. "RA". Notes: Always a non-empty string. Never reverts. ```js const anchor = await readContract("saturndexadapt", "getAnchorToken", []); // "RA" ``` ##### `anchoredPair(tokenA: string, tokenB: string): number` — READ Returns 1 if at least one of the two token symbols equals the anchor token (i.e., the pair is a valid RA-paired pool from the protocol's perspective), 0 otherwise. Use as a fast pre-check before calling pricing methods. Parameters: - `tokenA` (`string`): First token symbol. - `tokenB` (`string`): Second token symbol. Returns: `number` — 1 if the pair involves the anchor token, 0 if not. Notes: Pure computation, no on-chain reads. Never reverts. ```js const ok = await readContract("saturndexadapt", "anchoredPair", ["MKST", "RA"]); // 1 ``` ##### `hasAnchorPool(tokenSymbol: string, dexVersion: number): number` — READ Returns 1 if a live RA-paired pool exists for the given token on the specified DEX version (1 = v3/SATRN, 2 = v4/saturnpools), 0 if not. A token whose value is 1 can be priced via priceInAnchor. Always call this before depositing collateral to confirm the token is priceable. Parameters: - `tokenSymbol` (`string`): Token to check for an RA-paired pool. - `dexVersion` (`number`): 1 for Saturn DEX v3, 2 for Saturn DEX v4. Returns: `number` — 1 if an RA pool exists, 0 if the token is not priceable. Notes: Returns 1 unconditionally if tokenSymbol == anchorToken. No revert. ```js const priceable = await readContract("saturndexadapt", "hasAnchorPool", ["MKST", 2]); // 1 — safe to use as v4 collateral ``` ##### `expectAnchorPool(tokenSymbol: string, dexVersion: number)` — READ Reverts with a descriptive message if no RA-paired pool exists for the token on the given DEX version. Use in scripts or simulation to gate-check a token before building a transaction. Parameters: - `tokenSymbol` (`string`): Token symbol to validate. - `dexVersion` (`number`): 1 for v3, 2 for v4. Notes: Reverts: "No RA pricing pool for on requested DEX" if hasAnchorPool returns 0. ```js // Will throw if NEWTOKEN has no RA pool on v4 await readContract("saturndexadapt", "expectAnchorPool", ["NEWTOKEN", 2]); ``` #### Amount Scaling ##### `warmScale(tokenSymbol: string)` — WRITE Precomputes and stores the scale factor for a token in the v4 pool registry (saturnpools.computeAndStoreScaleFactor). Call this once for any new token before the first scaleUp call in a transaction batch to avoid extra compute cost at pricing time. Parameters: - `tokenSymbol` (`string`): Token whose scale factor should be cached. Notes: Delegates entirely to saturnpools. Reverts if the token doesn't exist on-chain. ```js const tx = sb.begin() .allowGas(from) .callContract("saturndexadapt", "warmScale", ["NEWTOKEN"]) .spendGas(from) .endScript(); ``` ##### `scaleUp(rawAmount: number, tokenSymbol: string): number` — READ Converts a raw token amount (native decimals) to the protocol's internal 8-decimal scaled representation. All pricing views expect scaled inputs; call scaleUp on any user-supplied raw amount before passing it to priceInAnchor or priceInToken. Parameters: - `rawAmount` (`number`): Amount in the token's native on-chain decimals. - `tokenSymbol` (`string`): Token symbol to determine the scale factor. Returns: `number` — Equivalent amount scaled to 8 decimal places. Notes: Internally calls computeAndStoreScaleFactor to ensure the factor is warm. Reverts if token unknown. ```js // Convert 10 RA (9-dec native) to scaled const scaled = await readContract("saturndexadapt", "scaleUp", [10_000_000_000, "RA"]); // e.g. 1_000_000_000 (8-dec) ``` ##### `scaleDown(scaledAmount: number, tokenSymbol: string): number` — READ Converts a scaled (8-decimal) amount back to the token's native raw decimals. Use to convert a pricing result into a value you can display to users or pass to a transfer call. Parameters: - `scaledAmount` (`number`): Amount in 8-decimal scaled units. - `tokenSymbol` (`string`): Token symbol to determine the scale factor. Returns: `number` — Equivalent amount in the token's native decimals. Notes: Truncates on division. Never reverts on valid input. ```js const raw = await readContract("saturndexadapt", "scaleDown", [1_000_000_000, "RA"]); // 10_000_000_000 (9-dec RA) ``` #### V4 Pool Reads ##### `v4PoolActive(poolId: number): number` — READ Returns 1 if the v4 pool is active (live and accepting swaps), 0 otherwise. Check before submitting any collateral or pricing call against a specific pool ID. Parameters: - `poolId` (`number`): Numeric pool ID in the v4 PoolRegistry. Returns: `number` — 1 if active, 0 if inactive or non-existent. ```js const active = await readContract("saturndexadapt", "v4PoolActive", [42]); ``` ##### `v4PoolProvider(poolId: number): address` — READ Returns the address of the liquidity provider for a v4 pool. Useful to verify ownership before pledging or locking a pool as collateral. Parameters: - `poolId` (`number`): Numeric pool ID in the v4 PoolRegistry. Returns: `address` — Wallet address of the pool's single provider. ```js const provider = await readContract("saturndexadapt", "v4PoolProvider", [42]); ``` ##### `v4PoolLocked(poolId: number): number` — READ Returns 1 if the v4 pool is currently locked by any financial product (loan, bond, rental, etc.), 0 if free. A locked pool cannot have liquidity added or removed, but swaps and provider fee accrual continue normally. Parameters: - `poolId` (`number`): Numeric pool ID. Returns: `number` — 1 if locked, 0 if free. Notes: Based on saturnpools.getPoolFinancialLockCount — non-zero lock count means locked. ```js const locked = await readContract("saturndexadapt", "v4PoolLocked", [42]); if (locked) { /* pool already used as collateral */ } ``` ##### `v4PoolTokenA(poolId: number): string` — READ Returns the symbol of token A for the given v4 pool. Parameters: - `poolId` (`number`): Numeric pool ID. Returns: `string` — Symbol of the pool's token A. ```js const tokenA = await readContract("saturndexadapt", "v4PoolTokenA", [42]); ``` ##### `v4PoolTokenB(poolId: number): string` — READ Returns the symbol of token B for the given v4 pool. Parameters: - `poolId` (`number`): Numeric pool ID. Returns: `string` — Symbol of the pool's token B. ```js const tokenB = await readContract("saturndexadapt", "v4PoolTokenB", [42]); ``` ##### `v4PoolReserveA(poolId: number): number` — READ Returns the current reserve of token A in the specified v4 pool, in scaled (8-decimal) units. Parameters: - `poolId` (`number`): Numeric pool ID. Returns: `number` — Scaled token A reserve amount. ```js const resA = await readContract("saturndexadapt", "v4PoolReserveA", [42]); ``` ##### `v4PoolReserveB(poolId: number): number` — READ Returns the current reserve of token B in the specified v4 pool, in scaled (8-decimal) units. Parameters: - `poolId` (`number`): Numeric pool ID. Returns: `number` — Scaled token B reserve amount. ```js const resB = await readContract("saturndexadapt", "v4PoolReserveB", [42]); ``` #### V4 Pool Lock Proxies ##### `v4LockPool(from: address, poolId: number)` — WRITE Increments the FinancialLock count on a v4 pool, preventing the provider from adding or removing liquidity for the duration of the lock. This is the mechanism behind LP-collateral loans: the pool stays in place while the loan is open. Can only be called by the saturnvault contract — not directly by end users. Documented here as a developer integration reference. Parameters: - `from` (`address`): Pool provider address; must be the pool's registered provider. - `poolId` (`number`): ID of the v4 pool to lock. Notes: Reverts: "Only vault" if not called from saturnvault. "Pool not active" if the pool is inactive. "Only pool provider" if from != pool provider. "Pool has active fee redirect" if a fee redirect is set. "Pool already under another financial product" if lockCount > 0. ```js // Not called directly — invoked by saturnvault during collateral deposit. // Shown for integration reference only. ``` ##### `v4UnlockPool(poolId: number)` — WRITE Decrements the FinancialLock count on a v4 pool, re-enabling liquidity management once the loan is repaid or liquidated. Can only be called by saturnvault — not directly by end users. Documented as a developer integration reference. Parameters: - `poolId` (`number`): ID of the v4 pool to unlock. Notes: Reverts: "Only vault" if not called from saturnvault. ```js // Not called directly — invoked by saturnvault during collateral release. // Shown for integration reference only. ``` #### V3 Pool Reads ##### `v3BuildPairKey(tokenA: string, tokenB: string): string` — READ Constructs the ordered pair key string "TOKENA_TOKENB" used as the storage key in Saturn DEX v3 pools. Utility for building the keys needed by v3PoolLiquidity and v3PoolReserve. Parameters: - `tokenA` (`string`): First token symbol. - `tokenB` (`string`): Second token symbol. Returns: `string` — Pair key string, e.g. "MKST_RA". ```js const key = await readContract("saturndexadapt", "v3BuildPairKey", ["MKST", "RA"]); // "MKST_RA" ``` ##### `v3PoolLiquidity(pairKey: string): number` — READ Returns the total liquidity units for a v3 pool identified by its ordered pair key. A non-zero result confirms the pool exists and has liquidity. Parameters: - `pairKey` (`string`): Ordered pair key, e.g. "MKST_RA". Returns: `number` — Total liquidity units in the v3 pool; 0 if pool does not exist. ```js const liq = await readContract("saturndexadapt", "v3PoolLiquidity", ["MKST_RA"]); ``` ##### `v3PoolReserve(pairKey: string, tokenSymbol: string): number` — READ Returns the reserve of a specific token inside a v3 pool. Both pairKey and tokenSymbol must be consistent — tokenSymbol must be one of the two tokens in the pair. Parameters: - `pairKey` (`string`): Ordered pair key, e.g. "MKST_RA". - `tokenSymbol` (`string`): The token whose reserve to read; must be in the pair. Returns: `number` — Token reserve in the v3 pool (scaled). ```js const raReserve = await readContract("saturndexadapt", "v3PoolReserve", ["MKST_RA", "RA"]); ``` ##### `v3ResolvePairKey(tokenA: string, tokenB: string): string` — READ Discovers the canonical direction of a v3 pool for a token pair by checking both orderings (TOKENA_TOKENB and TOKENB_TOKENA) and returning whichever has positive liquidity. Returns an empty string if no v3 pool exists for the pair. Use this when you have a token pair but don't know the canonical key ordering. Parameters: - `tokenA` (`string`): First token symbol. - `tokenB` (`string`): Second token symbol. Returns: `string` — Canonical pair key with liquidity, or "" if no v3 pool exists. Notes: Returns "" if neither ordering has positive liquidity. ```js const key = await readContract("saturndexadapt", "v3ResolvePairKey", ["RA", "MKST"]); // "MKST_RA" or "RA_MKST" depending on the canonical direction ``` #### V3 LP NFT Metadata ##### `v3NftPairKey(nftId: number): string` — READ Returns the v3 pool pair key associated with the given LP NFT ID. Use to identify which pool the NFT represents before valuing it as collateral. Parameters: - `nftId` (`number`): On-chain series ID of the v3 LP NFT. Returns: `string` — Pair key string, e.g. "MKST_RA". ```js const pairKey = await readContract("saturndexadapt", "v3NftPairKey", [7]); ``` ##### `v3NftTokenA(nftId: number): string` — READ Returns the symbol of token A recorded in the v3 LP NFT's metadata. Parameters: - `nftId` (`number`): On-chain series ID of the v3 LP NFT. Returns: `string` — Symbol of token A for this LP position. ```js const tokenA = await readContract("saturndexadapt", "v3NftTokenA", [7]); ``` ##### `v3NftTokenB(nftId: number): string` — READ Returns the symbol of token B recorded in the v3 LP NFT's metadata. Parameters: - `nftId` (`number`): On-chain series ID of the v3 LP NFT. Returns: `string` — Symbol of token B for this LP position. ```js const tokenB = await readContract("saturndexadapt", "v3NftTokenB", [7]); ``` ##### `v3NftLiquidity(nftId: number): number` — READ Returns the liquidity units recorded for the given v3 LP NFT. Divide by the pool's total liquidity (v3PoolLiquidity) to get the NFT's pro-rata share of pool reserves. Parameters: - `nftId` (`number`): On-chain series ID of the v3 LP NFT. Returns: `number` — Liquidity units attributed to this NFT position. ```js const nftLiq = await readContract("saturndexadapt", "v3NftLiquidity", [7]); const totalLiq = await readContract("saturndexadapt", "v3PoolLiquidity", ["MKST_RA"]); const share = nftLiq / totalLiq; ``` #### RA-Anchor Pricing ##### `priceInAnchor(tokenSymbol: string, scaledAmount: number, dexVersion: number): number` — READ Returns the scaled anchor-token (RA) equivalent of a given scaled amount of tokenSymbol, using the DEX reserves of the RA-paired pool on the specified DEX version. This is the central pricing primitive: the lending protocol uses it for every loan and collateral valuation. Pass scaled amounts (use scaleUp first). Returns the anchor amount in 8-decimal scaled units. Parameters: - `tokenSymbol` (`string`): Token to price in anchor units. - `scaledAmount` (`number`): Amount of the token in 8-decimal scaled units. - `dexVersion` (`number`): 1 for v3/SATRN, 2 for v4/saturnpools. Returns: `number` — Scaled RA-equivalent value. Notes: Reverts: "Amount must be > 0" if scaledAmount == 0. "No v3/v4 RA pool for " if no pricing pool exists. Returns scaledAmount unchanged if tokenSymbol == anchorToken. ```js // How much RA is 50 MKST worth on v4? const scaledMkst = await readContract("saturndexadapt", "scaleUp", [50_000_000_000, "MKST"]); const raValue = await readContract("saturndexadapt", "priceInAnchor", [scaledMkst, "MKST", 2]); // raValue is in 8-dec scaled RA units ``` ##### `priceInToken(fromToken: string, scaledAmount: number, fromDex: number, toToken: string, toDex: number): number` — READ Cross-token pricing: converts a scaled amount of fromToken (priced on fromDex) into the equivalent scaled value denominated in toToken (priced on toDex), routing through RA as an intermediate anchor. Use this for cross-DEX or cross-token collateral comparisons (e.g., compare v3 LP collateral to a v4 loan denomination). Parameters: - `fromToken` (`string`): Source token symbol. - `scaledAmount` (`number`): Amount of the source token in 8-decimal scaled units. - `fromDex` (`number`): DEX version for the source token's RA pool (1 = v3, 2 = v4). - `toToken` (`string`): Destination token symbol. - `toDex` (`number`): DEX version for the destination token's RA pool (1 = v3, 2 = v4). Returns: `number` — Scaled equivalent in toToken units. Notes: Reverts if either token lacks an RA-paired pool on its specified DEX. Reverts if scaledAmount == 0. ```js // How many USDC (v4) is 100 MKST (v4) worth? const scaledMkst = await readContract("saturndexadapt", "scaleUp", [100_000_000_000, "MKST"]); const usdcEquiv = await readContract("saturndexadapt", "priceInToken", ["MKST", scaledMkst, 2, "USDC", 2]); ``` #### LP Collateral Valuation ##### `v4PoolValueInBase(poolId: number, baseToken: string, baseDex: number): number` — READ Values the entire reserve of a v4 LP pool in terms of a base token, routed through RA. Sums priceInToken(tokenA, resA, 2, baseToken, baseDex) + priceInToken(tokenB, resB, 2, baseToken, baseDex). This is the headline collateral value used by saturnvault when evaluating a v4 LP deposit. Parameters: - `poolId` (`number`): Numeric v4 pool ID. - `baseToken` (`string`): Token to denominate the result in (e.g. "RA" or the loan token). - `baseDex` (`number`): DEX version for the base token's RA pool (1 = v3, 2 = v4). Returns: `number` — Total pool value in scaled baseToken units. Notes: Reverts: "v4 pool not RA-paired" if neither token in the pool is the anchor token. ```js // Full collateral value of v4 pool #42 denominated in RA const value = await readContract("saturndexadapt", "v4PoolValueInBase", [42, "RA", 2]); // Scaled 8-dec RA units ``` ##### `v3LpNftValueInBase(nftId: number, baseToken: string, baseDex: number): number` — READ Values a v3 LP NFT position in terms of a base token. Computes the NFT's pro-rata share of pool reserves (nftLiquidity / totalLiquidity) then prices both token portions through RA into baseToken. This is the collateral value used by saturnvault for v3 LP NFT deposits. Parameters: - `nftId` (`number`): On-chain series ID of the v3 LP NFT. - `baseToken` (`string`): Token to denominate the result in (e.g. "RA" or the loan token). - `baseDex` (`number`): DEX version for the base token's RA pool (1 = v3, 2 = v4). Returns: `number` — NFT position value in scaled baseToken units. Notes: Reverts: "v3 LP NFT not RA-paired" if the NFT's pair doesn't include the anchor. "v3 pool has no liquidity" or "v3 NFT has no liquidity" if either is zero. ```js // Value of v3 LP NFT #7 denominated in RA (v4 RA pool) const nftVal = await readContract("saturndexadapt", "v3LpNftValueInBase", [7, "RA", 2]); ``` ### SaturnTaz — `saturntaz` *TAZ Rewards & RA Pledging* — Saturn DEX v4 · Lending Protocol Awards TAZ governance tokens on every fully-repaid loan, split 30% to the borrower, 30% to the lender, and 40% to the lender's active RA pledgers (v1.1 anti-abuse formula). RA owners pledge to specific lenders to curate the lender set and earn a share of their loan rewards; pledges are fixed 30-day windows and can be custodial (v3, RA transferred in) or non-custodial (v4, RA locked in saturnholders). The reward formula is fully bounded — every input has a cap and a floor — so emission is predictable. An annual deposit cap gates TAZ inflows into the treasury regardless of how many authorized depositors exist. #### Treasury — Deposits & Balance ##### `depositTaz(from: address, amount: number)` — WRITE Deposits TAZ from an authorized treasury wallet into the contract's reward pool. The caller must be on the authorized-depositor allowlist (added by the owner). Enforces a rolling 365-day emission cap: all authorized depositors combined cannot exceed yearlyDepositCap TAZ in a single annual window. If the current window has expired it is silently rolled forward at deposit time. Parameters: - `from` (`address`): Authorized depositor address (must be witness and on the allowlist). - `amount` (`number`): Raw TAZ amount to deposit (9-decimal). Notes: Reverts: "Wallet not registered as treasury depositor" if from is not on the allowlist. "Insufficient TAZ balance" if from lacks enough TAZ. "Yearly deposit cap exceeded" if amount would breach the annual cap. ```js const tx = sb.begin() .allowGas(treasury) .callContract("saturntaz", "depositTaz", [treasury, 10_000_000_000_000n]) .spendGas(treasury) .endScript(); ``` ##### `getTazBalance(): number` — READ Returns the current TAZ balance held by this contract — the reward treasury available for distribution. Monitor this to alert when the treasury needs topping up. Parameters: none Returns: `number` — Raw TAZ balance (9-decimal) held in the contract. ```js const bal = await readContract("saturntaz", "getTazBalance", []); ``` ##### `getTazSymbol(): string` — READ Returns the TAZ token symbol used by this contract (default: "TAZ"). Check this if you need to query TAZ token metadata or display the token symbol dynamically. Parameters: none Returns: `string` — Current TAZ token symbol. ```js const sym = await readContract("saturntaz", "getTazSymbol", []); ``` ##### `getAuthorizedDepositor(wallet: address): number` — READ Returns 1 if the wallet is authorized to deposit TAZ into the treasury, 0 if not. Use to check allowlist status before attempting a depositTaz call. Parameters: - `wallet` (`address`): Address to check. Returns: `number` — 1 if authorized, 0 if not. ```js const ok = await readContract("saturntaz", "getAuthorizedDepositor", [walletAddr]); ``` #### Annual Deposit Cap Views ##### `getYearlyDepositCap(): number` — READ Returns the maximum raw TAZ that all authorized depositors may collectively deposit in a single 365-day window. Default is 40,000 TAZ (40,000 × 10^9 raw). Parameters: none Returns: `number` — Yearly TAZ deposit cap in raw 9-decimal units. ```js const cap = await readContract("saturntaz", "getYearlyDepositCap", []); ``` ##### `getYearlyDurationSeconds(): number` — READ Returns the length of one deposit-cap window in seconds (default: 31,536,000 = 365 days). Parameters: none Returns: `number` — Window duration in seconds. ```js const dur = await readContract("saturntaz", "getYearlyDurationSeconds", []); ``` ##### `getYearStartTime(): number` — READ Returns the unix timestamp at which the current annual deposit window started. Pair with getYearlyDurationSeconds to compute when the window resets. Parameters: none Returns: `number` — Unix timestamp (seconds) of the current window start. ```js const start = await readContract("saturntaz", "getYearStartTime", []); const end = start + await readContract("saturntaz", "getYearlyDurationSeconds", []); ``` ##### `getCurrentYearDeposited(): number` — READ Returns how much TAZ has been deposited by all authorized depositors in the current annual window so far. Parameters: none Returns: `number` — Raw TAZ deposited in the current window. ```js const used = await readContract("saturntaz", "getCurrentYearDeposited", []); ``` ##### `getYearlyRemaining(): number` — READ Convenience view: returns the remaining TAZ deposit budget in the current annual window. If the window has expired, returns the full cap (a fresh window starts on the next deposit). Use this to show treasury operators how much headroom is left. Parameters: none Returns: `number` — Remaining raw TAZ that may still be deposited in this window. Notes: Returns yearlyDepositCap if the window has expired. Returns 0 if the cap is already exhausted. Never reverts. ```js const remaining = await readContract("saturntaz", "getYearlyRemaining", []); // UI: "You may deposit up to X TAZ this year" ``` #### Pledge Configuration Views ##### `getPledgeDurationSeconds(): number` — READ Returns the fixed pledge window length in seconds (default: 2,592,000 = 30 days). A pledge cannot be withdrawn until this many seconds have elapsed since it was created. Parameters: none Returns: `number` — Pledge lock duration in seconds. ```js const secs = await readContract("saturntaz", "getPledgeDurationSeconds", []); const days = secs / 86400; // 30 ``` ##### `getMaxPledgersPerLender(): number` — READ Returns the maximum number of active pledgers a single lender may have at once. Once reached, new pledgers are blocked until an existing pledge expires and its slot is freed. Parameters: none Returns: `number` — Hard cap on simultaneous pledgers per lender (default: 30). ```js const cap = await readContract("saturntaz", "getMaxPledgersPerLender", []); ``` #### Reward Configuration Views ##### `getRewardParam(key: string): number` — READ Returns a single reward formula parameter by key. Valid keys: "baseReward" (max TAZ raw per loan when all factors at cap), "loanCapRA" (SCALED RA saturation cap), "durationCapDays" (days saturation cap), "pledgeCapRA" (SCALED RA pledge saturation cap), "minLoanRA" (floor — loans below this earn zero), "minDurationDays" (floor — loans shorter than this earn zero), "minPledgeRA" (floor — lenders with less eligible pledge earn zero), "maxRewardPerLoan" (hard TAZ ceiling per loan), "borrowerBps" (basis points for borrower), "lenderBps" (basis points for lender), "pledgerBps" (basis points for pledger pool). Parameters: - `key` (`string`): Parameter key (see description for valid keys). Returns: `number` — Current value of the requested reward parameter. ```js const base = await readContract("saturntaz", "getRewardParam", ["baseReward"]); // e.g. 30_000_000_000 (30 TAZ raw) const borrowerBps = await readContract("saturntaz", "getRewardParam", ["borrowerBps"]); // 3000 (30%) ``` #### RA Pledging — V3 (Custodial) ##### `pledgeV3(from: address, lender: address, amount: number)` — WRITE Custodial RA pledge: transfers amount of RA from the caller into this contract's custody and registers the pledge against lender for a fixed 30-day window. During the window the pledger is eligible to receive a share of TAZ from every loan the lender fully repays. Cannot pledge to yourself. Any previous pledge to the same lender must be withdrawn (unpledgeV3) before re-pledging. Parameters: - `from` (`address`): Pledger address (must be witness). - `lender` (`address`): Target lender address to pledge RA to. - `amount` (`number`): Raw RA amount to pledge (native RA decimals, typically 9-dec). Notes: Reverts: "Cannot pledge to yourself". "Existing pledge to this lender is still active - wait until expiry" if expiry > now. "Existing v3/v4 pledge must be withdrawn before re-pledging" if prior pledge balance remains. "Pledge amount rounds to zero" if amount is too small to survive scaleUp. "Lender pledger cap reached" if lender already has maxPledgersPerLender active pledgers. ```js const tx = sb.begin() .allowGas(pledger) .callContract("saturntaz", "pledgeV3", [pledger, lenderAddr, 5_000_000_000n]) .spendGas(pledger) .endScript(); ``` ##### `unpledgeV3(from: address, lender: address)` — WRITE Withdraws a custodial v3 RA pledge after the 30-day window expires. Returns the originally-pledged RA to the caller (amount is the stored scaled amount converted back to raw). Will revert if the pledge has not yet expired — pledges are time-locked. Parameters: - `from` (`address`): Pledger address (must be witness). - `lender` (`address`): Lender address the pledge was registered against. Notes: Reverts: "No v3 pledge to this lender" if no pledge exists. "Pledge still active - wait until expiry" if now < pledgeExpiry. ```js const tx = sb.begin() .allowGas(pledger) .callContract("saturntaz", "unpledgeV3", [pledger, lenderAddr]) .spendGas(pledger) .endScript(); ``` #### RA Pledging — V4 (Non-Custodial) ##### `pledgeV4(from: address, lender: address, amount: number)` — WRITE Non-custodial RA pledge: locks amount of RA in saturnholders via lockForPledge (the RA stays in the staking contract but cannot be unstaked while the pledge is active). Registers the pledge against lender for a fixed 30-day window. Requires that saturntaz is on saturnholders' lockForPledge allowlist. Cannot pledge to yourself; prior pledge must be cleared first. Parameters: - `from` (`address`): Pledger address (must be witness and have sufficient unlocked RA stake in saturnholders). - `lender` (`address`): Target lender address to pledge to. - `amount` (`number`): Raw RA amount to lock (native RA decimals). Notes: Reverts: "Cannot pledge to yourself". "Existing pledge to this lender is still active" if expiry > now. "Existing v3/v4 pledge must be withdrawn before re-pledging" if prior balance remains. saturnholders.lockForPledge reverts if from lacks sufficient unlocked stake. "Lender pledger cap reached" if the lender's pledger list is full. ```js const tx = sb.begin() .allowGas(pledger) .callContract("saturntaz", "pledgeV4", [pledger, lenderAddr, 5_000_000_000n]) .spendGas(pledger) .endScript(); ``` ##### `unpledgeV4(from: address, lender: address)` — WRITE Releases a non-custodial v4 RA pledge after the 30-day window expires. Calls saturnholders.unlockPledge to restore the RA to its normal unlocked-stake state. Will revert if the pledge has not yet expired. Parameters: - `from` (`address`): Pledger address (must be witness). - `lender` (`address`): Lender address the pledge was registered against. Notes: Reverts: "No v4 pledge to this lender" if no pledge exists. "Pledge still active - wait until expiry" if now < pledgeExpiry. ```js const tx = sb.begin() .allowGas(pledger) .callContract("saturntaz", "unpledgeV4", [pledger, lenderAddr]) .spendGas(pledger) .endScript(); ``` #### TAZ Reward Claims ##### `claimPledgeRewards(from: address)` — WRITE Transfers all accumulated TAZ rewards to the pledger. Pledger rewards accrue in pledgerClaimable[from] each time a lender's loan is fully repaid; call this to collect them. Borrower and lender rewards are paid out automatically at repayment time and do not require a separate claim. Parameters: - `from` (`address`): Pledger address claiming accumulated TAZ (must be witness). Notes: Reverts: "Nothing to claim" if pledgerClaimable[from] == 0. "Treasury short - try again later or contact admin" if the contract's TAZ balance is insufficient (should be rare if the treasury is properly funded). ```js const tx = sb.begin() .allowGas(pledger) .callContract("saturntaz", "claimPledgeRewards", [pledger]) .spendGas(pledger) .endScript(); ``` ##### `getPledgerClaimable(pledger: address): number` — READ Returns the accumulated unclaimed TAZ balance owed to a pledger. Use this to show a "Claimable rewards" figure in your UI before the pledger submits a claimPledgeRewards transaction. Parameters: - `pledger` (`address`): Pledger address to check. Returns: `number` — Raw TAZ (9-decimal) ready to claim. ```js const pending = await readContract("saturntaz", "getPledgerClaimable", [pledgerAddr]); // Show "Claimable: X TAZ" in the UI ``` #### Reward Preview ##### `previewLoanReward(loanValueInRA: number, durationDays: number, lender: address): number` — READ Simulates the total TAZ reward a loan would generate (before the 30/30/40 split) given the loan parameters and the lender's current active pledge. All anti-abuse formula caps and floors are applied. Returns 0 if any floor is not met. Use this in a loan quoting UI to show borrowers and lenders how much TAZ they stand to earn. Parameters: - `loanValueInRA` (`number`): Loan value in scaled (8-dec) RA units (as returned by saturndexadapt.priceInAnchor). - `durationDays` (`number`): Loan duration in days. - `lender` (`address`): Lender address whose current active pledge will be used in the formula. Returns: `number` — Total raw TAZ reward (9-decimal) that would be distributed on full repayment. Notes: Returns 0 if loanValueInRA < minLoanRA, durationDays < minDurationDays, or eligible pledge < minPledgeRA. Never reverts. ```js // Preview reward for a 30-day loan worth 50 RA const loanRA = await readContract("saturndexadapt", "scaleUp", [50_000_000_000, "RA"]); const reward = await readContract("saturntaz", "previewLoanReward", [loanRA, 30, lenderAddr]); const borrowerShare = reward * 0.30; const lenderShare = reward * 0.30; const pledgerShare = reward * 0.40; ``` #### Pledge State Views ##### `getPledgeScaledV3(pledger: address, lender: address): number` — READ Returns the scaled (8-decimal) RA amount of a pledger's active v3 custodial pledge to a specific lender. Returns 0 if no pledge exists. Parameters: - `pledger` (`address`): Pledger address. - `lender` (`address`): Lender address. Returns: `number` — Scaled RA amount (8-dec) of the v3 pledge; 0 if none. ```js const scaled = await readContract("saturntaz", "getPledgeScaledV3", [pledgerAddr, lenderAddr]); ``` ##### `getPledgeScaledV4(pledger: address, lender: address): number` — READ Returns the scaled (8-decimal) RA amount of a pledger's active v4 non-custodial pledge to a specific lender. Returns 0 if no pledge exists. Parameters: - `pledger` (`address`): Pledger address. - `lender` (`address`): Lender address. Returns: `number` — Scaled RA amount (8-dec) of the v4 pledge; 0 if none. ```js const scaled = await readContract("saturntaz", "getPledgeScaledV4", [pledgerAddr, lenderAddr]); ``` ##### `getPledgeExpiry(pledger: address, lender: address): number` — READ Returns the unix timestamp at which the pledge (v3 or v4) expires and becomes withdrawable. Returns 0 if no pledge has been made. Parameters: - `pledger` (`address`): Pledger address. - `lender` (`address`): Lender address. Returns: `number` — Unix expiry timestamp (seconds); 0 if no pledge. ```js const expiry = await readContract("saturntaz", "getPledgeExpiry", [pledgerAddr, lenderAddr]); const canWithdraw = Date.now() / 1000 >= expiry; ``` ##### `getPledgeRawV3(pledger: address, lender: address): number` — READ Returns the raw (native-decimal) RA amount of a v3 custodial pledge by converting the stored scaled value back via scaleDownRA. Useful for displaying the pledge amount to users in familiar token units. Parameters: - `pledger` (`address`): Pledger address. - `lender` (`address`): Lender address. Returns: `number` — Raw RA amount (native decimals) of the v3 pledge; 0 if none. ```js const rawRA = await readContract("saturntaz", "getPledgeRawV3", [pledgerAddr, lenderAddr]); ``` ##### `getPledgeRawV4(pledger: address, lender: address): number` — READ Returns the raw (native-decimal) RA amount of a v4 non-custodial pledge by converting the stored scaled value back via scaleDownRA. Parameters: - `pledger` (`address`): Pledger address. - `lender` (`address`): Lender address. Returns: `number` — Raw RA amount (native decimals) of the v4 pledge; 0 if none. ```js const rawRA = await readContract("saturntaz", "getPledgeRawV4", [pledgerAddr, lenderAddr]); ``` ##### `getLenderPledgerCount(lender: address): number` — READ Returns the current pledger list high-water mark for a lender (the highest slot index ever used, minus reclaimed gaps). Use alongside getLenderPledgerAt to enumerate all pledgers. Parameters: - `lender` (`address`): Lender address. Returns: `number` — High-water index; iterate 1..N to enumerate pledger slots. ```js const n = await readContract("saturntaz", "getLenderPledgerCount", [lenderAddr]); for (let i = 1; i <= n; i++) { const p = await readContract("saturntaz", "getLenderPledgerAt", [lenderAddr, i]); if (p) console.log("pledger:", p); } ``` ##### `getLenderPledgerAt(lender: address, index: number): address` — READ Returns the pledger address at a specific 1-indexed slot in a lender's pledger list. Freed slots return null/zero address. Use in combination with getLenderPledgerCount to enumerate the full pledger set. Parameters: - `lender` (`address`): Lender address. - `index` (`number`): 1-indexed slot number (1 to getLenderPledgerCount). Returns: `address` — Pledger address at this slot, or null address if the slot was freed. ```js const pledger = await readContract("saturntaz", "getLenderPledgerAt", [lenderAddr, 1]); ``` ##### `getLenderEligiblePledge(lender: address): number` — READ Returns the total scaled RA amount of all currently-eligible (non-expired) pledges backing a lender. This is exactly the value used by the reward formula at distribution time. Useful for showing lenders how much RA backing they have and for computing previewLoanReward inputs. Parameters: - `lender` (`address`): Lender address to evaluate. Returns: `number` — Total scaled (8-dec) RA across all active, non-expired pledgers. Notes: Returns 0 if lender has no pledgers or all pledges have expired. ```js const eligRA = await readContract("saturntaz", "getLenderEligiblePledge", [lenderAddr]); // Compare against getRewardParam("minPledgeRA") to confirm lender qualifies ``` #### Loan Snapshot Views ##### `getLoanRegistered(loanId: number): number` — READ Returns 1 if the loan was registered with this contract at creation (via onLoanCreated hook from saturnloans), 0 if not. A loan must be registered for rewards to be distributed on repayment. Parameters: - `loanId` (`number`): Loan ID as assigned by saturnloans. Returns: `number` — 1 if registered, 0 if not. ```js const reg = await readContract("saturntaz", "getLoanRegistered", [loanId]); ``` ##### `getLoanRewardPaid(loanId: number): number` — READ Returns 1 if a TAZ reward has already been distributed for this loan, 0 if not yet paid. Rewards are idempotent — a loan can only be rewarded once. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — 1 if reward distributed, 0 if pending or ineligible. ```js const paid = await readContract("saturntaz", "getLoanRewardPaid", [loanId]); ``` ##### `getLoanValueRA(loanId: number): number` — READ Returns the scaled (8-decimal) RA value of the loan, snapshotted at creation time by saturnloans. This is the loanValueInRA input used in the reward formula, not the live DEX price. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Scaled RA value of the loan at creation (8-dec). ```js const raVal = await readContract("saturntaz", "getLoanValueRA", [loanId]); ``` ##### `getLoanDurationDays(loanId: number): number` — READ Returns the duration (in days) of the loan, snapshotted at creation time. This is the durationDays input used in the reward formula. Parameters: - `loanId` (`number`): Loan ID. Returns: `number` — Loan duration in days as recorded at creation. ```js const days = await readContract("saturntaz", "getLoanDurationDays", [loanId]); ``` #### Protocol Statistics ##### `getTotalTazPaid(): number` — READ Returns the cumulative raw TAZ paid out as rewards since contract deployment (borrower + lender + pledger shares combined). Parameters: none Returns: `number` — Total raw TAZ (9-decimal) distributed as rewards. ```js const paid = await readContract("saturntaz", "getTotalTazPaid", []); ``` ##### `getTotalTazDeposited(): number` — READ Returns the cumulative raw TAZ deposited into the treasury via depositTaz since contract deployment. Parameters: none Returns: `number` — Total raw TAZ (9-decimal) ever deposited. ```js const deposited = await readContract("saturntaz", "getTotalTazDeposited", []); ``` ##### `getTotalLoansRewarded(): number` — READ Returns the number of loans for which a TAZ reward distribution was triggered (i.e., fully-repaid loans that passed all formula floors). Parameters: none Returns: `number` — Count of loans that have received a TAZ reward. ```js const rewarded = await readContract("saturntaz", "getTotalLoansRewarded", []); ``` ##### `getTotalLoansResolved(): number` — READ Returns the total number of loans resolved (both full repayments and defaults/liquidations) that were registered with this contract. Parameters: none Returns: `number` — Count of all resolved registered loans. ```js const resolved = await readContract("saturntaz", "getTotalLoansResolved", []); ``` # Saturn DEX v3 ### Saturn DEX v3 (SATRN) — `SATRN` *Monolithic v3 AMM — pools, swaps, liquidity NFTs, rewards* — Saturn DEX v3 The legacy Saturn DEX, still live on Phantasma mainnet as a single self-contained contract (SATRN). All DEX state — pools, reserves, LP positions — lives here. Pools are identified by string keys of the form "TOKEN0_TOKEN1". Liquidity positions are represented as non-fungible SATRN NFTs; each NFT carries the pool key and the exact liquidity-units it represents, making LP shares portable and composable — the saturnloans lending protocol accepts these SATRN LP NFTs as collateral. Swap fees total 0.4%: 0.21% to LPs, 0.09% to the admin, and 0.1% to the output-token owner (or a configurable wallet for SOUL/KCAL). Series-5 FACTORY NFT holders receive an 80% discount on all three fee slices. #### Pool Info & Reserves ##### `getTokensInDEXList(): string*` — READ Yields every token symbol that has been deposited into at least one pool. Use this to enumerate all tokens traded on v3 without knowing symbol names in advance. Parameters: none Returns: `string*` — Iterator of token symbol strings (e.g. "SOUL", "KCAL", "CROWN"). Notes: Returns an empty iterator when no pools exist. Each symbol appears at most once. ```js const script = sb.begin() .callContract("SATRN", "getTokensInDEXList", []) .endScript(); const res = await api.invokeRawScript("main", script); const tokens = res.decoded; // string[] ``` ##### `getPairsInDEXList(): string*` — READ Yields all pool keys in canonical "TOKEN0_TOKEN1" format. A pool key is the string used everywhere else in the SATRN API to identify a specific pair. Parameters: none Returns: `string*` — Iterator of pool key strings, e.g. "SOUL_KCAL". Notes: Returns an empty iterator if no pools have been created. The key orientation matches the order the pool was initialised with. ```js const script = sb.begin() .callContract("SATRN", "getPairsInDEXList", []) .endScript(); const pairs = (await api.invokeRawScript("main", script)).decoded; ``` ##### `getPoolsAndReservesInDEXList(): string*` — READ Yields all reserve-lookup keys in the form "POOL_KEY_TOKEN". Each pool produces two keys: one per token side. Pass a key to getTokenPairAndReserveKeysOnListVALUE() to retrieve the scaled reserve amount. Parameters: none Returns: `string*` — Iterator of reserve keys, e.g. "SOUL_KCAL_SOUL" and "SOUL_KCAL_KCAL". ```js const keys = (await api.invokeRawScript("main", sb.begin().callContract("SATRN","getPoolsAndReservesInDEXList",[]).endScript() )).decoded; ``` ##### `getAllReserves(): number*` — READ Yields the global scaled reserve for every token in the DEX, in the same order as getTokensInDEXList(). Values are scaled to 8 decimal places internally; divide by the token's scale factor to get real units. Parameters: none Returns: `number*` — Iterator of scaled reserve totals, one per token. ```js const reserves = (await api.invokeRawScript("main", sb.begin().callContract("SATRN","getAllReserves",[]).endScript() )).decoded; // number[] ``` ##### `getReserveValue(tokenSymbol: string): number` — READ Returns the global scaled reserve total for a single token across all pools. Useful for quickly checking whether a token has any liquidity in the DEX. Parameters: - `tokenSymbol` (`string`): Token symbol, e.g. "SOUL". Returns: `number` — Scaled reserve (8 dp internally). Returns 0 when the token is not in any pool. ```js const r = await readContract("SATRN", "getReserveValue", ["SOUL"]); ``` ##### `getTokenOnList(select: number): string` — READ Returns the symbol at index `select` in the tokens-in-DEX list. Combine with getCountOfTokensOnList() for paginated enumeration. Parameters: - `select` (`number`): Zero-based index. Returns: `string` — Token symbol at that index. Notes: Reverts if select >= count. ```js const first = await readContract("SATRN", "getTokenOnList", [0]); ``` ##### `getCountOfTokensOnList(): number` — READ Returns the number of distinct token symbols that have ever been deposited into any pool. Use this as the loop bound for getTokenOnList(). Parameters: none Returns: `number` — Count of tokens tracked by the DEX. ```js const n = await readContract("SATRN", "getCountOfTokensOnList", []); ``` ##### `getCountOfTokenPairsOnList(): number` — READ Returns the total number of pools that have been created. Use as the loop bound for getPairsInDEXList() pagination. Parameters: none Returns: `number` — Number of pools in the DEX. ```js const poolCount = await readContract("SATRN", "getCountOfTokenPairsOnList", []); ``` ##### `getCountOfTokenPairsAndReserveKeysOnList(): number` — READ Returns the total number of reserve keys (two per pool). Use as the loop bound for getTokenPairAndReserveKeysOnList(). Parameters: none Returns: `number` — Total reserve-key entries (2 × pool count). ```js const n = await readContract("SATRN", "getCountOfTokenPairsAndReserveKeysOnList", []); ``` ##### `getTokenPairAndReserveKeysOnList(select: number): string` — READ Returns the reserve key at index `select`, e.g. "SOUL_KCAL_SOUL". Feed this string directly to getTokenPairAndReserveKeysOnListVALUE() to retrieve the scaled reserve. Parameters: - `select` (`number`): Zero-based index into the reserve-keys list. Returns: `string` — Reserve key string. Notes: Reverts if select >= count. ```js const key = await readContract("SATRN", "getTokenPairAndReserveKeysOnList", [0]); ``` ##### `getTokenPairAndReserveKeysOnListVALUE(pairKeyPlusTokenToViewBalance: string): number` — READ Given a reserve key of the form "POOL_KEY_TOKEN" (e.g. "SOUL_KCAL_SOUL"), returns the scaled reserve amount for that token side. Divide by the token's scale factor (getScaleFactor) to recover real units. Parameters: - `pairKeyPlusTokenToViewBalance` (`string`): Composite key "poolKey_tokenSymbol", e.g. "SOUL_KCAL_SOUL". Returns: `number` — Scaled reserve amount. Returns 0 if the key is unknown. ```js const scaledReserve = await readContract( "SATRN", "getTokenPairAndReserveKeysOnListVALUE", ["SOUL_KCAL_SOUL"] ); ``` ##### `getAllContractBalances(): number*` — READ Yields the raw on-chain balance held by the SATRN contract for each tracked token, in the same order as getTokensInDEXList(). The balance may exceed scaled reserves due to accrued storage fees or donated amounts. Parameters: none Returns: `number*` — Iterator of raw token balances held by the contract. ```js const balances = (await api.invokeRawScript("main", sb.begin().callContract("SATRN","getAllContractBalances",[]).endScript() )).decoded; ``` ##### `getContractTokenBalanceEach(tokenSymbol: string): number` — READ Returns the raw balance of a specific token held by the SATRN contract. Subtract the unscaled reserve to find skimmable surplus. Parameters: - `tokenSymbol` (`string`): Token symbol to query. Returns: `number` — Raw token balance (native decimals) held by the contract. ```js const bal = await readContract("SATRN", "getContractTokenBalanceEach", ["KCAL"]); ``` ##### `getAllTotalLiquidityPerPool(): string*` — READ Yields one string per pool, formatted as "POOL_KEY_totalLiquidity". Convenient for a dashboard that needs to show pool depth without iterating reserves separately. Parameters: none Returns: `string*` — Strings like "SOUL_KCAL_4831920". Total liquidity is in LP units. ```js const poolDepths = (await api.invokeRawScript("main", sb.begin().callContract("SATRN","getAllTotalLiquidityPerPool",[]).endScript() )).decoded; ``` ##### `getLiquidityPerPoolSaturn(pairKey: string): number` — READ Returns the total LP liquidity units for a given pool key. This is the denominator used when computing a position's share of reserves on remove. Parameters: - `pairKey` (`string`): Pool key, e.g. "SOUL_KCAL". Returns: `number` — Total LP units in the pool. Returns 0 if the pool does not exist. ```js const lp = await readContract("SATRN", "getLiquidityPerPoolSaturn", ["SOUL_KCAL"]); ``` ##### `getUserLiquidityInPool(from: address, poolKey: string): number` — READ Sums the LP units across every SATRN NFT owned by `from` that belongs to `poolKey`. Use this to show a user their total share in a specific pool before they decide to remove or consolidate. Parameters: - `from` (`address`): Wallet address to query. - `poolKey` (`string`): Pool key, e.g. "SOUL_KCAL". Returns: `number` — Aggregate LP units held by the user in that pool. Returns 0 if they have none. ```js const myLP = await readContract( "SATRN", "getUserLiquidityInPool", ["P2K..myWallet..", "SOUL_KCAL"] ); ``` #### Swaps ##### `swap(from: address, amountIn: number, tokenIn: string, tokenOut: string, minAmountOut: number): number` — WRITE Executes a constant-product AMM swap of `amountIn` of `tokenIn` for `tokenOut`. The total fee is 0.4%: 0.21% stays in the pool, 0.09% goes to the admin, and 0.1% goes to the output-token owner (for SOUL/KCAL, where there is no on-chain token owner, this slice is redirected to the configured chain-token fee wallet). Holders of a Series-5 FACTORY NFT pay only 0.08% total (5× discount applied to all three slices). Pass `minAmountOut > 0` to enforce a slippage guard; the transaction reverts if the actual output is below that floor. Returns the real raw amount of `tokenOut` delivered to `from`. Parameters: - `from` (`address`): Caller and signing witness; both payer and recipient. - `amountIn` (`number`): Raw input amount in `tokenIn` native units. - `tokenIn` (`string`): Symbol of the token being sold. - `tokenOut` (`string`): Symbol of the token being bought. - `minAmountOut` (`number`): Minimum acceptable output. Pass 0 to disable the slippage guard (not recommended). Returns: `number` — Actual raw amount of `tokenOut` transferred to `from`. Notes: Reverts if: pool does not exist; pool has no liquidity; amountIn is below token-scaled minimum (see getMinRawForSwap); output would round to zero; admin fee rounds to zero; token-owner fee rounds to zero; slippage guard fails (realAmountOut < minAmountOut); reentrancy guard is set for `from`. ```js // Swap 1 SOUL for KCAL, accept at least 50_000 KCAL raw const from = "P2K..myWallet.."; const tx = sb.begin() .allowGas(from, sb.nullAddress, gasPrice, gasLimit) .callContract("SATRN", "swap", [from, 100000000, "SOUL", "KCAL", 50000]) .spendGas(from) .endScript(); const hash = await wallet.sendTransaction(tx, "main"); ``` #### Liquidity (v2) ##### `initializePool(from: address, amountToken0: number, amountToken1: number, token0Symbol: string, token1Symbol: string): bool` — WRITE Creates a brand-new liquidity pool for the `token0Symbol`/`token1Symbol` pair. Both tokens must not already share a pool (in either orientation). The initial price is set by the ratio `amountToken0 / amountToken1`. A SATRN NFT representing the initial LP position is minted to `from`. The pool key will be `token0Symbol_token1Symbol`. Returns `true` on success. Parameters: - `from` (`address`): Creator and signing witness; must hold sufficient balances of both tokens. - `amountToken0` (`number`): Raw amount of token0 to seed the pool. - `amountToken1` (`number`): Raw amount of token1 to seed the pool. - `token0Symbol` (`string`): Symbol of the first token. - `token1Symbol` (`string`): Symbol of the second token. Returns: `bool` — Always true on success (reverts on any failure). Notes: Reverts if: pool already exists; either token does not exist on-chain; amounts are below their scaled minimums (see getMinRawForPoolCreation); caller has insufficient balance; token0Symbol == token1Symbol; reentrancy guard is set for `from`. ```js const from = "P2K..myWallet.."; const tx = sb.begin() .allowGas(from, sb.nullAddress, gasPrice, gasLimit) .callContract("SATRN", "initializePool", [from, 100000000, 500000000, "SOUL", "KCAL"]) .spendGas(from) .endScript(); await wallet.sendTransaction(tx, "main"); ``` ##### `addLiquidity_v2(from: address, amountFirstToken: number, tokenFirst: string, tokenSecond: string, maxSecondTokenAmount: number): bool` — WRITE Adds liquidity to an existing pool. You specify how much of `tokenFirst` to deposit; the contract calculates the proportional amount of `tokenSecond` required to maintain the current price. If that computed second-token amount exceeds `maxSecondTokenAmount`, the transaction reverts — use this as your slippage cap on the second token. A new SATRN NFT is minted representing the LP units added. Returns `true` on success. Parameters: - `from` (`address`): Caller and signing witness; must hold the required balances. - `amountFirstToken` (`number`): Raw amount of `tokenFirst` to deposit. - `tokenFirst` (`string`): Symbol of the token you are specifying the deposit amount for. - `tokenSecond` (`string`): Symbol of the paired token (computed amount). - `maxSecondTokenAmount` (`number`): Maximum raw units of `tokenSecond` you are willing to pay. Acts as a slippage guard. Returns: `bool` — Always true on success. Notes: Reverts if: pool does not exist or has no liquidity; amountFirstToken is below scaled minimum (see getMinRawForAddLiquidity); computed second-token amount exceeds maxSecondTokenAmount; caller lacks balance; liquidityTokens minted would be 0; reentrancy guard is set for `from`. ```js const from = "P2K..myWallet.."; // Pre-check: const required = await readContract("SATRN","getMinRawForAddLiquidity",["SOUL"]); const tx = sb.begin() .allowGas(from, sb.nullAddress, gasPrice, gasLimit) .callContract("SATRN", "addLiquidity_v2", [from, 50000000, "SOUL", "KCAL", 300000000]) .spendGas(from) .endScript(); await wallet.sendTransaction(tx, "main"); ``` ##### `removeLiquidity(from: address, NFTuniqueID: number): bool` — WRITE Burns a SATRN LP NFT and returns the proportional share of pool reserves to `from`. The returned amounts are computed as `(nftLiquidity / poolTotalLiquidity) × reserveForToken`, then scaled back to raw units. If the scale-down rounding would lose more than `maxTruncationPercent` of either side, the transaction reverts with a "consolidate first" message — use consolidatePositions() to merge small NFTs before removing. TACAL reward claims are no longer performed automatically here; claim TACAL separately if needed. Returns `true` on success. Parameters: - `from` (`address`): Caller and signing witness; must own the NFT. - `NFTuniqueID` (`number`): The unique numeric ID of the SATRN LP NFT to burn. Returns: `bool` — Always true on success. Notes: Reverts if: `from` does not own the NFT; pool has no liquidity; scaled share rounds to zero for either token; truncation exceeds maxTruncationPercent (10% default); reentrancy guard is set for `from`. ```js const nftId = 42; // from getUserLiquidityPools_v2 const tx = sb.begin() .allowGas(from, sb.nullAddress, gasPrice, gasLimit) .callContract("SATRN", "removeLiquidity", [from, nftId]) .spendGas(from) .endScript(); await wallet.sendTransaction(tx, "main"); ``` ##### `consolidatePositions(from: address, poolKey: string): bool` — WRITE Merges all SATRN LP NFTs owned by `from` in `poolKey` into a single new NFT. Requires at least two matching NFTs. This is the standard fix for "truncation too high" errors on removeLiquidity — a single large NFT always has enough LP units to survive the scale-down check. TACAL reward claims are no longer performed automatically; claim before consolidating if needed. Returns `true` on success. Parameters: - `from` (`address`): Caller and signing witness; must own at least 2 NFTs in the pool. - `poolKey` (`string`): Pool key of the positions to merge, e.g. "SOUL_KCAL". Returns: `bool` — Always true on success. Notes: Reverts if: fewer than 2 matching NFTs are found; total liquidity is 0; pool does not exist; reentrancy guard is set for `from`. ```js const tx = sb.begin() .allowGas(from, sb.nullAddress, gasPrice, gasLimit) .callContract("SATRN", "consolidatePositions", [from, "SOUL_KCAL"]) .spendGas(from) .endScript(); await wallet.sendTransaction(tx, "main"); ``` #### LP Position NFTs ##### `getUserLiquidityPools_v2(from: address): string*` — READ Yields the pool key associated with each SATRN LP NFT owned by `from`, in ownership-list order. Parallel-iterate with NFT.getOwnerships to map NFT IDs to their pool keys. This is the recommended way to enumerate a user's open positions. Parameters: - `from` (`address`): Wallet address to enumerate positions for. Returns: `string*` — Iterator of pool key strings, one per owned SATRN NFT. ```js const script = sb.begin() .callContract("SATRN", "getUserLiquidityPools_v2", ["P2K..myWallet.."]) .endScript(); const poolKeys = (await api.invokeRawScript("main", script)).decoded; ``` ##### `getNFTliquidity(nftidnumber: number): number` — READ Returns the LP units stored in a specific SATRN NFT. This is the numerator used by removeLiquidity when computing the user's reserve share. Parameters: - `nftidnumber` (`number`): SATRN NFT unique ID. Returns: `number` — LP units represented by this NFT. Returns 0 if the ID is unknown or already burned. ```js const lp = await readContract("SATRN", "getNFTliquidity", [42]); ``` ##### `getNFTticketReserveA(nftidnumber: number): string` — READ Returns the symbol of the first token (reserve A) encoded in the LP NFT. This is the `token0` orientation the pool was created with. Parameters: - `nftidnumber` (`number`): SATRN NFT unique ID. Returns: `string` — Token symbol for reserve A, e.g. "SOUL". ```js const tokenA = await readContract("SATRN", "getNFTticketReserveA", [42]); ``` ##### `getNFTticketReserveB(nftidnumber: number): string` — READ Returns the symbol of the second token (reserve B) encoded in the LP NFT. Parameters: - `nftidnumber` (`number`): SATRN NFT unique ID. Returns: `string` — Token symbol for reserve B, e.g. "KCAL". ```js const tokenB = await readContract("SATRN", "getNFTticketReserveB", [42]); ``` ##### `getNFTticketKey(nftidnumber: number): string` — READ Returns the pool key encoded in the LP NFT, derived by concatenating reserveA + "_" + reserveB. Equivalent to calling getNFTticketReserveA + "_" + getNFTticketReserveB but in a single call. Parameters: - `nftidnumber` (`number`): SATRN NFT unique ID. Returns: `string` — Pool key string, e.g. "SOUL_KCAL". ```js const key = await readContract("SATRN", "getNFTticketKey", [42]); ``` ##### `getTotalNftMinted(): number` — READ Returns the running count of live SATRN LP NFTs (incremented on mint, decremented on burn). Useful for analytics and for verifying that consolidation or removal decreased the count. Parameters: none Returns: `number` — Current number of live SATRN LP NFTs. ```js const count = await readContract("SATRN", "getTotalNftMinted", []); ``` #### Fees & Discounts ##### `getChainTokenFeeWallet(): address` — READ Returns the wallet that receives the 0.1% token-owner fee slice on swaps where the output token is SOUL or KCAL (chain-native tokens that have no on-chain token owner). Returns `@null` if admin has never called setChainTokenFeeWallet, in which case the swap path uses the hardcoded fallback address P2KAZhQ34TrLE7diBUqQNiTG1CyZjrntegfwcP9UcY5xC87. Parameters: none Returns: `address` — Configured fee-redirect wallet, or @null if not yet set. ```js const wallet = await readContract("SATRN", "getChainTokenFeeWallet", []); ``` ##### `getTotalStorageFeesPaid(): number` — READ Returns the cumulative SOUL storage-fee amount accrued in the contract's accounting ledger (historic, from before v3.9.8.13 when fees were removed). The actual contract balance minus scaled reserves represents skimmable surplus today. Parameters: none Returns: `number` — Accumulated SOUL storage fee total in raw SOUL units (historic accounting only). ```js const fees = await readContract("SATRN", "getTotalStorageFeesPaid", []); ``` #### Protocol Views ##### `getScaleFactor(tokenSymbol: string): number` — READ Returns the cached scale factor for a token — the multiplier applied to raw amounts before AMM math. For an 8-decimal token the factor is 1; for a 0-decimal token it is 10^8. Returns 0 if the scale factor has not been computed yet (it is computed and cached on the first pool or swap interaction with that token). Parameters: - `tokenSymbol` (`string`): Token symbol to query. Returns: `number` — Scale factor. 1 for 8-decimal tokens; 10^8 for 0-decimal tokens; 0 if uncached. ```js const sf = await readContract("SATRN", "getScaleFactor", ["CROWN"]); ``` ##### `getTargetDecimals(): number` — READ Returns the internal decimal-place target all tokens are scaled to before AMM arithmetic. Currently fixed at 8. Parameters: none Returns: `number` — Target decimals (8). ```js const dec = await readContract("SATRN", "getTargetDecimals", []); ``` ##### `getMaxTruncationPercent(): number` — READ Returns the current maximum truncation-loss threshold (in percent) enforced by removeLiquidity. If scale-down rounding discards more than this percentage on either token side, the remove reverts and the user must consolidate positions first. Parameters: none Returns: `number` — Max truncation percent (default 10, range 1–50). ```js const maxTrunc = await readContract("SATRN", "getMaxTruncationPercent", []); ``` ##### `getMinRawForPoolCreation(tokenSymbol: string): number` — READ Returns the minimum raw amount of `tokenSymbol` required to create a pool. Factors in the token's scale factor and the global minScaledPoolUnits parameter. Call this before building the initializePool transaction to validate user input. Parameters: - `tokenSymbol` (`string`): Token you intend to use in the pool. Returns: `number` — Minimum raw amount in `tokenSymbol` native units for pool creation. ```js const minSoul = await readContract("SATRN", "getMinRawForPoolCreation", ["SOUL"]); ``` ##### `getMinRawForSwap(tokenSymbol: string): number` — READ Returns the minimum raw input amount for swapping `tokenSymbol`. The swap reverts below this floor because the admin fee would round to zero at smaller amounts. Always call this before submitting a swap to avoid unnecessary revert costs. Parameters: - `tokenSymbol` (`string`): Input token symbol. Returns: `number` — Minimum raw input amount for `tokenSymbol` swaps. ```js const minIn = await readContract("SATRN", "getMinRawForSwap", ["SOUL"]); ``` ##### `getMinRawForAddLiquidity(tokenSymbol: string): number` — READ Returns the minimum raw amount of `tokenSymbol` when calling addLiquidity_v2. The second token's amount is computed proportionally, so only the first token needs this explicit check. Parameters: - `tokenSymbol` (`string`): First-token symbol for the addLiquidity_v2 call. Returns: `number` — Minimum raw amount of the first token for add-liquidity. ```js const minAdd = await readContract("SATRN", "getMinRawForAddLiquidity", ["SOUL"]); ``` ##### `getReentrancyGuardState(user: address): number` — READ Returns the reentrancy guard value for `user`. 0 = unlocked (normal state), 1 = locked (a state-mutating call from this address is in progress). Useful for diagnostics; if a call failed mid-execution and left the guard stuck at 1, the admin can call clearReentrancy. Under normal operation this always returns 0. Parameters: - `user` (`address`): Address to check. Returns: `number` — 0 = unlocked, 1 = locked. ```js const guard = await readContract("SATRN", "getReentrancyGuardState", ["P2K..myWallet.."]); ``` ##### `getVersion(): string` — READ Returns the on-chain version string of the deployed SATRN contract. Use this to confirm which build is live before relying on version-specific behaviour. Parameters: none Returns: `string` — Version string, e.g. "3.9.9.2". ```js const ver = await readContract("SATRN", "getVersion", []); ``` #### Disabled Methods (Disabled in v1.0) ##### `addLiquidity(from: address, amountFirstToken: number, tokenFirst: string, tokenSecond: string)` — WRITE DISABLED. This method was the original add-liquidity entrypoint but lacked a `maxSecondTokenAmount` slippage guard. It is permanently disabled in v3.9.9.1 — calling it always reverts with "Deprecated: use addLiquidity_v2 with maxSecondTokenAmount parameter". Use addLiquidity_v2() instead. Parameters: - `from` (`address`): Caller address (unused — always reverts). - `amountFirstToken` (`number`): Deposit amount (unused). - `tokenFirst` (`string`): First token symbol (unused). - `tokenSecond` (`string`): Second token symbol (unused). Notes: Always reverts. Do not call. ##### `getUserLiquidityPools(from: address, nftID: number): string*` — READ DISABLED. The original per-NFT pool-lookup method. It is permanently disabled in v3.9.9.1 — calling it always reverts with "Deprecated: use getUserLiquidityPools_v2". Use getUserLiquidityPools_v2() instead, which iterates all owned NFTs without requiring an individual NFT ID. Parameters: - `from` (`address`): Wallet address (unused — always reverts). - `nftID` (`number`): NFT ID (unused). Notes: Always reverts. Do not call.