Reference

Motoswap SDK - Full Public API Inventory

Every exported function of @motoswap/sdk, with types and formulas.

The @motoswap/sdk package is a TypeScript library of pure functions used by the Motoswap backend and frontend. All math is deterministic; there is no network I/O and no wallet state.

Package status

Package name: @motoswap/sdk.

Availability: not published yet. The package is not on any public registry and there is no install path for an outside integrator. Use this page as the specification: every function below states its formula, and each is a few lines of bigint math. The samples show call shapes and cannot run until the package is published.

Dependencies:

  • viem (^2.21.0) - address utilities and type definitions
  • @noble/hashes - keccak for grindVanitySalt
  • @motoswap/shared (workspace) - domain types (Address, VaultId, LockPosition, etc.) and constants

Imports:

import {
  bestRoute,
  getAmountsOut,
  bestSplitRoute,
  bestMidRate,
  pathMidRate,
  splitMidRate,
  optimalSwapInput,
  getAmountOut,
  sqrtBigInt,
  effectiveStake,
  isUnlocked,
  summarizeUserPositions,
  computeMasterChefPoolAPR,
  computeVaultAPR,
  priceFromReserves,
  toUsd,
  amountOutMin,
  submitAmountOutMin,
  amountInMax,
  midPriceBps,
  executionPriceBps,
  priceImpactBps,
  grindVanitySalt,
  type Hop,
  type MidRate,
  type RoutePair,
  type RouteResult,
  type SplitLeg,
  type SplitRouteResult,
  type GrindVanityParams,
  type VanityResult,
} from "@motoswap/sdk";

That is the whole export list. BPS_DENOMINATOR is used inside the slippage module but is not re-exported. Its value is 10_000n; define it yourself, since @motoswap/shared is not published either.

Build: TypeScript + Bun (bun test), exports as ES modules (.ts entry in package.json).


routing.ts - Multi-hop swap routing and price discovery

bestRoute<A extends string>(tokenIn, tokenOut, pairs, amountIn, maxHops, feeBps?)

Find the highest-output path from one token to another across a curated set of pairs, within a hop limit. Generic over address type (preserves Address branding). Returns null if tokens are identical or no path exists. Enumerates all simple paths up to maxHops edges, prices each via getAmountsOut, returns argmax by output (ties keep shorter paths). The backend and frontend both use this for identical routing logic.

  • tokenIn, tokenOut: A - branded address type (typically Address).
  • pairs: readonly RoutePair<A>[] - the routing graph; must include all candidate pairs (e.g., direct pairs + hub routes).
  • amountIn: bigint - exact input amount in base units.
  • maxHops: number - maximum edges to traverse (e.g., 2 for TOKEN → HUB → WETH).
  • feeBps?: bigint - per-pool LP swap fee in basis points; defaults to SWAP_FEE_BPS (30 bps). It must not fold in the once-per-trade FeeRouter protocol fee: pricing a multi-hop route at a combined bps-per-hop double-counts that fee and mis-ranks routes against a direct swap. The protocol fee is netted once by the caller, not per hop.

Returns: RouteResult<A> | null - `{ path: A[], amountOut: bigint, hops: number }`.

const route = bestRoute(tokenA, tokenB, pairs, 1_000_000n, 2);
if (route) {
  console.log(route.path);       // [tokenA, hub, tokenB]
  console.log(route.amountOut);  // final output in base units
  console.log(route.hops);       // e.g. 2
}

getAmountsOut(amountIn, hops, feeBps?)

Chains getAmountOut across a path's hops (Uniswap V2 style), returning per-hop intermediates. If any hop is dry (non-positive reserve), collapses the rest to zero rather than throwing.

  • amountIn: bigint - starting amount in base units.
  • hops: readonly Hop[] - array of `{ reserveIn, reserveOut }` pairs, one per edge, oriented in swap direction.
  • feeBps?: bigint - per-hop swap fee in basis points; defaults to SWAP_FEE_BPS (30 bps).

Returns: bigint[] with length = hops.length + 1, where result[0] = amountIn.

bestSplitRoute<A>(tokenIn, tokenOut, pairs, amountIn, maxHops, minImprovementBps?, feeBps?)

