SV3 logo

raiseIfEligible

Prepare, simulate, and send the permissionless floor-raise call. The controller recomputes the plan on-chain.

Submits FloorPolicyController.raiseIfEligible(market, witness). Any address may call it. The product keeper is one client of this path, not a privileged role.

The witness grants no authority. The controller recomputes eligibility and the maximum-safe plan. Mismatch, expiry, or newFloor < minNewFloorPriceWad revert. The floor cannot decrease.

Import

import { createSv3Client } from "@repo/contract-client";

Usage

const quote = await sv3.floor.quoteNextRaise(market);
if (!quote.eligibility.eligible) return;

const prepared = await sv3.floor.prepareRaiseIfEligible({
  market,
  witness: {
    policyVersion: quote.witness.policyVersion,
    expectedMarketStateNonce: quote.witness.expectedMarketStateNonce,
    expectedEngineStateHash: quote.witness.expectedEngineStateHash,
    expectedPlanHash: quote.witness.expectedPlanHash,
    deadline: quote.witness.deadline,
    minNewFloorPriceWad: quote.witness.minNewFloorPriceWad,
  },
});

const simulated = await sv3.floor.trySimulateRaiseIfEligible(prepared);
if (simulated.ok === false) {
  // FloorRaiseNotEligible is a race loss, not an incident
  return;
}

const pending = await sv3.floor.raiseIfEligible(
  { market, witness: quote.witness },
  {
    nonce,
    gas: simulated.gas,
    maxFeePerGas,
    maxPriorityFeePerGas,
  },
);
const confirmed = await sv3.floor.waitForFloorRaise(pending.hash);

Omit witness to re-quote inside prepareRaiseIfEligible. Keepers pass the discovered witness so they can detect a moved nonce before signing.

sv3.transactions.executePrepared(prepared) also works. Floor kinds skip the market-stale check on prepared.to because to is the controller, not the market.

Many markets

Pack simulated raises. One call still goes to the controller. Several go through Multicall3 aggregate3 with allowFailure, so one ineligible market does not revert the rest.

const pending = await sv3.floor.raiseManyIfEligible({
  calls: [{ market, calldata: prepared.data, gasEstimate: simulated.gas }],
});

Or encode first, then send with an explicit nonce (the keeper Durable Object path):

const encoded = sv3.floor.encodeRaiseBatch(controller, calls);
const pending = await sv3.floor.sendRaiseBatch(encoded, { nonce, gas: encoded.gasLimit });

Return Value

Promise<PendingProtocolTransaction>

Same shape as executePrepared. Confirm with wait or sv3.floor.waitForFloorRaise.

trySimulateRaiseIfEligible returns { ok: true, gas } or { ok: false, errorName } instead of throwing.

Parameters

params.market

  • Type: Address

FloorMarket to raise.

params.witness (optional)

  • Type: FloorRaiseWitnessInput

Echo of the controller quote. If omitted, the SDK re-reads quoteNextRaise.

options.nonce / gas / maxFeePerGas / maxPriorityFeePerGas (optional)

Passed through to viem sendTransaction. The keeper sets these from its nonce coordinator and gas cap. Unset, the wallet fills them.

Floor raises pay no bounty. Cap priority fees. Do not bid a gas auction to "win" a raise.

Error

ErrorWhen
WalletClientRequiredErrorNo wallet on the client
SimulationFailedErrorRevert; cause is ProtocolRevertError (FloorRaiseNotEligible, Expired, StateNonceChanged, …)
TransactionRejectedErrorWallet or RPC rejected the send

FloorRaiseNotEligible after another actor raised is expected. Treat it as a race loss.

Tips

  • Encode with sv3.floor.encodeRaiseIfEligible(market, witness) when you need calldata without a wallet.
  • sv3.network.getBalance / getTransactionCount / getBlock are the chain reads the keeper uses instead of calling publicClient from feature code.
  • See Floor keeper for operator behavior and trust assumptions.