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 forgrindVanitySalt@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 (typicallyAddress).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 forTOKEN → HUB → WETH).feeBps?: bigint- per-pool LP swap fee in basis points; defaults toSWAP_FEE_BPS(30 bps). It must not fold in the once-per-tradeFeeRouterprotocol 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 toSWAP_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, default25.feeBps?: bigint- per-pool LP fee, defaultSWAP_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 inpath.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. Pass0nfor 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 likebestSplitRoute's output.pairs: readonly RoutePair<A>[]- pairs covering every hop in every leg.feeBps: bigint- required, no default. Same meaning as onpathMidRate, 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 livefactory.swapFeeBps(), orSWAP_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]andVAULT_CONFIG: Record<VaultId, VaultConfig>- lock option indices and static configuration (e.g.,rewardMultiplierBps). The multipliers are static; the source of truth isMotoStaking.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);