Find a two-leg split order when it beats the best single path (e.g., a token whose liquidity sits half in TOKEN/WETH and half in TOKEN/MOTO). It only combines paths that share no pool. Ternary-searches the split fraction; returns null if improvement < minImprovementBps (default 25 bps) or fewer than 2 viable paths exist, or when amountIn <= 1n.

  • minImprovementBps?: number - a plain number, default 25.
  • feeBps?: bigint - per-pool LP fee, default SWAP_FEE_BPS.

Returns: SplitRouteResult<A> | null:

{
  legs: [SplitLeg<A>, SplitLeg<A>],
  amountOut: bigint,       // combined
  singleOut: bigint,       // best single-path for comparison
  improvementBps: number,  // e.g. 150 = 1.5% better
}

SplitLeg<A> = `{ path: A[], amountIn: bigint, amountOut: bigint }`.

bestMidRate<A>(tokenIn, tokenOut, pairs, maxHops)

Fee-free reference price (MID price) chained across the best route. This is the route-selection reference: which path has the best marginal rate. Returned as an exact rational `{ num, den }` so you preserve precision. Returns MidRate | null. To grade the impact of a trade, use pathMidRate or splitMidRate on the path it actually executed on.

pathMidRate<A>(path, pairs, feeBps)

Mid rate of one specific path, chained hop by hop. This is the reference a trade actually executed on that path must be graded against. Grading against bestMidRate is wrong when the best-mid path is a thin, skewed pool: the reference becomes a rate no real size can fill at and the reported impact inflates. Returns MidRate | null (null when a hop is missing or dry).

  • path: readonly A[] - the token sequence, at least 2 entries.
  • pairs: readonly RoutePair<A>[] - pairs covering every hop in path.
  • feeBps: bigint - required, no default. With a positive value each hop's LP fee is folded into the rate, so an impact graded against it measures size only and does not grow with hop count. Pass 0n for the raw fee-free rate.

splitMidRate<A>(legs, pairs, feeBps)

Amount-weighted mid rate across a split order's legs, (Σ amountIn_i × mid_i) / Σ amountIn_i, exact-rational. Grades a combined split execution on the same scale as a single-path impact. Returns MidRate | null (null on an empty leg set, a non-positive amountIn, or any leg whose pathMidRate is null).

  • legs: readonly { path: readonly A[]; amountIn: bigint }[] - the split legs, shaped like bestSplitRoute's output.
  • pairs: readonly RoutePair<A>[] - pairs covering every hop in every leg.
  • feeBps: bigint - required, no default. Same meaning as on pathMidRate, applied inside each leg before the amount weighting.

Types:

  • Hop = { reserveIn: bigint; reserveOut: bigint }
  • RoutePair<A> = { token0: A; token1: A; reserve0: bigint; reserve1: bigint }
  • RouteResult<A> = { path: A[]; amountOut: bigint; hops: number }
  • MidRate = { num: bigint; den: bigint }
  • SplitLeg<A> = { path: A[]; amountIn: bigint; amountOut: bigint }
  • SplitRouteResult<A> = { legs: [SplitLeg<A>, SplitLeg<A>]; amountOut: bigint; singleOut: bigint; improvementBps: number }

zap.ts - Single-sided liquidity math

optimalSwapInput(amountIn, reserveIn, feeBps)

Closed-form optimal amount to swap in a constant-product zap, so the swap output plus remaining input combine into liquidity with near-zero dust. Output is independent of the pool's output reserve. Matches the on-chain MotoSwapZap._getSwapAmount when fees are in sync.

  • amountIn: bigint - total single-token input (base units).
  • reserveIn: bigint - pool reserve of input token (must be positive).
  • feeBps: bigint - input-side swap fee. Required, no default. Pass the pool's live factory.swapFeeBps(), or SWAP_FEE_BPS (30 bps).
const toSwap = optimalSwapInput(10_000_000n, 1_000_000_000n, SWAP_FEE_BPS);
const toAdd  = 10_000_000n - toSwap;

getAmountOut(amountIn, reserveIn, reserveOut, feeBps)

Standard constant-product output for a swap with an input-side fee. Used internally by optimalSwapInput and exposed for simulation callers.

