Skip to main content

DeepBook Predict Testnet Workflow

A complete DeepBook Predict flow on Sui Testnet covers reading a live oracle, minting binary and vertical range positions, redeeming before and after settlement, and running the liquidity provider vault flow. It continues from the DeepBook Predict quickstart, which covers install, funding, and your first mint.

There is no dedicated Predict TypeScript SDK. You build every transaction with the Sui TypeScript SDK against the protocol's Move entry points. The transaction samples live in the examples/deepbook-predict package, which CI type-checks with tsc --noEmit against @mysten/sui version 2.22.1. This document does not execute them against Testnet. The oracle-list response shape matches the live Testnet server. Run the manual verification steps before you rely on the write paths.

caution

DeepBook Predict smart contracts might change before Mainnet deployment. Treat the current package IDs, object layouts, and entry points as Testnet integration targets. All package IDs and source references on these pages are pinned to the predict-testnet-4-16 branch and change at Mainnet launch.

Configuration and client

Keep every Testnet-only ID in one configuration block. These values come from the Contract Information page and change at Mainnet launch.

export type PredictNetwork = 'testnet' | 'mainnet';

export type PredictConfig = {
network: PredictNetwork;
fullnodeUrl: string;
packageId: string;
predictObjectId: string;
quoteType: string;
serverUrl: string;
};

// Testnet IDs pinned to the `predict-testnet-4-16` branch. These change at
// Mainnet launch. Source: Contract Information page.
const TESTNET: PredictConfig = {
network: 'testnet',
fullnodeUrl: 'https://fullnode.testnet.sui.io:443',
packageId: '0xf5ea2b3749c65d6e56507cc35388719aadb28f9cab873696a2f8687f5c785138',
predictObjectId: '0xc8736204d12f0a7277c86388a68bf8a194b0a14c5538ad13f22cbd8e2a38028a',
// DeepBook Test USDC (DUSDC), 6 decimals.
quoteType:
'0xe95040085976bfd54a1a07225cd46c8a2b4e8e2b6732f140a0fc49850ba73e1a::dusdc::DUSDC',
serverUrl: 'https://predict-server.testnet.mystenlabs.com',
};

// DeepBook Predict has no Mainnet deployment yet, so there is no Mainnet entry
// to select. Add one at launch: every value differs from Testnet, including the
// quote asset, which is a test coin on Testnet and a real asset on Mainnet.
const CONFIGS: Partial<Record<PredictNetwork, PredictConfig>> = {
testnet: TESTNET,
};

export function predictConfigFor(network: PredictNetwork): PredictConfig {
const config = CONFIGS[network];
if (!config) {
throw new Error(`DeepBook Predict has no ${network} deployment.`);
}
return config;
}

// Resolve once at startup and pass the result down, rather than reading the
// network in each module. The examples target Testnet.
export const PREDICT = predictConfigFor('testnet');

// Oracle ID, expiry, and strike are NOT hardcoded. Read a live oracle from the
// Predict server before minting: GET /predicts/:predict_id/oracles.
export type ActiveOracle = {
oracleId: string; // object ID of the OracleSVI
expiry: number; // ms timestamp
strike: number; // fixed-point strike, per oracle scale
};

The samples reuse the client from the quickstart:

import { SuiGrpcClient } from '@mysten/sui/grpc';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { decodeSuiPrivateKey } from '@mysten/sui/cryptography';
import { PREDICT } from './config.js';

export function getKeypair(privateKey: string): Ed25519Keypair {
const { secretKey } = decodeSuiPrivateKey(privateKey);
return Ed25519Keypair.fromSecretKey(secretKey);
}

export const client = new SuiGrpcClient({
network: PREDICT.network,
baseUrl: PREDICT.fullnodeUrl,
});

Understand the oracle

Each market is an OracleSVI object for one underlying asset and one expiry. It holds spot, forward, SVI parameters, a lifecycle status, and, after expiry, a settlement price. Each oracle constrains strikes to a grid rather than free-form values: min_strike plus multiples of tick_size. You trade a binary position at one grid strike, or a vertical range between two grid strikes.

An oracle moves through 4 lifecycle states:

  • Inactive: exists but not yet activated.
  • Active: accepts live price and SVI updates. Minting requires this state.
  • Pending settlement: reached expiry, awaiting the first post-expiry price.
  • Settled: the first post-expiry price freezes the settlement price. No further live updates.

