Skip to content
Runner · ExamplesLadderrung 05

Cooldown via ctx.state

cooldown via ctx.state

Mirrors runner/src/plugins/05-exampleCooldown

What this teaches

  • ctx.state is a per-plugin KV store (get/set/delete/has/keys)
  • Default backend is in-memory (lost on restart) — see plugins/index.ts to wire a persistent backend
  • A "trade no more than once per N minutes" pattern

New vs exampleSizing

  • Plugin remembers its own last-trade timestamp across ticks
import { noop, type StrategyPlugin } from "@automark/runtime-core";
import { Market } from "@automark/sdk/market";
import { minutes } from "@automark/sdk/duration";

const COOLDOWN_MS = minutes(30);   // 30 min between trades

export default function createExampleCooldown(): StrategyPlugin {

  const vaultId = process.env.VAULT_ID;

  if (!vaultId) throw new Error("exampleCooldown: VAULT_ID not set");

  return {
    name: "exampleCooldown",
    vaultId,
    triggers: [{ kind: "cron", everySeconds: 60 }],

    async decide(ctx) {

      if (ctx.vault.isFrozen) return [noop("frozen")];

      // `getOrDefault` from ctx.state — saves the `?? 0` boilerplate that
      // wraps almost every cooldown/counter read. Returns the fallback only
      // when the key is missing or expired (preserves stored 0 and false).
      const lastTradeMs = await ctx.state.get<number>("lastTradeMs") ?? 0;

      const sinceLast = ctx.now - lastTradeMs;

      if (sinceLast < COOLDOWN_MS) {
        return [
          noop(`cooldown — ${Math.round((COOLDOWN_MS - sinceLast) / 1000)}s left`),
        ];
      }

      const btc = await Market.find({
        asset: "BTC",
        expiryAfterMs: ctx.now + minutes(5),
        client: ctx.suiClient,
      });

      const p = await btc.price();

      const quantity = ctx.vault.maxSinglePosition / 4n;

      if (quantity === 0n) return [noop("no headroom")];

      // NOTE: we set the cooldown BEFORE the action returns. If the tx fails,
      // the cooldown still applies — defensive choice. See exampleOnExecuted to
      // record state in onExecuted instead (only on success).
      await ctx.state.set("lastTradeMs", ctx.now);

      return [
        {
          kind: "vault.mintBinary",
          params: {
            marketId: btc.id,
            strike: btc.strikeAbove(p.forwardRaw, { pctBps: 200 }),
            isUp: true,
            quantity,
          },
        },
      ];
    },
  };
}


// ctx.state provides an API to handle in-memory data and persist information across ticks

Environment variables

  • VAULT_ID