Formula: amountOut = (amountInWithFee * reserveOut) / (reserveIn * FEE_DENOMINATOR + amountInWithFee) where amountInWithFee = amountIn * (10_000 - feeBps).

feeBps is required, no default - pass the pool's live factory.swapFeeBps(), or SWAP_FEE_BPS (30 bps). Throws RangeError if either reserve is non-positive.

sqrtBigInt(value)

Integer square root via Newton's method (floor). Throws RangeError on negative input.


pricing.ts - Token pricing utilities

priceFromReserves({ tokenReserve, stableReserve, tokenDecimals, stableDecimals })

Spot price of a token in USD, derived from a pool's reserves against a stablecoin reference (assumed ~$1). Reserves go in as bigint. Returns USD per whole token as a JS number, which is display precision only: never feed the result back into amount math.

Formula: (stableReserve / 10^stableDecimals) / (tokenReserve / 10^tokenDecimals)

Returns 0 if tokenReserve is zero.

toUsd(amount, decimals, priceUsd)

USD value of a base-unit token amount given a spot price. (amount / 10^decimals) * priceUsd. amount is a bigint; decimals and priceUsd are numbers; the result is a display number.


apr.ts - APR math for farm/pool

computeMasterChefPoolAPR(motoPerBlock, allocPoint, totalAllocPoint, lpStaked, motoPriceUsd, lpPriceUsd)

Annualized percentage rate for a MasterChef LP pool. MasterChef has no emission campaign running and none is planned, so there is no live rate to feed it. Returns 0 when inputs make APR undefined (zero total alloc, zero stake, zero LP price). All six arguments and the result are JS numbers in whole-token units, not Wei: this is a display figure. Convert on-chain bigint values with formatUnits first.

Formula: (motoPerBlock × allocPoint / totalAllocPoint × BLOCKS_PER_YEAR × motoPriceUsd) / (lpStaked × lpPriceUsd) × 100

Warning: MasterChef emits per second, not per block, so compute APR from currentRewardPerSecond() rather than from this helper.

computeVaultAPR(vaultId, recentFeeVolumeUsd, totalEffectiveStake, motoPriceUsd)

Built on static boost constants

This function, along with effectiveStake, VAULT_IDS and VAULT_CONFIG, uses static multiplier values. MotoStaking holds one position per wallet with a lock option 0 to 3. The source of truth is MotoStaking.boostBps(durationOption), the extra on top of a 1x base (multiplier = 1 + boostBps / 10000), which governance can change. The symbols still ship, so they are documented here, but do not build against them.

Annualized percentage rate for a stake vault. Fee income is annualized from a recent (e.g., 24h) USD fee figure; stake value is the vault's effective stake (multiplier-weighted) priced in MOTO.

Throws RangeError if vaultId is not in [0, 1, 2, 3].

Formula: (recentFeeVolumeUsd × 365) / (totalEffectiveStake × motoPriceUsd) × 100


positions.ts - LP / farm / stake position math

effectiveStake(amount, vaultId)

Uses the static boost constants (see the warning on computeVaultAPR). Do not build against it.

Multiplier-weighted stake for a position, derived from its vault's reward multiplier. Option 0 is the shortest lock; longer locks scale higher. There is no flexible, no-lock tier.

Formula: (amount × VAULT_CONFIG[vaultId].rewardMultiplierBps) / 10_000

isUnlocked(position, now)

Returns true when now >= position.lockUntil (unix seconds).

summarizeUserPositions(userAddress, positions, now)

Aggregates a user's vault positions into a single summary.

Returns: UserStakeSummary:

{
  userAddress: Address,
  totalStaked: bigint,
  totalEffectiveStake: bigint,
  positionCount: number,
  unlockedCount: number,
}

Types (imported from @motoswap/shared):

  • LockPosition = { positionId: number; vaultId: VaultId; amount: bigint; lockUntil: number }
  • UserStakeSummary - as above.

slippage.ts - Slippage helpers

amountOutMin(quote, toleranceBps)

quote × (10_000 − toleranceBps) / 10_000. Throws on negative inputs or tolerance ≥ 100%.

submitAmountOutMin(quoted, live, toleranceBps)

Floor to write into swap calldata when a live pre-submit re-quote differs from what the user reviewed. Takes the better of the two.