Minting requires a live (active) oracle. Redeeming works against a live or settled oracle.

Read a live oracle from the server

Read the current oracle list from the public Predict server, then select an active oracle and a strike from its grid. The server returns snake_case fields and the strike grid rather than a single strike.

import { PREDICT, type ActiveOracle } from './config.js';

type ServerOracle = {
oracle_id: string;
expiry: number;
min_strike: number;
tick_size: number;
status: string; // "inactive" | "active" | "pending_settlement" | "settled"
};

// Picks the first active oracle and a strike `tickIndex` ticks up the grid.
export async function getActiveOracle(
predictObjectId: string,
tickIndex = 0,
): Promise<ActiveOracle> {
const res = await fetch(`${PREDICT.serverUrl}/predicts/${predictObjectId}/oracles`);
if (!res.ok) throw new Error(`oracle fetch failed: ${res.status}`);
const oracles = (await res.json()) as ServerOracle[];
const live = oracles.find((o) => o.status === 'active');
if (!live) throw new Error('no active oracle available');
return {
oracleId: live.oracle_id,
expiry: live.expiry,
strike: live.min_strike + tickIndex * live.tick_size,
};
}

Set up the PredictManager

Each user creates one PredictManager and reuses it. See the quickstart for create_manager and reading the new ID from effects. Because the manager is shared during creation, deposit into it and mint from it in later transactions.

The manager stores your deposited quote balance, binary position quantities keyed by MarketKey, and range quantities keyed by RangeKey. Read them with predict_manager::balance, predict_manager::position, and predict_manager::range_position. The manager does not store positions as separate objects.

Deposit and mint a binary position

A binary position pays out when settlement lands above the strike (an up position) or at or below it (a down position). Deposit DUSDC into the manager and mint one up position in a single transaction. Build the MarketKey with market_key::up.

import { Transaction } from '@mysten/sui/transactions';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { client } from './client.js';
import { PREDICT, type ActiveOracle } from './config.js';

// Deposits DUSDC into the manager and mints one binary "up" position, in a
// single PTB. The deposit is sourced from the signer's DUSDC wherever it sits,
// whether that is an address balance or several separate coin objects.
export async function mintBinaryUp(params: {
signer: Ed25519Keypair;
managerId: string;
oracle: ActiveOracle;
depositAmount: bigint; // DUSDC base units (6 decimals)
quantity: bigint; // position quantity
}) {
const { signer, managerId, oracle, depositAmount, quantity } = params;
const tx = new Transaction();

// 1. Source the deposit amount in DUSDC and deposit it into the manager.
const deposit = tx.coin({ balance: depositAmount, type: PREDICT.quoteType });
tx.moveCall({
target: `${PREDICT.packageId}::predict_manager::deposit`,
typeArguments: [PREDICT.quoteType],
arguments: [tx.object(managerId), deposit],
});

// 2. Build the MarketKey for an "up" binary position.
const key = tx.moveCall({
target: `${PREDICT.packageId}::market_key::up`,
arguments: [
tx.pure.id(oracle.oracleId),
tx.pure.u64(oracle.expiry),
tx.pure.u64(oracle.strike),
],
});

// 3. Mint the position, paying from the manager's deposited balance.
tx.moveCall({
target: `${PREDICT.packageId}::predict::mint`,
typeArguments: [PREDICT.quoteType],
arguments: [
tx.object(PREDICT.predictObjectId),
tx.object(managerId),
tx.object(oracle.oracleId),
key,
tx.pure.u64(quantity),
tx.object.clock(),
],
});

const result = await client.signAndExecuteTransaction({
transaction: tx,
signer,
include: { effects: true },
});
// Wait for finality before acting on the result, so later reads reflect it.
await client.waitForTransaction({ result });

if (result.$kind === 'FailedTransaction') {
// The transaction is onchain and the sender paid gas. Do not retry it.
const { status } = result.FailedTransaction;
throw new Error(
`mint aborted: ${status.success ? 'unknown' : JSON.stringify(status.error)}`,
);
}
return result.Transaction;
}

For a down position, use market_key::down with the same oracle, expiry, and strike.

Preview mint cost

Before minting, preview the cost. The predict::get_trade_amounts read function returns (mint_cost, redeem_payout) per the requested quantity, priced from oracle fair value plus protocol spread. For rendering, the public server also exposes oracle state and resolved ask bounds through GET /oracles/:oracle_id/state and GET /oracles/:oracle_id/ask-bounds. Deposit at least the previewed mint_cost before you mint.

