Guides

Stake MOTO

Lock MOTO to earn a boosted share of protocol fees.

MotoStaking locks MOTO for one of four durations at a boosted weight and pays multi-token revenue share from the Collector (the staking bucket of the protocol fee. The split starts at 2 : 2 : 2 : 1 across staking, treasury, Rakeback, and buyback and burn, which is 0.20%, 0.20%, 0.20% and 0.10% of each swap at launch).

Deployment status

MotoStaking is deployed on Ethereum at /config.addresses.motoStaking (see the deployment status table). Staking, appending, unstaking and every read on this page work against it. The deployed implementation has no reward tokens registered and its feeRouter() is a 0x…dEaD placeholder, so rewardTokensLength() reads 0 and claimAsMoto reverts. Both arrive with a contract upgrade.

Lock options

durationOptionDurationBoost
090 days1x
1180 days2x
2365 days4x
3730 days8x

Read the ladder live from durations(i) and boostBps(i). A position's weight is amount * (BPS + boostBps) / BPS with BPS = 10000, so the multiplier is 1 + boostBps / 10000: the four tiers read 0, 10000, 30000 and 70000 for 1x, 2x, 4x and 8x. Boost is snapshotted when the position opens. Rewards arrive in the quote assets fees were collected in, not as MOTO emissions.

The essentials

  • stake(amount, durationOption) opens a position at the chosen tier.
  • appendStake(amount) adds to an existing position; the remaining lock is extended by a weighted average (old money keeps its remaining clock, new money carries a full lock). The lock never shortens, and a dust append never re-locks a matured position.
  • Principal vests linearly over the lock; requestUnstake moves vested principal into a 30-day linear drip you claim from as it arrives.
  • Reward claims have no cooldown.

Vault metadata is served at GET /stake/vaults. Per-user positions and pending rewards are chain-direct reads. There is no per-user stake endpoint on the API.

Reading a position

// One position per wallet. Returns a 7-field tuple.
const [amount, original, durationOption, start, boostBps, duration, availableFloor] =
  await publicClient.readContract({
    address: motoStaking,
    abi: parseAbi([
      "function positions(address) view returns (uint256 amount, uint256 original, uint8 durationOption, uint64 start, uint256 boostBps, uint256 duration, uint256 availableFloor)",
    ]),
    functionName: "positions",
    args: [user],
  });

// Accrued reward for one reward token.
const owed = await publicClient.readContract({
  address: motoStaking,
  abi: parseAbi(["function pendingReward(address account, address token) view returns (uint256)"]),
  functionName: "pendingReward",
  args: [user, weth],
});

duration is the lock length snapshotted at stake time, and it is what vesting is computed against, not whatever the tier ladder says today. A later change to the ladder therefore cannot retroactively re-vest an open position. boostBps is snapshotted for the same reason.

There is no getPositions and no pendingClaim on this contract. If you find either in older material, it is wrong.

Write surface

  • stake(uint256 amount, uint8 durationOption) - approve MOTO to MotoStaking first. One position per wallet: a second stake reverts PositionExists(), use appendStake
  • appendStake(uint256 amount) - one argument; the tier is inherited from the open position
  • requestUnstake(uint256 amount)
  • claimUnstaked() drains every matured ticket; claimUnstakedFrom(uint256 maxTickets) bounds the walk when a wallet has many
  • claim(address token), the no-arg claim() that settles every reward token at once, and claimAsMoto(address token, uint256 minMotoOut, address[] path, uint256 deadline) (note the trailing deadline). path must run token to MOTO, the swap goes through the FeeRouter and pays the protocol fee, and minMotoOut is checked against the MOTO that actually reached you (BelowMinMotoOut)

Operators: purgeRewardToken takes (address token, uint256 maxResidual), with maxResidual capped at MAX_PURGE_RESIDUAL. In the current source, reward tokens are an owner-curated list (addRewardToken), and notifyReward reverts RewardTokenNotApproved(token) for anything else. addRewardToken is not on the deployed implementation yet. It arrives with a contract upgrade.

Full function/event surface: reference/contracts-full-inventory.

On this page