Module 04: Trading
Buy tokens, sell tokens, open leveraged positions, simulate before executing.
Prerequisites
- Wallet funded with USDB (→01), client initialized (→02)
- Understand hybrid multiplier + reward phase (→11) before sizing trades
Next steps after a trade
- Bought STASIS? Stake it (→06), then borrow against it (→05)
- Bought Floor+? Borrow against it (→05) for capital efficiency
- Leverage is terminal — use it last in any stack (→12 for full strategies)
- Hit an error? See the error reference below or SDK Reference for the full error index
1. Why Trade on BASIS
BASIS token mechanics differ fundamentally from standard AMMs:
- Stable+ tokens can only go up. Elastic supply + 100% slippage retention means every trade permanently increases the price. STASIS is the canonical Stable+. No rug is mathematically possible — every token in circulation was purchased at market price.
- Floor+ tokens have a rising floor. Price moves freely above a floor that never decreases. Sell pressure creates a dip, not a death spiral. Your downside shrinks over time as the floor rises.
- No price liquidation on leverage. Leverage positions expire by time only — price crashes cannot trigger liquidation. Leverage is a trading action (§3c below), not a loan — it has its own methods and ID system.
- All fees flow to the STASIS vault. Every trade across every token routes through STASIS. Platform volume directly benefits STASIS stakers.
- Every trade earns airdrop points. Trading volume contributes to your point accumulation; reward-phase trades earn more.
- Gas is sponsored. Up to 0.001 BNB per wallet per day via MegaFuel — small trades are economical.
2. How Trading Works
Routing
All trades route through a single SWAP contract using STASIS as the hub. There are no direct token-to-token swaps.
| Trade | Path | Hops |
|---|---|---|
| Buy / sell STASIS | USDB ↔ STASIS | 2-hop |
| Buy / sell any factory token | USDB ↔ STASIS ↔ Token | 3-hop |
Every trade on the platform — regardless of token — flows through the STASIS pool, which is why STASIS stakers earn from all platform activity.
Token Types
| Type | Price Behavior | Fee | Leverage |
|---|---|---|---|
| Stable+ (incl. STASIS) | Can only go up | 0.5% per swap | 20–36x |
| Floor+ | Free above rising floor | 1.5% per swap | Lower (floor-based LTV) |
| Predict+ (Stable+ subtype) | Price only up (Stable+ subtype) | 1.5% per swap | 20–36x |
Two-Phase Lifecycle
Tokens are tradeable from creation via the hybrid AMM. During the reward phase (initial period), early buyers earn reward shares claimable via claimRewards() and boosted airdrop points. After the reward phase, trading continues normally — the AMM mechanics don't change, reward shares just stop accruing on new buys.
AMM Pricing
BASIS uses a modified constant-product AMM where the hybridMultiplier (1–90 for Floor+, or exactly 100 for Stable+) changes the pricing formula for both buys and sells. This is not a standard AMM with a bolt-on sell mechanism — the multiplier modifies the core formula itself, so every trade behaves differently from a traditional AMM.
- Stable+ (multiplier=100): Maximum retention on every trade. Price can only go up — both buys and sells increase the price.
- Floor+ (multiplier=1–90): Partial retention — value is retained in the pool on every trade, creating a rising floor. Even at multiplier 1, tokens are dramatically more stable than any traditional AMM.
Implication for agents: Standard AMM arbitrage assumptions do not apply. On Stable+ tokens, price increases on every trade regardless of direction. On Floor+ tokens, the floor rises with every trade. Model your strategies around these mechanics.
Fee Distribution
| Recipient | Stable+ | Floor+ / Predict+ |
|---|---|---|
| Creator | 20% of fee | 20% of fee |
| Staking yield | 16% | 16% |
| Reward phase buyers | 4% | 4% |
| Platform treasury | 60% | 60% |
Predict+ tokens have an additional split: 2/3 of the fee goes to the prediction ecosystem (bounty + winning pot); the creator gets 20% of the remaining 1/3.
3. Actions
3a. Buy — client.trading.buy()
// JS
const result = await client.trading.buy("0xTokenAddress", parseUnits("5", 18));
// With slippage protection (recommended)
const preview = await client.trading.getAmountsOut(parseUnits("5", 18), [USDB, MAINTOKEN, TOKEN]);
const minOut = preview * 98n / 100n; // 2% tolerance
const result = await client.trading.buy(TOKEN, parseUnits("5", 18), minOut);
# Python
result = client.trading.buy("0xTokenAddress", 5 * 10**18)
# With slippage protection
preview = client.trading.get_amounts_out(5 * 10**18, [USDB, MAINTOKEN, TOKEN])
min_out = preview * 98 // 100
result = client.trading.buy(TOKEN, 5 * 10**18, min_out)
| Param | Type | Description |
|---|---|---|
tokenAddress | string | Token to buy |
usdbAmount | bigint/int | USDB amount (18 decimals) |
minOut | bigint/int | Min tokens to receive (slippage guard). Default: 0 |
wrapTokens | boolean | Wrap output to wSTASIS immediately. Saves a separate wrap tx if you plan to stake. Default: false |
Pre-flight checklist:
- Check USDB balance (
client.tokens.balanceOf(usdbAddress, wallet)) - Check pool depth: Call
client.api.getToken(tokenAddress)and compare your trade size againstliquidityUSD. If your trade exceeds 5% of pool depth, run the impact probe (Section 7) or split into smaller trades. Elastic supply AMMs are more sensitive to large orders than traditional AMMs — use 5% as the sizing threshold. - Simulate first:
getAmountsOut()to preview output - Set
minOutto ~95–98% of simulated output for slippage protection - Check for active surge tax:
client.taxes.getCurrentSurgeTax(tokenAddress)— creators can activate temporary extra fees up to 15% on low-multiplier Floor+ tokens
What happens: USDB → SWAP contract → tokens minted (elastic supply) and transferred. Taxes distributed. If Stable+, price increases permanently. If buying during reward phase, you earn reward shares claimable via claimRewards() (→07).
Pool depth ≠ tradability. BASIS uses elastic supply — tokens are minted on buy and burned on sell. There are no traditional liquidity pools to run dry. All factory tokens are tradeable from creation.
liquidityUSDfromgetToken()represents the virtual pool depth that determines price impact, not whether trading is possible. A token with $100 liquidity is still fully tradeable — your price impact will just be higher per dollar spent.
Fee: 0.5% for Stable+/STASIS, 1.5% for Floor+/Predict+
→ For trades exceeding 5% of pool depth, see Section 7 (Position Sizing) before executing. Elastic supply AMMs are more sensitive to large orders than traditional AMMs. Large single buys on shallow pools move the price significantly — a 30% pool depth trade can cause 35%+ price impact. Probe first, split if needed.
3b. Sell — client.trading.sell() and sellPercentage()
// JS — sell exact amount
const result = await client.trading.sell("0xTokenAddress", parseUnits("1", 18), true); // true = to USDB
// JS — sell percentage (reads balance automatically)
const result = await client.trading.sellPercentage("0xTokenAddress", 50); // sell 50%
# Python — sell exact amount
result = client.trading.sell("0xTokenAddress", 1 * 10**18, to_usdb=True)
# Python — sell percentage
result = client.trading.sell_percentage("0xTokenAddress", 50)
sell() parameters:
| Param | Type | Description |
|---|---|---|
tokenAddress | string | Token to sell |
amount | bigint/int | Token amount (18 decimals) |
toUsdb | boolean | Sell all the way to USDB (3-hop). Default: false |
minOut | bigint/int | Min output. Default: 0 |
swapToETH | boolean | Swap to native BNB. Default: false |
sellPercentage() parameters:
| Param | Type | Description |
|---|---|---|
tokenAddress | string | Token to sell |
percentage | number | 1–100 |
toUsdb | boolean | Sell to USDB. Default: false |
Pre-flight checklist:
- Check token balance (
client.tokens.balanceOf(tokenAddress, wallet)) - If selling wSTASIS: check it is not locked as loan collateral — locked wSTASIS cannot be sold
- Preview with
getAmountsOut()to estimate output
Token-specific sell behavior:
- Stable+: You CAN sell, but price stays up for other holders. Slippage on exit is the only downside.
- Floor+: Price dips on sell, but floor holds. Other holders are protected from cascade selling.
Sell vs Borrow decision:
- Sell when the price is right and you want to lock in profit or fully exit the position
- Borrow when you want liquidity but still believe in the upside — keeps your exposure intact
- Good operators use both. Borrowing is powerful but delaying an exit on a peaked token is not a strategy.
3c. Leverage Buy — client.trading.leverageBuy()
⚠️ Leverage is a trading action, not a loan.
leverageBuy()creates an amplified position in one atomic transaction. Internally it uses recursive loans, but you never touch those loans directly. Do NOT callclient.loans.extendLoan(),client.loans.repayLoan(), or anyclient.loansmethod on a leverage position — they use different ID systems and will fail or act on the wrong position. Manage leverage exclusively through the methods in this section (3c and 3d).
Always simulate before executing.
// JS — Step 1: Simulate
const sim = await client.leverageSimulator.simulateLeverage(
parseUnits("10", 18),
[USDB, MAINTOKEN],
10n // min 10 days
);
console.log("Total collateral:", sim.totalCollateral);
console.log("Total borrowed:", sim.totalBorrowed);
console.log("Total fees:", sim.totalFees);
// Step 2: Execute with slippage protection
const expected = await client.trading.getAmountsOut(parseUnits("10", 18), [USDB, MAINTOKEN]);
const minOut = expected * 97n / 100n; // 3% tolerance for leverage
const result = await client.trading.leverageBuy(parseUnits("10", 18), minOut, [USDB, MAINTOKEN], 10n);
// Step 3: Wait for backend sync (~5s) before calling partialLoanSell
await new Promise(r => setTimeout(r, 5000));
# Python
sim = client.leverage_simulator.simulate_leverage(10 * 10**18, [USDB, MAINTOKEN], 10)
print(f"Collateral: {sim.totalCollateral}, Fees: {sim.totalFees}, Borrowed: {sim.totalBorrowed}")
expected = client.trading.get_amounts_out(10 * 10**18, [USDB, MAINTOKEN])
min_out = expected * 97 // 100
result = client.trading.leverage_buy(10 * 10**18, min_out, [USDB, MAINTOKEN], 10)
import time; time.sleep(5)
For a factory token (3-hop path):
// Use simulateLeverageFactory for factory tokens
const sim = await client.leverageSimulator.simulateLeverageFactory(
parseUnits("10", 18),
[USDB, MAINTOKEN, "0xFactoryToken..."],
10n
);
const result = await client.trading.leverageBuy(
parseUnits("10", 18), minOut,
[USDB, MAINTOKEN, "0xFactoryToken..."],
10n
);
leverageBuy() parameters:
| Param | Type | Description |
|---|---|---|
amount | bigint/int | USDB collateral input |
minOut | bigint/int | Min tokens to receive (use 3%+ tolerance) |
path | address[] | [USDB, MAINTOKEN] for STASIS or [USDB, MAINTOKEN, factoryToken] |
numberOfDays | bigint/int | Loan duration. Min 10, max 1000 |
How it works internally (you don't need to manage this):
$10 USDB → buy tokens → internal loan on those tokens → get ~$9.80 back
→ buy more tokens → internal loan → get ~$9.60 back
→ repeat automatically until dust remains
The contract handles the entire recursive loop in a single atomic transaction. Either it fully succeeds or fully fails — no half-built positions. The individual loans created during this loop are internal implementation details — you interact with the result as a single leverage position, not as separate loans.
Leverage characteristics:
| Token Type | Effective Leverage | Why |
|---|---|---|
| Stable+ (STASIS) | 20–36x | Floor = spot (100% LTV), maximum loops |
| Floor+ near launch | High (varies) | Floor ≈ spot at launch — this window closes fast |
| Floor+ (spot >> floor) | Lower | LTV calculated against floor, not spot — fewer effective loops |
Leverage sizing rules:
- Smaller positions on deep pools = more loops = higher leverage
- Larger positions = fewer effective loops (price impact reduces each loan yield)
- Minimum input for meaningful 2x+: generally >$10
- Rule: Always take minimum duration (10 days) and extend. Extensions cost 0.005%/day vs 2% to re-originate.
What happens on expiry:
- Stable+: Tokens are burned to cover the internal debt. Since price only goes up, debt is always covered. Remaining tokens are claimable.
- Floor+: Tokens are sold on market to cover the internal debt. Since debt is based on floor price, the amount sold is typically small if the token appreciated.
- Worst case: no price increase, entire position consumed by debt, nothing left. You never owe anything beyond your collateral. No margin calls.
- You do NOT repay leverage manually. There is no
repayLoan()call for leverage. On expiry, the contract auto-settles. Before expiry, usepartialLoanSell(isLeverage=true)to take profit (see §3d below).
3d. Close / Partial Close — client.trading.partialLoanSell(isLeverage=true)
⚠️ This method lives on
client.trading, NOTclient.loans. Despite the name "partialLoanSell", this is a trading contract operation whenisLeverage=true. Do not confuse it withclient.loans.hubPartialLoanSell()which is for regular loans only. Passing100nas percentage fully closes the position — the "partial" in the name refers to the ability to close in 10% increments, not a constraint.
Close all or part of a leveraged position.
// JS — Read positions (1-indexed: loop from 1 to count inclusive)
const count = await client.trading.getLeverageCount(wallet);
for (let i = 1n; i <= count; i++) {
const pos = await client.trading.getLeveragePosition(wallet, i);
if (pos[9]) { // pos[9] = active flag
console.log(`Active position ${i}: ${pos[2]} tokens of ${pos[1]}`);
}
}
// Close a position — 100n = full close, or use 10-90 for partial
const positionId = 1n; // 1-indexed — find via the loop above
const position = await client.trading.getLeveragePosition(wallet, positionId);
// Preview sell output for slippage protection
const sellPreview = await client.trading.getAmountsOut(position[2], [MAINTOKEN, USDB]);
const sellMinOut = sellPreview * 98n / 100n;
const result = await client.trading.partialLoanSell(positionId, 100n, true, sellMinOut);
# Python — Read positions (1-indexed)
count = client.trading.get_leverage_count(wallet)
for i in range(1, count + 1):
pos = client.trading.get_leverage_position(wallet, i)
if pos[9]: # active flag
print(f"Active position {i}: {pos[2]} tokens of {pos[1]}")
# Close a position
position_id = 1
position = client.trading.get_leverage_position(wallet, position_id)
sell_preview = client.trading.get_amounts_out(int(position[2]), [MAINTOKEN, USDB])
sell_min_out = sell_preview * 98 // 100
result = client.trading.partial_loan_sell(position_id, 100, True, sell_min_out)
| Param | Type | Description |
|---|---|---|
positionId | bigint/int | Leverage position index (1-indexed). Find via getLeverageCount() + getLeveragePosition() loop. Index 0 is always empty. |
percentage | bigint/int | Must be divisible by 10 (10, 20, 30 ... 100). 100 = full close. Non-multiples silently revert. |
isLeverage | boolean | true for leverage positions. false targets a regular loan (wrong contract). |
minOut | bigint/int | Min USDB output (slippage protection). Use getTokenPrice() + slippage — never pass 0n in production. |
getLeveragePosition return fields:
| Index | Field | Type | Description |
|---|---|---|---|
| 0 | owner | address | Wallet that opened the position |
| 1 | collateralToken | address | Token used as collateral |
| 2 | collateralAmount | uint256 | Amount of collateral held by the contract |
| 4 | fullAmount | uint256 | Total repay obligation |
| 5 | borrowedAmount | uint256 | Original borrowed amount |
| 6 | liquidationTime | uint256 | Unix timestamp of expiry |
| 8 | isLiquidated | bool | Whether position was liquidated |
| 9 | active | bool | Whether position is still open |
| 10 | creationTime | uint256 | Unix timestamp of creation |
| 12 | metadata | object | { leverageBuyAmount, cashedOut } |
Critical: Wait ~5 seconds after leverageBuy() before calling partialLoanSell() — the backend needs time to sync the position state.
Troubleshooting: Position returns all zeros? Leverage positions are 1-indexed. If
getLeveragePosition(wallet, 0)returns all zeros, try index 1. Loop from 1 togetLeverageCount()inclusive — index 0 is always empty/cleared.
4. Simulate Before Executing
All simulation methods are read-only and free to call.
Price Preview
// Preview output for any swap — returns a single bigint (scalar), not an array
const expectedOut = await client.trading.getAmountsOut(parseUnits("5", 18), [USDB, MAINTOKEN, TOKEN]);
expected_out = client.trading.get_amounts_out(5 * 10**18, [USDB, MAINTOKEN, TOKEN])
# Returns a single int, not a list
Leverage Simulation
// STASIS path (2-hop)
const sim = await client.leverageSimulator.simulateLeverage(amount, [USDB, MAINTOKEN], days);
// Factory token path (3-hop)
const sim = await client.leverageSimulator.simulateLeverageFactory(amount, [USDB, MAINTOKEN, TOKEN], days);
simulateLeverage() key return fields:
| Field | Description |
|---|---|
totalCollateral | Your total position size in tokens after all loops |
totalBorrowed | Total USDB borrowed across all loops |
totalFees | Total origination fees paid (2% per loop) |
totalRepay | Total you would need to repay to close the position |
realLiquidity | Actual pool liquidity used in the simulation |
Position Inspection
// Current prices
const usdPrice = await client.trading.getUSDPrice(tokenAddress);
const stasisPrice = await client.trading.getTokenPrice(tokenAddress); // denominated in STASIS
// Existing leverage positions (1-indexed: loop 1 to count inclusive)
const count = await client.trading.getLeverageCount(walletAddress); // 1 param only — no token address
for (let i = 1n; i <= count; i++) {
const pos = await client.trading.getLeveragePosition(walletAddress, i); // 2 params — no token address
if (pos[9]) console.log(`Position ${i}: ${pos[2]} of ${pos[1]}, expires ${pos[6]}`);
}
Additional Simulator Methods
| Method | Description |
|---|---|
calculateFloor(hybridMultiplier, reserve0, reserve1, baseReserve0, xereserve0, xereserve1) | Floor price for a Floor+ token given current reserves |
getCollateralValue(tokenAmount, reserve0, reserve1) | USDB value of tokens at current reserves — compare vs borrowedAmount for position health |
getCollateralValueHybrid(...) | Collateral value for hybrid tokens with elastic reserve calculations |
calculateTokensForBuy(usdbAmount, reserve0, reserve1) | Tokens received for a given USDB input at current reserves |
5. Fees
| Action | Fee | Goes To |
|---|---|---|
| Buy/Sell Stable+ (incl. STASIS) | 0.5% per swap | Creator 20%, staking yield 16%, reward buyers 4%, treasury 60% |
| Buy/Sell Floor+ / Predict+ | 1.5% per swap | Same split (Predict+ has additional ecosystem split) |
| Leverage origination | 2% per loop | Loan contract (taken automatically each loop) |
| Leverage interest | 0.005%/day per loop | Loan contract |
| Surge tax (if active) | Variable (up to 15%) | Anti-dump mechanism — check before trading |
Round-trip cost reality:
- Stable+: ~1% raw (buy + sell), plus slippage
- Floor+/Predict+: ~3% raw (buy + sell), plus slippage
- Leverage: Effective total fee depends on number of loops. Simulate first to see
totalFees.
Loan duration rule: Always take minimum duration (10 days) and extend as needed. A 100-day loan costs 2% + 0.5% = 2.5%. A 10-day loan extended 90 days costs the same 2.5% but gives you the option to exit early without re-originating.
6. Floor+ Profit Scenarios
Floor+ tokens are the most interesting to unwind. The floor rises over time, which can lock in gains even after price pullbacks.
| Scenario | Floor at Buy | Current Price | Best Move | Why |
|---|---|---|---|---|
| Token ran up big | $1.00 | $3.50 | Sell, repay loan | Captures full spread above entry |
| Token near floor | $1.00 | $1.10 | Hold, extend loan | Floor protects you — wait for volume |
| Token pulled back but floor rose | $1.00 (floor now $1.80) | $2.00 | Sell at $2.00 | Risen floor locked in gains through pullback |
| Token at floor, no volume | $1.00 | $1.00 | Let loan expire | Minimal loss — floor preserved your downside |
Key insight: Loan LTV is calculated against the floor price, but you sell at the market price. When a Floor+ token runs up, there is a large spread between what you owe (floor-based) and what the token is actually worth. Selling captures that entire spread.
Leverage exit example: $100 input → $800 position at $1.00. Token rises to $2.00. Position worth ~$1,600, debt ~$700. Profit: ~$900 from $100 input. Use partialLoanSell() to scale out in 10% increments without closing the whole position.
7. Position Sizing
Large single buys on shallow pools move the price significantly. Always probe before committing.
// Probe price impact before entering
const testAmount = targetAmount / 100n; // 1% probe
const testOut = await client.trading.getAmountsOut(testAmount, path);
const testRate = testOut * 100n / testAmount;
const fullOut = await client.trading.getAmountsOut(targetAmount, path);
const fullRate = fullOut * 100n / targetAmount;
const impactBps = (testRate - fullRate) * 10000n / testRate; // basis points
// < 50bp (0.5%): good, standard trade
// 50–200bp (0.5–2%): acceptable for conviction plays
// > 200bp (2%+): split into multiple smaller trades
Factors affecting impact:
startLP(creator-set virtual depth parameter — NOT deposit capital) determines pool depth — higher = less impact per trade- Stable+ pools only grow over time (100% slippage retention) — impact decreases as the token matures
- Floor+ pools grow more slowly — impact decreases but less predictably
- All factory token trades also route through the STASIS pool, so STASIS pool depth matters too
Get token details before trading:
// Use getToken(address) to inspect liquidityUSD, multiplier, startingLiquidityUSD
// liquidityUSD is the current pool depth — use it to size trades
8. Complete Examples
Example: Buy, Check Balance, Sell 50%
const { BasisClient } = require("basis-sdk-js");
async function tradeTokens() {
const client = await BasisClient.create({ privateKey: "0xYourKey..." });
const TOKEN = "0xTokenAddress...";
// Check price
const price = await client.trading.getUSDPrice(TOKEN);
console.log("Price:", price, "USD");
// Preview
const fiveUsdb = parseUnits("5", 18);
const preview = await client.trading.getAmountsOut(fiveUsdb, [
client.usdbAddress, client.mainTokenAddress, TOKEN
]);
// Buy with 2% slippage tolerance
const minOut = preview * 98n / 100n;
try {
const buy = await client.trading.buy(TOKEN, fiveUsdb, minOut);
console.log("Bought:", buy.hash);
} catch (e) {
if (e.message.includes("slippage")) {
// Retry with 5% tolerance
const retryMin = preview * 95n / 100n;
const buy = await client.trading.buy(TOKEN, fiveUsdb, retryMin);
console.log("Bought (retry):", buy.hash);
} else throw e;
}
// Sell 50% — reads balance automatically
const sell = await client.trading.sellPercentage(TOKEN, 50);
console.log("Sold 50%:", sell.hash);
}
from basis_sdk import BasisClient
def trade_tokens():
client = BasisClient.create(private_key="0xYourKey...")
TOKEN = "0xTokenAddress..."
price = client.trading.get_usd_price(TOKEN)
print("Price:", price, "USD")
FIVE_USDB = 5 * 10**18
preview = client.trading.get_amounts_out(FIVE_USDB, [
client.usdb_address, client.main_token_address, TOKEN
])
min_out = preview * 98 // 100 # 2% slippage tolerance
buy = client.trading.buy(TOKEN, FIVE_USDB, min_out)
print("Bought:", buy["hash"])
sell = client.trading.sell_percentage(TOKEN, 50)
print("Sold 50%:", sell["hash"])
Example: Leverage — Simulate, Open, Partial Close
const { BasisClient } = require("basis-sdk-js");
async function leverageTrading() {
const client = await BasisClient.create({ privateKey: "0xYourKey..." });
const USDB = client.usdbAddress;
const MAINTOKEN = client.mainTokenAddress;
const path = [USDB, MAINTOKEN];
// 1. Simulate
const sim = await client.leverageSimulator.simulateLeverage(parseUnits("10", 18), path, 10n);
console.log("Collateral:", sim.totalCollateral, "Fees:", sim.totalFees, "Borrowed:", sim.totalBorrowed);
// 2. Open with slippage protection (min 10 days)
const expected = await client.trading.getAmountsOut(parseUnits("10", 18), path);
const minOut = expected * 97n / 100n; // 3% tolerance for leverage
const open = await client.trading.leverageBuy(parseUnits("10", 18), minOut, path, 10n);
console.log("Opened:", open.hash);
// 3. Wait for backend sync
await new Promise(r => setTimeout(r, 5000));
// 4. Get position (1-indexed — loop from 1 to count inclusive)
const wallet = client.walletClient.account.address;
const count = await client.trading.getLeverageCount(wallet); // 1 param only
// Find the active position
let activeId;
for (let i = 1n; i <= count; i++) {
const pos = await client.trading.getLeveragePosition(wallet, i); // 2 params only
if (pos[9]) { activeId = i; break; } // pos[9] = active flag
}
const position = await client.trading.getLeveragePosition(wallet, activeId);
console.log("Position:", position);
// 5. Partial close — 50% (must be multiple of 10, 100 = full close)
const sellAmt = position[2] / 2n; // pos[2] = collateralAmount
const sellPreview = await client.trading.getAmountsOut(sellAmt, [MAINTOKEN, USDB]);
const sellMin = sellPreview * 98n / 100n;
const close = await client.trading.partialLoanSell(activeId, 50n, true, sellMin);
console.log("Partially closed:", close.hash);
}
import time
from basis_sdk import BasisClient
def leverage_trading():
client = BasisClient.create(private_key="0xYourKey...")
USDB = client.usdb_address
MAINTOKEN = client.main_token_address
path = [USDB, MAINTOKEN]
sim = client.leverage_simulator.simulate_leverage(10 * 10**18, path, 10)
print(f"Collateral: {sim.totalCollateral}, Fees: {sim.totalFees}")
expected = client.trading.get_amounts_out(10 * 10**18, path)
min_out = expected * 97 // 100 # 3% tolerance
open_result = client.trading.leverage_buy(10 * 10**18, min_out, path, 10)
print("Opened:", open_result["hash"])
time.sleep(5) # Wait for backend sync
count = client.trading.get_leverage_count(client.wallet_address) # 1 param only
# Find active position (1-indexed)
active_id = None
for i in range(1, count + 1):
pos = client.trading.get_leverage_position(client.wallet_address, i) # 2 params only
if pos[9]: # active flag
active_id = i
break
position = client.trading.get_leverage_position(client.wallet_address, active_id)
sell_preview = client.trading.get_amounts_out(int(position[2]) // 2, [MAINTOKEN, USDB])
sell_min = sell_preview * 98 // 100
close = client.trading.partial_loan_sell(active_id, 50, True, sell_min)
print("Partially closed:", close["hash"])
9. Error Reference
| Error | Trigger | Fix |
|---|---|---|
"slippage limit reached" / "min out" | buy/sell output below minOut | Lower minOut (~95%), reduce trade size, or retry after block |
"Under 2x" | leverageBuy() input too small | Increase input (>$10 recommended) |
"has not bonded yet" | AMM call on reward-phase token | Check hasBonded() first; use reward-phase method if still in that phase |
"Token has already bonded" | Reward-phase call on token after reward phase | Use regular buy() / sell() |
"token has been closed" | Token is closed/retired | Cannot trade — check token status |
"Bad path" / "wrong path" | Multi-hop routing error | Ensure path routes through STASIS: [USDB, MAINTOKEN, token] |
"Bad pair" | Invalid swap pair | Verify both addresses are valid token contracts |
"Insufficient balance" | Any trade | Check balanceOf() before attempting |
"Contract low on liquidity" | Large sell on shallow pool | Sell in smaller chunks; wait for more liquidity |
"Trading is not enabled" | Admin pause | Platform paused — wait and retry |
Reward Phase State Mismatch Recovery
// Check reward phase state before trading
const tokenInfo = await client.factory.getToken(tokenAddress);
const isBonded = tokenInfo.hasBonded; // true = reward phase completed
if (isBonded) {
// Use regular buy/sell
await client.trading.buy(tokenAddress, amount, minOut);
} else {
// Token still in reward phase — use reward-phase methods
// Check SDK docs for reward-phase buy methods
}
10. Strategy Context
| Situation | Recommended Action | Module |
|---|---|---|
| Bought STASIS | Wrap to wSTASIS, stake for vault yield | →06 |
| Holding wSTASIS | Borrow USDB against it, redeploy | →05 |
| Bought Floor+ token | Borrow against it (floor = safe collateral) | →05 |
| Want amplified exposure | Simulate then leverageBuy() (terminal path) | This module |
| Leveraged, want to take profit | partialLoanSell(isLeverage=true) in 10% increments | This module |
| Loan approaching expiry | Extend cheaply (400x less than re-originate) | →05 |
| Full multi-step capital stack | Build paths A→B→C→E for maximum efficiency | →12 |
The core insight from stacking strategies: Buying a token does not lock your capital. Borrow against almost any position to get USDB back, then deploy it again. Three stacked paths is the sweet spot — beyond that, fees erode the bag meaningfully. Leverage (Path E) is always the terminal step: it fully deploys your remaining USDB and does not return capital for further stacking.
See Also
- Module 05 — Lending: Borrow against any position you bought here
- Module 06 — Staking: Wrap STASIS to wSTASIS for vault yield
- Module 11 — Token Mechanics: hybrid multiplier, floor math, reward phase internals
- Module 12 — Strategy & Stacking: Chain buy → stake → borrow → leverage for full efficiency
- Module 18 — SDK Reference: Full method signatures and error index
- Module 19 — FAQ: Conversational answers on trading, slippage, leverage