Mint a vertical range position

A vertical range pays out when settlement lands in the half-open band (lower_strike, higher_strike]. Build the key with range_key::new, which aborts if lower_strike is not less than higher_strike. This example assumes the manager already holds enough DUSDC.

import { Transaction } from '@mysten/sui/transactions';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { client } from './client.js';
import { PREDICT, type ActiveOracle } from './config.js';

export async function mintRange(params: {
signer: Ed25519Keypair;
managerId: string;
oracle: ActiveOracle;
lowerStrike: bigint;
higherStrike: bigint;
quantity: bigint;
}) {
const { signer, managerId, oracle, lowerStrike, higherStrike, quantity } = params;
const tx = new Transaction();

const key = tx.moveCall({
target: `${PREDICT.packageId}::range_key::new`,
arguments: [
tx.pure.id(oracle.oracleId),
tx.pure.u64(oracle.expiry),
tx.pure.u64(lowerStrike),
tx.pure.u64(higherStrike),
],
});

tx.moveCall({
target: `${PREDICT.packageId}::predict::mint_range`,
typeArguments: [PREDICT.quoteType],
arguments: [
tx.object(PREDICT.predictObjectId),
tx.object(managerId),
tx.object(oracle.oracleId),
key,
tx.pure.u64(quantity),
tx.object.clock(),
],
});

const result = await client.signAndExecuteTransaction({
transaction: tx,
signer,
include: { effects: true },
});
// Wait for finality before acting on the result, so later reads reflect it.
await client.waitForTransaction({ result });

if (result.$kind === 'FailedTransaction') {
// The transaction is onchain and the sender paid gas. Do not retry it.
const { status } = result.FailedTransaction;
throw new Error(
`mint_range aborted: ${status.success ? 'unknown' : JSON.stringify(status.error)}`,
);
}
return result.Transaction;
}

Preview range amounts with predict::get_range_trade_amounts, which mirrors get_trade_amounts for a RangeKey.

Redeem and settlement

Redeeming sells a position back to the vault and deposits the payout into the owner's manager. The payout depends on the oracle lifecycle:

  • Before settlement: the payout is the post-trade bid value at current oracle prices.
  • After settlement: a binary position pays its settled fair value, and a vertical range pays its full value when settlement landed in the band, or zero otherwise.

The owner redeems a position with predict::redeem.

import { Transaction } from '@mysten/sui/transactions';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { client } from './client.js';
import { PREDICT, type ActiveOracle } from './config.js';

export async function redeemBinaryUp(params: {
signer: Ed25519Keypair;
managerId: string;
oracle: ActiveOracle;
quantity: bigint;
permissionless?: boolean; // for settled positions redeemed by anyone
}) {
const { signer, managerId, oracle, quantity, permissionless } = params;
const tx = new Transaction();

const key = tx.moveCall({
target: `${PREDICT.packageId}::market_key::up`,
arguments: [
tx.pure.id(oracle.oracleId),
tx.pure.u64(oracle.expiry),
tx.pure.u64(oracle.strike),
],
});

tx.moveCall({
target: `${PREDICT.packageId}::predict::${permissionless ? 'redeem_permissionless' : 'redeem'}`,
typeArguments: [PREDICT.quoteType],
arguments: [
tx.object(PREDICT.predictObjectId),
tx.object(managerId),
tx.object(oracle.oracleId),
key,
tx.pure.u64(quantity),
tx.object.clock(),
],
});

const result = await client.signAndExecuteTransaction({
transaction: tx,
signer,
include: { effects: true },
});
// Wait for finality before acting on the result, so later reads reflect it.
await client.waitForTransaction({ result });

if (result.$kind === 'FailedTransaction') {
// The transaction is onchain and the sender paid gas. Do not retry it.
const { status } = result.FailedTransaction;
throw new Error(
`redeem aborted: ${status.success ? 'unknown' : JSON.stringify(status.error)}`,
);
}
return result.Transaction;
}

Permissionless redemption

