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.

TradePathHops
Buy / sell STASISUSDB ↔ STASIS2-hop
Buy / sell any factory tokenUSDB ↔ STASIS ↔ Token3-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

TypePrice BehaviorFeeLeverage
Stable+ (incl. STASIS)Can only go up0.5% per swap20–36x
Floor+Free above rising floor1.5% per swapLower (floor-based LTV)
Predict+ (Stable+ subtype)Price only up (Stable+ subtype)1.5% per swap20–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

RecipientStable+Floor+ / Predict+
Creator20% of fee20% of fee
Staking yield16%16%
Reward phase buyers4%4%
Platform treasury60%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)
ParamTypeDescription
tokenAddressstringToken to buy
usdbAmountbigint/intUSDB amount (18 decimals)
minOutbigint/intMin tokens to receive (slippage guard). Default: 0
wrapTokensbooleanWrap output to wSTASIS immediately. Saves a separate wrap tx if you plan to stake. Default: false

Pre-flight checklist:

  1. Check USDB balance (client.tokens.balanceOf(usdbAddress, wallet))
  2. Check pool depth: Call client.api.getToken(tokenAddress) and compare your trade size against liquidityUSD. 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.
  3. Simulate first: getAmountsOut() to preview output
  4. Set minOut to ~95–98% of simulated output for slippage protection
  5. 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. liquidityUSD from getToken() 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:

ParamTypeDescription
tokenAddressstringToken to sell
amountbigint/intToken amount (18 decimals)
toUsdbbooleanSell all the way to USDB (3-hop). Default: false
minOutbigint/intMin output. Default: 0
swapToETHbooleanSwap to native BNB. Default: false

sellPercentage() parameters:

ParamTypeDescription
tokenAddressstringToken to sell
percentagenumber1–100
toUsdbbooleanSell to USDB. Default: false

Pre-flight checklist:

  1. Check token balance (client.tokens.balanceOf(tokenAddress, wallet))
  2. If selling wSTASIS: check it is not locked as loan collateral — locked wSTASIS cannot be sold
  3. 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 call client.loans.extendLoan(), client.loans.repayLoan(), or any client.loans method 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:

ParamTypeDescription
amountbigint/intUSDB collateral input
minOutbigint/intMin tokens to receive (use 3%+ tolerance)
pathaddress[][USDB, MAINTOKEN] for STASIS or [USDB, MAINTOKEN, factoryToken]
numberOfDaysbigint/intLoan 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 TypeEffective LeverageWhy
Stable+ (STASIS)20–36xFloor = spot (100% LTV), maximum loops
Floor+ near launchHigh (varies)Floor ≈ spot at launch — this window closes fast
Floor+ (spot >> floor)LowerLTV 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, use partialLoanSell(isLeverage=true) to take profit (see §3d below).

3d. Close / Partial Close — client.trading.partialLoanSell(isLeverage=true)

⚠️ This method lives on client.trading, NOT client.loans. Despite the name "partialLoanSell", this is a trading contract operation when isLeverage=true. Do not confuse it with client.loans.hubPartialLoanSell() which is for regular loans only. Passing 100n as 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)
ParamTypeDescription
positionIdbigint/intLeverage position index (1-indexed). Find via getLeverageCount() + getLeveragePosition() loop. Index 0 is always empty.
percentagebigint/intMust be divisible by 10 (10, 20, 30 ... 100). 100 = full close. Non-multiples silently revert.
isLeveragebooleantrue for leverage positions. false targets a regular loan (wrong contract).
minOutbigint/intMin USDB output (slippage protection). Use getTokenPrice() + slippage — never pass 0n in production.

getLeveragePosition return fields:

IndexFieldTypeDescription
0owneraddressWallet that opened the position
1collateralTokenaddressToken used as collateral
2collateralAmountuint256Amount of collateral held by the contract
4fullAmountuint256Total repay obligation
5borrowedAmountuint256Original borrowed amount
6liquidationTimeuint256Unix timestamp of expiry
8isLiquidatedboolWhether position was liquidated
9activeboolWhether position is still open
10creationTimeuint256Unix timestamp of creation
12metadataobject{ 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 to getLeverageCount() 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:

FieldDescription
totalCollateralYour total position size in tokens after all loops
totalBorrowedTotal USDB borrowed across all loops
totalFeesTotal origination fees paid (2% per loop)
totalRepayTotal you would need to repay to close the position
realLiquidityActual 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

MethodDescription
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

ActionFeeGoes To
Buy/Sell Stable+ (incl. STASIS)0.5% per swapCreator 20%, staking yield 16%, reward buyers 4%, treasury 60%
Buy/Sell Floor+ / Predict+1.5% per swapSame split (Predict+ has additional ecosystem split)
Leverage origination2% per loopLoan contract (taken automatically each loop)
Leverage interest0.005%/day per loopLoan 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.

ScenarioFloor at BuyCurrent PriceBest MoveWhy
Token ran up big$1.00$3.50Sell, repay loanCaptures full spread above entry
Token near floor$1.00$1.10Hold, extend loanFloor protects you — wait for volume
Token pulled back but floor rose$1.00 (floor now $1.80)$2.00Sell at $2.00Risen floor locked in gains through pullback
Token at floor, no volume$1.00$1.00Let loan expireMinimal 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

ErrorTriggerFix
"slippage limit reached" / "min out"buy/sell output below minOutLower minOut (~95%), reduce trade size, or retry after block
"Under 2x"leverageBuy() input too smallIncrease input (>$10 recommended)
"has not bonded yet"AMM call on reward-phase tokenCheck hasBonded() first; use reward-phase method if still in that phase
"Token has already bonded"Reward-phase call on token after reward phaseUse regular buy() / sell()
"token has been closed"Token is closed/retiredCannot trade — check token status
"Bad path" / "wrong path"Multi-hop routing errorEnsure path routes through STASIS: [USDB, MAINTOKEN, token]
"Bad pair"Invalid swap pairVerify both addresses are valid token contracts
"Insufficient balance"Any tradeCheck balanceOf() before attempting
"Contract low on liquidity"Large sell on shallow poolSell in smaller chunks; wait for more liquidity
"Trading is not enabled"Admin pausePlatform 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

SituationRecommended ActionModule
Bought STASISWrap to wSTASIS, stake for vault yield→06
Holding wSTASISBorrow USDB against it, redeploy→05
Bought Floor+ tokenBorrow against it (floor = safe collateral)→05
Want amplified exposureSimulate then leverageBuy() (terminal path)This module
Leveraged, want to take profitpartialLoanSell(isLeverage=true) in 10% incrementsThis module
Loan approaching expiryExtend cheaply (400x less than re-originate)→05
Full multi-step capital stackBuild 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