Skip to content
Protocol contracts not yet configured for this build
Enter app

Developers

Running the buyback keeper

The keeper is a script, not a privileged oracle. It computes a defensible minTokensOut, verifies the route, simulates, and only then submits.

Crucially, the on-chain bounds do not depend on it behaving well — see keeper constraints.

Running it

bash
pnpm keeper:buyback                    # plan and simulate onlypnpm keeper:buyback -- --eth 0.05      # size the spend explicitlypnpm keeper:buyback -- --slippage 200  # 2.00% tolerance (default 1.00%)pnpm keeper:buyback -- --broadcast     # submit

Broadcasting requires PLOUTO_KEEPER_KEY. Without both the flag and the key, it will not send a transaction.

What it checks first

  1. Chain ID is 4663.
  2. The registry is initialized.
  3. The executor is not paused.
  4. The budget clears minEthPerExecution.
  5. The factory record's curve matches the registry's curve.
  6. The phase is tradeable — 0 or 2, never 1 or 3.

Sizing

Respects every on-chain cap, and drains a small residual rather than stranding it:

typescript
const shareCap = (budget * shareBps) / 10_000n;let ethIn = opt('eth') ? parseEther(opt('eth')!) : budget <= maxPerExec ? budget : shareCap;if (ethIn > maxPerExec) ethIn = maxPerExec;if (ethIn > budget) ethIn = budget;if (ethIn > shareCap && ethIn !== budget) ethIn = shareCap;

The contract allows spending the entire budget when it is at or below the per-execution cap, so a residual can always be cleared.

Quoting, pre-graduation

Constant product against the phantom-inclusive reserves, net of the curve fee:

typescript
const netIn = (ethIn * (10_000n - feeBps)) / 10_000n;expectedOut = (tokenReserve * netIn) / (quoteReserve + netIn);

It also refuses to trade while the snipe tax is active:

typescript
if (snipeBps > 0n) fail(`the snipe tax is still active (${snipeBps} bps). Wait for it to decay.`);

Quoting, post-graduation

From the live pool price, read the same way the contract reads it:

typescript
const sqrtPriceX96 = await client.readContract({  address: executor, abi: buybackExecutorAbi,  functionName: 'poolSqrtPriceX96', args: [token],});const Q96 = 1n << 96n;expectedOut = (((ethIn * sqrtPriceX96) / Q96) * sqrtPriceX96) / Q96;

This is a spot quote and ignores depth, which is precisely why the on-chain price-impact bound is the real guard.

The floor

typescript
const minTokensOut = (expectedOut * (10_000n - SLIPPAGE_BPS)) / 10_000n;if (minTokensOut === 0n) fail('computed minTokensOut is zero — refusing to submit an unprotected buy');

A zero floor is rejected locally, and the contract rejects it too with ZeroMinTokensOut().

Simulate, then submit

typescript
const sim = await client.simulateContract({  address: executor,  abi: buybackExecutorAbi,  functionName: 'executeBuyback',  args: [ethIn, minTokensOut, deadline],  account: keeper,});// ... only then:const hash = await wallet.writeContract(sim.request);

The script prints the simulated retirement and the realised slippage versus spot before it broadcasts.

The revenue keeper

bash
pnpm keeper:route              # report the four fee states, simulatepnpm keeper:route -- --broadcast

Claiming and routing are permissionless, so this exists to run on a schedule rather than to hold a permission. It prints the escrow balance, unallocated revenue, held donations and lifetime totals every run.

Operational advice

  • Give the keeper a dedicated low-value hot wallet holding only gas.
  • Keep maxPriceImpactBps tight. The default is 500 (5%); widening it widens the sandwich window.
  • Run buybacks at irregular intervals and sizes rather than on a predictable schedule.
  • Monitor the executor's paused() state — a paused executor means someone intervened.