predict::redeem_permissionless lets a third party close out a settled position for its owner. These 3 properties define it:

  • Any address can call it. The function routes through the package-internal deposit_permissionless, which skips the owner check that predict_manager::deposit and withdraw enforce (PredictManager: deposit and withdraw). The caller needs no capability, no relationship to the owner, and no signature from them.
  • It becomes available at settlement, not at expiry. The position must be settled. An oracle reaches settled only when the first post-expiry price update freezes the settlement price, and it sits in pending settlement until then (Oracle lifecycle). Calling earlier fails.
  • The payout goes to the owner, never the caller. The quote amount lands in the owner's PredictManager balance, and only the owner can withdraw it. The caller pays gas and receives nothing.

Redeem a vertical range with predict::redeem_range.

caution

The protocol pays the caller no fee, rebate, or share of the payout for a permissionless redemption. There is no keeper incentive, so no economic reason exists for an unrelated third party to run this. Treat it as infrastructure that you run for your own users, or that a position owner runs for themselves, and budget the gas as your own operating cost rather than expecting the network to close out positions for you.

Monitor settlement and redeem automatically

Positions are rows in the manager's positions table keyed by MarketKey, not standalone objects, so you cannot discover them by scanning owned objects (Binary position quantities). An automated redeemer needs its own index, built from events:

  1. Track positions as users open them. Record the manager ID and MarketKey fields from each PositionMinted event, and clear the entry on the matching PositionRedeemed. This index is what makes step 3 possible, because nothing onchain enumerates a manager's positions for you.
  2. Watch for settlement. Subscribe to OracleSettled as Stream events over gRPC shows. Trigger on the event rather than on the expiry timestamp, because the pending settlement gap has no fixed duration.
  3. Select the affected positions. On settlement of an oracle, take the tracked entries whose MarketKey names that oracle ID.
  4. Confirm each is still open, then redeem. Read the current quantity before submitting, so a position the owner already redeemed does not cost you a failed transaction. Build the MarketKey with market_key::up, market_key::down, or market_key::new, pass it to predict_manager::position in the same transaction, and simulate the pair. The key is command 0 and the quantity is command 1, so decode returnValues[0] of the second command with bcs.U64.

Use the same simulation helper described in Check the limiter before you withdraw, which returns the raw bytes for every command in one call. PredictManager is a shared object, so unlike the vault accessors this read is reachable from a simulated transaction.

position() has 2 properties that shape the loop. It returns 0 for a key that was never opened and for one already redeemed, so treat zero as nothing to do rather than as an error. And it requires the exact key up front, which is why the event index in step 1 is not optional.

If you would rather not maintain that index, two alternatives enumerate positions directly: list the dynamic fields of the positions table using the table's id, where each field key is a BCS-serialized MarketKey, or read the indexed portfolio endpoints from the Predict server. The server path is simpler, at the cost of depending on the indexer being current.

Make the worker idempotent. The event stream delivers at least once, so the same OracleSettled can arrive twice after a reconnect, and another party can redeem the same position between your check and your submission. Confirm each redemption against the resulting PositionRedeemed event instead of assuming a submitted transaction succeeded.

Liquidity provider flow

Liquidity providers supply an accepted quote asset into the shared vault and receive PLP shares. The vault takes the opposite side of every Predict trade, so LP returns track vault profit and loss. The first supplier receives shares one-to-one with the amount they supply. Later suppliers receive shares proportional to their deposit relative to current vault value.

Supply liquidity

predict::supply returns a Coin<PLP>. Transfer it to the supplier to keep it.

import { Transaction } from '@mysten/sui/transactions';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { client } from './client.js';
import { PREDICT } from './config.js';

export async function supplyLiquidity(params: {
signer: Ed25519Keypair;
amount: bigint;
}) {
const { signer, amount } = params;
const tx = new Transaction();

// Sourced from the signer's DUSDC, whether it sits in an address balance or
// across several coin objects.
const supply = tx.coin({ balance: amount, type: PREDICT.quoteType });
const plp = tx.moveCall({
target: `${PREDICT.packageId}::predict::supply`,
typeArguments: [PREDICT.quoteType],
arguments: [tx.object(PREDICT.predictObjectId), supply, tx.object.clock()],
});
tx.transferObjects([plp], signer.toSuiAddress());

const result = await client.signAndExecuteTransaction({
transaction: tx,
signer,
include: { effects: true },
});
// Wait for finality before acting on the result, so later reads reflect it.
await client.waitForTransaction({ result });

if (result.$kind === 'FailedTransaction') {
// The transaction is onchain and the sender paid gas. Do not retry it.
const { status } = result.FailedTransaction;
throw new Error(
`supply aborted: ${status.success ? 'unknown' : JSON.stringify(status.error)}`,
);
}
return result.Transaction;
}

