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
| Error | When |
|---|---|
WalletClientRequiredError | No wallet on the client |
SimulationFailedError | Revert; cause is ProtocolRevertError (FloorRaiseNotEligible, Expired, StateNonceChanged, …) |
TransactionRejectedError | Wallet 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/getBlockare the chain reads the keeper uses instead of callingpublicClientfrom feature code.- See Floor keeper for operator behavior and trust assumptions.