Module 17: Contracts & API
Reference module for contract addresses, SDK setup, API details, production operations, and error handling. Organized for quick lookup.
Prerequisites
- Wallet basics and SDK install via Module 02
- For an explanation of which contracts back which actions, see Module 18
Next steps
- Pick an action module to start writing code: trading, lending, staking, token creation, predictions, vesting
- For agent identity & faucet, see Module 03
- For full error context, see the SDK Reference error index
1. Contract Addresses
Canonical addresses are published at https://launchonbasis.com/contracts.json. The SDK fetches this on startup and warns if hardcoded defaults are stale. All addresses are overridable via constructor options.
BSC Mainnet — Default Addresses:
| Contract | SDK Name | Address | Purpose |
|---|---|---|---|
| USDB | usdbAddress | 0x42bcF288e51345c6070F37f30332ee5090fC36BF | Protocol stablecoin (→01) |
| STASIS (MAINTOKEN) | mainTokenAddress | 0x3067ce754a36d0a2A1b215C4C00315d9Da49EF15 | Governance/utility token |
| SWAP | swapAddress | 0x9F9cF98F68bDbCbC5cf4c6402D53cEE1D180715f | AMM swap router — used by all trades and leverage |
| FACTORY (ATokenFactory) | factoryAddress | 0xB6BA282f29A7C67059f4E9D0898eE58f5C79960D | Token creation |
| STAKING (AStasisVault) | stakingAddress | 0x1FE7189270fb93c32a1fEfA71d1795c05C41cb33 | wSTASIS vault |
| LOANS (LoanHub) | loanHubAddress | 0xFe19644d52fD0014EBa40c6A8F4Bfee4Ce3B2449 | Loans (leverage lives on SWAP, not here) |
| VESTING | vestingAddress | 0xedd987c7723B9634b0Aa6161258FED3e89F9094C | Token vesting schedules |
| TAXES (ATaxes) | taxesAddress | 0x4501d1279273c44dA483842ED17b5451e7d3A601 | Surge tax management |
| PREDICTION (MarketTrading) | marketTradingAddress | 0x396216fc9d2c220afD227B59097cf97B7dEaCb57 | Prediction market trading |
| RESOLVER (AMarketResolver) | resolverAddress | 0xB5FFCCB422531Cf462ec430170f85d8dD3dC3f57 | Market resolution & voting |
| PRIVATE MARKETS | privateMarketAddress | 0x28675A82ee3c2e6d2C85887Ea587FbDD3E3C86EE | Private prediction markets |
| Market Reader | readerAddress | 0xF406cA6403c57Ad04c8E13F4ae87b3732daa087d | Read-only market data helper (→10) |
| Leverage Simulator | leverageAddress | 0xeffb140d821c5B20EFc66346Cf414EeAC8A8FDB2 | Off-chain leverage simulation |
| Floor | — | 0x359dE659F0242352dD7F021c2EcB370284D95F45 | Floor price mechanism |
| ERC-8004 Identity Registry | — | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 | On-chain agent identity |
Canonical source: The SDK auto-fetches addresses from
https://launchonbasis.com/contracts.jsonon startup and warns if hardcoded defaults are stale. Always trust that endpoint over any hardcoded values in documentation.
Naming note: MAINTOKEN is the contract/SDK variable name for STASIS. In code:
client.mainTokenAddress(JS) /client.main_token_address(Python).
Constructor override names: factoryAddress, swapAddress, marketTradingAddress, loanHubAddress, vestingAddress, stakingAddress, resolverAddress, privateMarketAddress, readerAddress, leverageAddress, taxesAddress, usdbAddress, mainTokenAddress
2. SDK Installation & Configuration
Installation
JavaScript / TypeScript:
npm install github:Launch-On-Basis/SDK-TS
Python:
pip install git+https://github.com/Launch-On-Basis/SDK-PY.git
Initialization Modes
Three modes unlock progressively more functionality.
Mode 1 — Read-Only (no credentials)
On-chain reads only. No private key or API key required.
JavaScript:
const { BasisClient } = require("basis-sdk-js");
const client = new BasisClient();
const price = await client.trading.getUSDPrice("0xTokenAddress...");
Python:
from basis_sdk import BasisClient
client = BasisClient()
price = client.trading.get_usd_price("0xTokenAddress...")
Mode 2 — API Key (read-only + off-chain data)
Adds access to off-chain endpoints: token lists, candles, trade history, etc.
JavaScript:
const client = new BasisClient({ apiKey: "bsk_your_api_key" });
const tokens = await client.api.getTokens({ limit: 10 });
Python:
client = BasisClient(api_key="bsk_your_api_key")
tokens = client.api.get_tokens(limit=10)
Mode 3 — Full Mode (private key — recommended for agents)
Automatically authenticates via SIWE, auto-provisions an API key, and enables all write operations. This is the mode to use for agents.
On first run with no existing API key, the SDK creates a new key and logs it. Save this key — it is only shown once. Pass it on subsequent runs via apiKey.
JavaScript:
// First run — SDK creates and logs a new API key. Save it!
const client = await BasisClient.create({ privateKey: process.env.BASIS_PRIVATE_KEY });
// Subsequent runs — pass the saved key
const client = await BasisClient.create({
privateKey: process.env.BASIS_PRIVATE_KEY,
apiKey: "bsk_your_saved_key",
});
// Execute a trade
const { parseUnits } = require("viem");
const result = await client.trading.buy("0xTokenAddress...", parseUnits("5", 18));
console.log("Tx hash:", result.hash);
Python:
import os
# First run
client = BasisClient.create(private_key=os.environ["BASIS_PRIVATE_KEY"])
# Subsequent runs
client = BasisClient.create(
private_key=os.environ["BASIS_PRIVATE_KEY"],
api_key="bsk_your_saved_key"
)
result = client.trading.buy("0xTokenAddress...", 5_000_000_000_000_000_000)
print("Tx hash:", result["hash"])
Session lifetime: SIWE sessions expire when the browser closes (no TTL). For long-running agents, API keys are better — they are auto-provisioned during
BasisClient.create()and do not expire.client.apiKeypersists across restarts if you store it.
Configuration Options
All options are passed to BasisClient constructor or BasisClient.create.
| Option | Type | Default | Description |
|---|---|---|---|
privateKey | string | — | Wallet private key. Enables write operations and automatic SIWE auth. |
apiKey | string | — | API key for data endpoints. On first run with privateKey, a key is auto-created and logged — save it and pass it on future runs. Only shown once at creation. |
rpcUrl | string | https://bsc-dataseed.binance.org/ | Custom BSC RPC endpoint. Validated on connect — must return chainId 56. |
apiDomain | string | https://launchonbasis.com | Base URL for the Basis API. |
agent | boolean or object | — | ERC-8004 agent registration. true for defaults, or { name, description, capabilities } for custom metadata. Register after building real capabilities, not on day one. |
Client Properties
JavaScript (camelCase):
| Property | Type | Description |
|---|---|---|
client.usdbAddress | address | USDB contract address |
client.mainTokenAddress | address | STASIS/MAINTOKEN contract address |
client.stakingAddress | address | wSTASIS vault contract address |
client.publicClient | PublicClient | viem public client for read-only calls |
client.walletClient | WalletClient | viem wallet client for write operations (requires privateKey) |
client.walletClient.account.address | address | Your wallet address |
client.api | BasisAPI | Off-chain API wrapper |
client.apiKey | string | Auto-provisioned API key (persistent, no expiry) |
Python (snake_case):
| Property | Type | Description |
|---|---|---|
client.w3 | Web3 | web3.py instance for raw contract calls |
client.wallet_address | str | Your wallet address |
client.usdb_address | str | USDB contract address |
client.main_token_address | str | STASIS/MAINTOKEN contract address |
client.api_key | str | Auto-provisioned API key (persistent, no expiry) |
Token Amount Conventions
All SDK methods expect raw integer amounts in the token's smallest unit. All Basis tokens use 18 decimals — no exceptions.
| Token | Decimals | Example |
|---|---|---|
| USDB | 18 | 5 * 10**18 = 5 USDB |
| STASIS | 18 | 1 * 10**18 = 1 STASIS |
| Factory tokens | 18 | 1 * 10**18 = 1 token |
JavaScript:
import { parseUnits, formatUnits } from "viem";
const usdbRaw = parseUnits("5", 18); // 5000000000000000000n
const humanUsdb = formatUnits(5000000000000000000n, 18); // "5"
Python:
usdb_raw = 5 * 10**18 # 5000000000000000000
# or via web3:
from web3 import Web3
usdb_raw = Web3.to_wei(5, "ether")
human = Web3.from_wei(5000000000000000000, "ether") # 5
Exception: sellPercentage() takes a 1–100 integer, not a raw amount.
Private Key Security
Never hardcode private keys in source files or commit them to version control.
// JS — use environment variables
const client = await BasisClient.create({ privateKey: process.env.BASIS_PRIVATE_KEY });
# Python — use environment variables
import os
client = BasisClient.create(private_key=os.environ["BASIS_PRIVATE_KEY"])
Best practices:
- Store keys in
.envfiles (add.envto.gitignore) - Use a secrets manager for production (AWS Secrets Manager, HashiCorp Vault, etc.)
- Generate a dedicated wallet for your agent — do not reuse personal wallets
- Keep a small BNB balance as a gas fallback (~0.005 BNB)
3. API Reference
Base URL
https://launchonbasis.com/api
Rate Limits
| Auth Type | Limit | Scope |
|---|---|---|
API Key (/api/v1/*) | 60 req/min | Per key |
| SIWE Session (core endpoints) | 30 req/min | Per IP |
Transaction Sync (/api/v1/sync) | 20 req/min | Per IP |
When exceeded: 429 Too Many Requests
Rate limit headers on every response:
X-RateLimit-Limit— max requests per windowX-RateLimit-Remaining— requests left in current windowX-RateLimit-Reset— unix timestamp when the window resets
Pagination
Offset-based (browsable lists — tokens, orders, comments, whitelist):
?page=1&limit=20
→ { "total": 100, "page": 1, "limit": 20, "hasMore": true }
Cursor-based (append-only data — trades, transactions, liquidity):
?limit=20 // first page
?cursor=499&limit=20 // next page (use nextCursor from previous response)
→ { "limit": 20, "hasMore": true, "nextCursor": "479" }
Each endpoint notes which style it uses.
Authentication
| Method | How | Lifetime |
|---|---|---|
| SIWE session | client.authenticate() | Until browser/process closes |
| API key | Pass apiKey to constructor | No expiry |
For long-running agents: use API keys. BasisClient.create() auto-provisions a key — store it and pass it on restarts.
HTTP Status Codes
| Status | Meaning |
|---|---|
400 | Bad request (missing or invalid parameters) |
401 | Not authenticated (missing or expired session/API key) |
403 | Forbidden (not the owner or insufficient permissions) |
404 | Resource not found |
409 | Conflict (duplicate resource, e.g. metadata already exists) |
422 | Validation failed (invalid signature, sync error) |
429 | Rate limit exceeded |
Transaction Sync
The SDK automatically syncs transaction state to the backend after every write operation. This calls POST /api/v1/sync with the transaction hash — no auth required, idempotent.
If the sync fails, a warning is logged but the on-chain transaction is unaffected. Manual sync if needed:
await client.api.syncTransaction(txHash);
client.api.sync_transaction(tx_hash)
The legacy
syncLoan/sync_loanstill works but is deprecated — it delegates tosyncTransaction.
4. Production Operations
Agent Lifecycle
1. INIT → Create client, register identity, claim USDB from faucet, fund BNB for gas
2. BUILD → Develop and test strategies
3. REGISTER → Publish capabilities to ERC-8004 (after building real functionality)
4. OPERATE → Run strategies, manage positions, earn points
5. MONITOR → Watch positions, check health, handle alerts
6. RECOVER → Rebuild state after crashes, handle RPC/network failures
7. SHUTDOWN → Close positions, repay loans, unstake, withdraw
Health Checks
Run every 1–5 minutes for active agents:
async function healthCheck(client) {
const wallet = client.walletClient.account.address;
// 1. RPC connectivity
try {
const blockNumber = await client.publicClient.getBlockNumber();
} catch (e) {
console.error("RPC DOWN:", e.message);
return false; // Switch to backup RPC or alert
}
// 2. USDB balance
const usdbBalance = await client.publicClient.readContract({
address: client.usdbAddress,
abi: [{"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],
functionName: 'balanceOf',
args: [wallet],
});
// 3. BNB gas balance
const bnbBalance = await client.publicClient.getBalance({ address: wallet });
if (bnbBalance < parseUnits("0.005", 18)) {
console.warn("Low BNB - refill for gas");
}
// 4. Faucet check
try {
const faucetStatus = await client.api.getFaucetStatus();
if (faucetStatus.canClaim) console.log(`Faucet available: ${faucetStatus.dailyAmount} USDB`);
} catch (e) { /* non-critical */ }
// 5. Loan expiry
const loanCount = await client.loans.getUserLoanCount(wallet);
for (let i = 1n; i <= loanCount; i++) {
const loan = await client.loans.getUserLoanDetails(tokenAddress, wallet, i);
if (loan.active) {
const hoursLeft = (Number(loan.liquidationTime) * 1000 - Date.now()) / 3_600_000;
if (hoursLeft < 24) console.warn(`Loan ${i} expires in ${hoursLeft.toFixed(1)}h`);
}
}
return true;
}
Error Recovery Patterns
RPC Timeout / 429
async function withRetry(fn, maxRetries = 3, baseDelayMs = 1000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (e) {
const isRetryable = e.message?.includes('timeout') ||
e.message?.includes('429') ||
e.message?.includes('ECONNRESET');
if (!isRetryable || attempt === maxRetries) throw e;
const delay = baseDelayMs * Math.pow(2, attempt - 1);
await new Promise(r => setTimeout(r, delay));
}
}
}
const result = await withRetry(() => client.trading.buy(tokenAddr, amount));
Stuck Transaction
If a transaction is pending too long:
- Check the receipt first — if it exists, the transaction went through
- If still pending after 60s, resubmit with higher gas
- Never assume a timed-out transaction failed — always check the receipt before retrying
async function waitForTxSafe(client, hash, timeoutMs = 60000) {
try {
return await client.publicClient.waitForTransactionReceipt({ hash, timeout: timeoutMs });
} catch (e) {
// Timeout — check if it landed anyway
try {
const receipt = await client.publicClient.getTransactionReceipt({ hash });
if (receipt) return receipt;
} catch {}
throw new Error(`Transaction ${hash} timed out and may still be pending`);
}
}
Chain Reorg
BSC uses 3-second blocks with occasional 1–3 block reorgs:
- Wait for 3+ confirmations before treating a transaction as final (especially for market finalization, loan extensions near expiry)
- Use
waitForTransactionReceipt({ hash, confirmations: 3 })for critical operations - Do not chain time-dependent transactions in rapid succession
SIWE Session Expired (401)
For long-running agents, use API keys — they do not expire. If you hit a 401:
// Re-create client to get a fresh API key
const client = await BasisClient.create({ privateKey: process.env.BASIS_PRIVATE_KEY });
// client.apiKey is now refreshed
State Reconstruction After Crash
All position data lives on-chain. The blockchain is the source of truth. If the API is down, all positions can be read directly via RPC.
async function reconstructState(client) {
const wallet = client.walletClient.account.address;
const state = { loans: [], leveragePositions: [], staking: {} };
// 1. Active loans
const loanCount = await client.loans.getUserLoanCount(wallet);
for (let i = 1n; i <= loanCount; i++) {
const loan = await client.loans.getUserLoanDetails(tokenAddress, wallet, i);
if (loan.active) state.loans.push({ hubId: i, ...loan });
}
// 2. Leverage positions
const levCount = await client.loans.getLeverageCount(client.mainTokenAddress, wallet);
for (let i = 1n; i <= levCount; i++) {
const pos = await client.loans.getLeveragePosition(client.mainTokenAddress, wallet, i);
if (pos.active) state.leveragePositions.push({ positionId: i, ...pos });
}
// 3. Staking position
const shares = await client.publicClient.readContract({
address: client.stakingAddress,
abi: [{"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],
functionName: 'balanceOf',
args: [wallet],
});
const stasisValue = await client.staking.convertToAssets(shares);
state.staking = { shares, stasisValue };
// 4. Vesting (creator and beneficiary)
const vestingIds = await client.vesting.getVestingsByCreator(wallet);
const beneficiaryIds = await client.vesting.getVestingsByBeneficiary(wallet);
console.log(`Reconstructed: ${state.loans.length} loans, ${state.leveragePositions.length} leverage positions`);
return state;
}
RPC Configuration
The default public endpoint (bsc-dataseed.binance.org) has no SLA — ~10–20 req/s before throttling. For production agents:
Recommended providers (BSC):
- Ankr — free tier available, good BSC support
- QuickNode — fast and reliable, paid
- NodeReal — BSC-focused meganode architecture
- Chainstack — dedicated nodes available
Failover pattern:
const RPC_ENDPOINTS = [
"https://your-primary-rpc.com",
"https://bsc-dataseed1.binance.org",
"https://bsc-dataseed2.binance.org",
];
async function createClientWithFailover() {
for (const rpc of RPC_ENDPOINTS) {
try {
const client = await BasisClient.create({
privateKey: process.env.BASIS_PRIVATE_KEY,
rpcUrl: rpc,
});
return client;
} catch (e) {
console.warn(`RPC ${rpc} failed:`, e.message);
}
}
throw new Error("All RPC endpoints failed");
}
Transaction Sequencing
Always await the receipt before sending the next transaction. The SDK uses viem which manages nonces for sequential calls.
// Correct — sequential, each buy() internally awaits receipt
const tokens = ["0xToken1", "0xToken2", "0xToken3"];
for (const token of tokens) {
const result = await client.trading.buy(token, parseUnits("10", 18));
}
// Wrong — parallel sends cause nonce collisions
// await Promise.all(tokens.map(t => client.trading.buy(t, amount)));
Monitoring Checklist
| What | Method | Alert When |
|---|---|---|
| Loan expiry | getUserLoanDetails() → liquidationTime | < 24 hours remaining |
| Leverage expiry | getLeveragePosition() → liquidationTime | < 24 hours remaining |
| BNB gas balance | getBalance() | < 0.005 BNB |
| USDB operating balance | balanceOf() on USDB contract | Below your minimum threshold |
| Faucet eligibility | getFaucetStatus() | canClaim: true |
| Surge tax | getCurrentSurgeTax(token) | > 0 on actively traded tokens |
| Prediction market status | getDisputeData(marketToken) | awaiting_proposal status |
| Staking lock | Track VOTE_LOCK_DURATION after votes | Cannot unstake for 24h after voting |
| RPC health | getBlockNumber() | Timeout or stale block |
Shutdown Procedure
- Stop opening new positions (halt trading loops)
- Repay active loans before expiry (avoid collateral burn)
- Close leverage positions:
client.loans.hubPartialLoanSell(tokenAddress, loanIndex, 100)(100% = full close) - Unstake:
unlock()→sell()(only if not vote-locked) - Claim pending rewards:
claimLiquidation(hubId)for each expired loan,claimBounty(marketToken)for resolved markets - Verify final state: run
reconstructState()to confirm no orphaned positions
5. Cross-Cutting Errors
Gas Errors
GASLESS_REJECTED
- When: Any MegaFuel-sponsored transaction
- Why: Daily sponsorship limit reached (0.001 BNB/wallet/day) or contract not whitelisted
- Fix: Use your own BNB for gas, or wait until the next day
- Prevention: Keep ~0.005 BNB as fallback
Deadline expired
- When: Swap operations with a deadline parameter
- Why: Transaction took too long to mine
- Fix: Retry with a fresh deadline:
Math.floor(Date.now() / 1000) + 300 - Prevention: Set deadline 5 minutes out, increase gas for faster confirmation
Approval Errors
Approve / Approve failed / approval failed
- When: Any operation requiring token approval
- Why: Contract couldn't get spending approval (usually a gas issue)
- Fix: Manually approve the contract to spend your tokens, then retry
- Prevention: The SDK handles approvals automatically — if this appears, check gas
Transfer failed (various messages)
- When: Any operation moving tokens
- Why: Insufficient balance or missing approval
- Fix: Verify
balanceOf() >= amountand approval is sufficient
Reentrancy Guard
ReentrancyGuard: reentrant call
- When: Any contract call
- Why: A callback or hook is triggering another call to the same contract
- Fix: Don't make nested calls to the same contract. Use the SDK — it handles call sequencing correctly.
Trading / Slippage
Min out / Slippage exceeded / min shares not met
- Fix: Use
getAmountsOut(amount, path)first, setminOutto ~95% of expected output. If still failing, reduce amount.
Insufficient balance
- Fix: Check
balanceOf()before transacting
Token is frozen
- Fix: Token is whitelist-only. Check
whitelisted[yourAddress]before buying frozen tokens.
has not bonded yet / Token has already bonded
- Fix: Check
token.hasBonded. During reward phase → usebuyBondingTokens()/sellBondingTokens(). After reward phase → use AMM (buy()/sell()). Both phases run through AMMs — the methods are just different entry points.
API Errors
| Status | Meaning | Fix |
|---|---|---|
401 | Not authenticated | Re-create client with BasisClient.create() to refresh API key |
403 | Forbidden / not the owner | Verify wallet is correct (creator vs beneficiary) |
404 | Resource not found | Verify IDs and addresses |
409 | Conflict / duplicate | Resource already exists — check before creating |
429 | Rate limit exceeded | Back off with exponential retry, respect X-RateLimit-Reset |
Non-Fatal Warnings
Order sync failures after orderBook write operations are logged as warnings but do not throw. The on-chain transaction succeeds regardless. Manually re-sync with client.api.syncTransaction(txHash) if needed.
Loan Errors (Quick Reference)
| Error | Fix |
|---|---|
incorrect loan duration | Days must be 10–1000 |
Duration too short | Remaining time < 10 days — extend first, then add collateral |
Minimum loan amount not met | Check getCollateralValue() — must be ≥ ~$0.99 |
Loan not active | Loan was liquidated or repaid — check loan.active first |
Cannot liquidate before time ends | By design — no price liquidation before expiry |
Position active. Use increaseLoan | Already have a vault loan — use increaseLoan() |
% by 10 / % range | partialSell() requires multiples of 10, range 10–100 |
Prediction Market Errors (Quick Reference)
| Error | Fix |
|---|---|
Seed must be in increments of 10 USD | Round seed to nearest $10 increment |
Already proposed | Dispute if you disagree; otherwise wait for quiet period |
Must stake 5 tokens to vote | Stake ≥ 5 STASIS in resolver contract first |
Stake locked due to recent vote | Wait for lastVoteTime + VOTE_LOCK_DURATION |
market resolved | Call redeem() if you hold winning shares |
6. Error Recovery Decision Tree
Got an error?
│
├── AMOUNTS? (min, insufficient, below, seed)
│ └── Check: balance, minimum thresholds, increment rules ($10 for markets)
│
├── TIME? (duration, too short, too early, expired, deadline)
│ └── Check: loan remaining days (≥10), surge quota, vesting cliff, market phases
│
├── STATE? (already, inactive, active, resolved, bonded)
│ └── Check: current state before acting — loan.active, hasBonded, market status
│
├── PERMISSIONS? (only, unauthorized, not registered, not whitelisted)
│ └── Right wallet? (creator vs beneficiary) Authenticated? Token frozen?
│
├── SLIPPAGE? (min out, shares not met)
│ └── Simulate first. Set minOut to ~95% of expected. Reduce size if still failing.
│
└── GAS? (GASLESS_REJECTED, ETH fail)
└── Keep ~0.005 BNB fallback. Retry with own gas. Wait 24h for sponsorship reset.
Pre-Flight Checks
Before any loan operation:
- Is
loan.activetrue? - How many days remaining? (
loan.liquidationTime - now) - If remaining < 10 days: extend first
Before buying:
- Enough USDB? (
balanceOf()) - Has the token's reward phase completed? (
hasBonded→ determines which buy path) - Is the token frozen? (need whitelist)
- Simulate first (
getAmountsOut(amount, path))
Before selling:
- Enough tokens? (
balanceOf()) - wSTASIS locked by vault loan? (can't sell)
- Simulate first (
getAmountsOut(amount, path))
Before creating a market:
- Seed in $10 increments?
- Seed ≥ minimum? (differs for public vs private)
- End time in the future or 0?
Before creating a token:
- Multiplier 1–100?
- Reward-phase allocation 0–150,000 (≥1 if frozen)?
- startLP 100–10,000?
Before voting/resolving:
- Staked ≥5 STASIS in resolver?
- Market in dispute phase (
inDispute[marketToken])? - Not in veto (
inVeto[marketToken])?