Read vault state and value your PLP

PLP shares are a proportional claim on vault value, not a fixed-value token. Before you supply or withdraw, read the vault aggregates and derive what your shares are worth.

No single read function returns the vault totals. Vault has the store ability and lives inside the Predict shared object, and Predict exposes no accessor that returns &Vault, so vault_value(), balance(), and total_max_payout() are unreachable from a programmable transaction block or a simulation (Reading vault state). Read the values from the Predict object contents instead:

Fetch the Predict object with its contents, then read 4 fields. The Predict server itself reads exactly these paths in crates/predict-server/src/server.rs, so treat them as the reference layout:

ValuePath in the object contents
Pooled treasury balancevault.balance
Mark-to-market liabilityvault.total_mtm
Worst-case settlement liabilityvault.total_max_payout
PLP total supplytreasury_cap.total_supply.value

The server derives the same quantities you need from those 4 reads: vault_value as balance - total_mtm floored at zero, share price as vault_value / plp_total_supply, and withdrawal capacity as balance - total_max_payout floored at zero.

Fetch the object with client.getObject and the json include, as chain.ts does. Compute the derived values in BigInt: integer division truncates, which reproduces the contract's round-down, and quote amounts exceed the range where floating point stays exact.

Three derived quantities matter to a liquidity provider, and the chain stores none of them:

  • Vault value: balance - total_mtm. This is the net asset value your shares claim against.
  • Share price: vault_value / plp_total_supply. Use it to price a supply, and expect it to move between reading it and landing your transaction.
  • Redemption value: shares_burned * vault_value / plp_total_supply, rounded down. Burning the entire supply short-circuits onchain and returns the full vault value with no rounding loss.

Compute these in BigInt, as the sample does. Integer division truncates, which reproduces the contract's round-down behavior, and quote amounts exceed the range where floating point stays exact. See the worked example for the same arithmetic with concrete numbers.

caution

The json representation of object contents is not guaranteed stable across API implementations. The gRPC and GraphQL shapes can differ. For an integration you intend to run unattended, parse the BCS content bytes with a generated parser for the Predict struct instead, and treat the field paths in this sample as specific to the API you read from.

Unsettled oracles move your share value

total_mtm is the mark-to-market liability of every open position, and vault value subtracts it. While oracles remain unsettled, that number moves with trader positions and oracle prices, so your share value moves with it even though you placed no trade. The vault is the counterparty to every trade, so liquidity providers hold the other side of trader profit and loss.

Plan around 2 consequences:

  • Mark-to-market is an estimate until settlement. total_mtm reflects current oracle state. It resolves to a realized outcome only when each oracle settles, so a share price you read mid-life is provisional. Do not treat it as a guaranteed outcome.
  • Worst-case liability locks capital, and settlement releases it. total_max_payout reserves what the vault owes if every oracle settles at its worst strike. Because binary payouts jump at settlement, that reserve can far exceed current mark, and it caps withdrawals through available. Settlement compaction replaces the worst-case estimate with exact settled liability, which is what frees the reserved capital.

Read total_max_payout alongside vault_value for this reason. A vault can look solvent on share price while withdrawals stay capped, because the reserve check runs against balance, not vault_value.

Withdraw liquidity

predict::withdraw burns a Coin<PLP> and returns the selected quote asset. The withdrawal succeeds only when enough funds remain after the vault covers its current maximum payout and the withdrawal limiter allows the amount.

import { Transaction } from '@mysten/sui/transactions';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
import { client } from './client.js';
import { PREDICT } from './config.js';

export async function withdrawLiquidity(params: {
signer: Ed25519Keypair;
plpCoinId: string;
}) {
const { signer, plpCoinId } = params;
const tx = new Transaction();

const quote = tx.moveCall({
target: `${PREDICT.packageId}::predict::withdraw`,
typeArguments: [PREDICT.quoteType],
arguments: [tx.object(PREDICT.predictObjectId), tx.object(plpCoinId), tx.object.clock()],
});
tx.transferObjects([quote], signer.toSuiAddress());

const result = await client.signAndExecuteTransaction({
transaction: tx,
signer,
include: { effects: true },
});
// Wait for finality before acting on the result, so later reads reflect it.
await client.waitForTransaction({ result });

if (result.$kind === 'FailedTransaction') {
// The transaction is onchain and the sender paid gas. Do not retry it.
const { status } = result.FailedTransaction;
throw new Error(
`withdraw aborted: ${status.success ? 'unknown' : JSON.stringify(status.error)}`,
);
}
return result.Transaction;
}