amountInMax(quote, toleranceBps)

quote × (10_000 + toleranceBps) / 10_000. For exact-out swaps.

midPriceBps(reserveIn, reserveOut)

(reserveOut × 10_000) / reserveIn. Returns null for empty pools.

executionPriceBps(amountIn, amountOut)

Effective execution price at 10_000 precision. Returns null if amountIn == 0.

priceImpactBps({ amountIn, reserveIn, reserveOut, feeBps })

Price impact in basis points, measured as the wedge between mid and execution prices.

Formula: impact = 1 − (amountOut × reserveIn) / (amountInAfterFee × reserveOut), scaled to bps, where amountInAfterFee = amountIn × (10_000 − feeBps) / 10_000. The LP fee is excluded on purpose: the figure measures trade size against the curve, and the fee is its own display line.

Returns bigint | null: null for empty pools or zero input. feeBps is required, no default - pass the pool's live factory.swapFeeBps(), or 30 for a Uniswap V2 pair.


vanity.ts - Vanity address grinding

grindVanitySalt(params: GrindVanityParams): VanityResult

Legacy. The TokenLauncher contract this targets is retired and has no mainnet address, so there is nothing to build against. The symbol still ships.

Grinds a salt to find a CREATE2 address that matches a trailing vanity suffix. TokenLauncher deployments required each launched token to end in a specific pattern (default 5afe). Caller-bound: the CREATE2 salt includes msg.sender, so a bot cannot replay a victim's ground salt. Runs in a hot loop using raw @noble/hashes keccak (~3x faster than viem marshalling). Run in a Worker to avoid UI jank.

Params:

{
  launcher: Address,           // TokenLauncher contract
  deployer: Address,           // caller (baked into salt)
  name: string,
  symbol: string,
  creationBytecode: Hex,       // LaunchedToken init code from @motoswap/abis
  suffix: number,              // e.g. 0x5afe
  nibbles: number,             // trailing hex nibbles to match (0 disables)
  maxIterations?: number,      // default 5,000,000
}

Returns: `{ salt: Hex, token: Address, iterations: number }`.

Throws Error if no matching salt found within maxIterations.


Constants (from @motoswap/shared)

  • SWAP_FEE_BPS = 30n - per-pool Uniswap V2 LP fee in basis points (0.30%).
  • BLOCKS_PER_YEAR = 2_628_000 (12-second blocks).
  • BPS_DENOMINATOR = 10_000n.
  • VAULT_IDS = [0, 1, 2, 3] and VAULT_CONFIG: Record<VaultId, VaultConfig> - lock option indices and static configuration (e.g., rewardMultiplierBps). The multipliers are static; the source of truth is MotoStaking.boostBps(durationOption) (multiplier = 1 + boostBps / 10000). Do not build against them.

Summary of Key Integration Patterns

Routing (most integrators start here):

const route = bestRoute(tokenA, tokenB, allPairs, amountIn, maxHops); // amountIn: bigint
if (route) {
  const minOut = amountOutMin(route.amountOut, toleranceBps);        // toleranceBps: bigint, 50n = 0.5%
  // submit swap with path = route.path, amountOutMin = minOut
}

Zap (single-sided LP entry):

// feeBps is required on both calls - read the live pool fee, or SWAP_FEE_BPS.
// factory comes from GET /config. Check it against the zero address before you call it.
const feeBps = await factory.read.swapFeeBps();

const toSwap = optimalSwapInput(amountIn, reserveIn, feeBps);
const toAdd  = amountIn - toSwap;
const bought = getAmountOut(toSwap, reserveIn, reserveOut, feeBps);
// add LP with (toAdd, bought)

Pricing & APR:

const priceUsd = priceFromReserves({
  tokenReserve: reserve0, stableReserve: reserve1,
  tokenDecimals: 18, stableDecimals: 6,
});

const apr = computeMasterChefPoolAPR(
  motoPerBlock, allocPoint, totalAllocPoint,
  lpStaked, motoPriceUsd, lpPriceUsd,
);

Positions & Staking:

const summary = summarizeUserPositions(userAddr, positions, now);
console.log(summary.totalEffectiveStake, summary.unlockedCount);

On this page