Check the limiter before you withdraw

predict::available_withdrawal returns the amount the withdrawal limiter currently permits, as a u64 in raw quote units. It reads state and returns a value rather than mutating anything, so you call it by simulating a transaction instead of executing one. Simulation costs no gas and needs no signature, which makes it safe to run on every withdrawal attempt.

Build a transaction with a single moveCall targeting predict::available_withdrawal, passing the Predict object and the clock, then simulate it and decode the command's first return value with bcs.U64.

The Predict oracle feed service already does this, in simulateReturnValues. Follow its 3 choices, because each one matters for read calls:

  • Simulate with checksEnabled: false. Validation checks reject calls that never execute onchain, including non-entry Move functions. Leaving checks on makes a read that would otherwise work fail.
  • Default the sender to 0x0. A simulated read needs no funded address and no signature, so it works before a user connects a wallet. Normalize the address rather than passing a bare string.
  • Return the raw bytes per command. The service maps commandResults to returnValues[].bcs and leaves decoding to the caller, which keeps one helper usable for every read function. Decode a u64 return with bcs.U64.

The function delegates straight to the withdrawal limiter:

Read the limiter itself before you rely on the number:

Compare the result to the quote amount your PLP burn releases, not to the share count. withdraw() converts shares to quote units as shares_burned * vault_value / plp_total_supply, rounded down, so derive the outgoing amount with the redemption value formula before you compare.

caution

This check alone does not guarantee the withdrawal succeeds, for 2 reasons. A disabled limiter returns u64::MAX rather than a real capacity, and the limiter starts disabled on a new deployment, so a preflight that only compares your amount against this value passes every time and tells you nothing. The limiter is also the second gate: the first reserves the vault's worst-case settlement liability, and withdraw() aborts with EWithdrawExceedsAvailable when your amount exceeds balance - total_max_payout. Treat your usable ceiling as the smaller of that value and available_withdrawal. See Max payout.

Both gates read live state, so the value you simulate can go stale before your transaction executes. Another liquidity provider's withdrawal drains the same token bucket, and a mint raises max payout. Treat the check as a way to fail fast and size a request, then handle the onchain abort as the authoritative outcome.

Vault strategy considerations

These points follow directly from the protocol's documented mechanics. They are not investment advice.

  • Withdrawals can be capped by liability. predict::withdraw only releases funds available after the vault reserves its current total maximum payout. When open positions carry large payout coverage, less is withdrawable. Read the current withdrawable amount with predict::available_withdrawal, as Check the limiter before you withdraw shows.
  • A rate limiter can throttle outflows. The vault includes a withdrawal limiter with a configured capacity and refill rate. Large withdrawals can be limited even when funds are otherwise available.
  • LP value tracks vault profit and loss. Because the vault is the counterparty to every trade, PLP share value rises and falls with trader outcomes, not with a fixed yield.
  • Minting is bounded by an exposure cap. After each mint, the vault asserts that total mark-to-market liability stays within a configured percentage of vault value, which protects existing LPs.

For the accounting model behind these limits, see Design.

Verify on Testnet

The write paths above pass compile checks but do not execute. To confirm them end to end:

  1. Fund a Testnet address with SUI, then confirm with sui client gas.
  2. Request DUSDC, then confirm your DUSDC balance with sui client balance.
  3. Fetch a live oracle with getActiveOracle. Confirm the JSON includes an oracle with "status": "active".
  4. Create a PredictManager (see the quickstart) and confirm the ID resolves with sui client object MANAGER_ID.
  5. Run mintBinaryUp with a depositAmount at or above the previewed mint cost. Confirm a success status and a PositionMinted event.
  6. Run mintRange with two grid strikes where lowerStrike < higherStrike. Confirm a RangeMinted event.
  7. Run redeemBinaryUp. Confirm a PositionRedeemed event and that the payout lands in the manager balance (predict_manager::balance).
  8. Run supplyLiquidity, then confirm you hold a PLP coin with sui client objects.
  9. Run withdrawLiquidity with that PLP coin. Confirm a Withdrawn event.