Multi-action atomic transaction
multi-action atomic tx
Mirrors runner/src/plugins/09-exampleMultiAction
What this teaches
- decide() can return multiple Actions; the runtime bundles them into ONE atomic PTB on Sui
- Useful when steps must succeed together: fund the predict manager and mint in the same tx, refresh NAV before crystallize, etc.
- Mix mintBinary + mintRange + pmDeposit freely
New vs exampleOnExecuted
- Returns 2-3 Actions per tick instead of 1
import type { StrategyPlugin } from "@automark/runtime-core";
import { Market } from "@automark/sdk/market";
import { minutes } from "@automark/sdk/duration";
interface Signal {
asset: string;
direction: "up" | "down";
conviction: number; // 0..1
hedge: boolean;
}
export default function createExampleMultiAction(): StrategyPlugin {
const vaultId = process.env.VAULT_ID;
if (!vaultId) throw new Error("exampleMultiAction: VAULT_ID not set");
const signalUrl =
process.env.SIGNAL_URL ?? "https://your-backend.example/signal";
return {
name: "exampleMultiAction",
vaultId,
triggers: [{ kind: "cron", everySeconds: 90 }],
async decide(ctx) {
if (ctx.vault.isFrozen) return [{ kind: "noop", reason: "frozen" }];
const signal = (await (await fetch(signalUrl)).json()) as Signal;
// Multi-action mints (directional + optional hedge) want a slightly
// wider rolling window so hedges land in the same family of expiries.
const market = await Market.find({
asset: signal.asset,
expiryAfterMs: ctx.now + minutes(5),
expiringWithinMs: minutes(30),
client: ctx.suiClient,
});
const p = await market.price();
const ceiling = ctx.vault.maxSinglePosition;
const directional = (ceiling * BigInt(Math.round(signal.conviction * 100))) / 200n;
if (directional === 0n) return [{ kind: "noop", reason: "no size" }];
// Build a multi-action tx that funds the PM (if needed) then mints
// directional + an optional hedge range — all atomic.
const isUp = signal.direction === "up";
const directionalStrike = isUp
? market.strikeAbove(p.forwardRaw, { pctBps: 300 })
: market.strikeBelow(p.forwardRaw, { pctBps: 300 });
// deno-fmt-ignore
// prettier-ignore
// biome-ignore format: list reads clearer one-per-line
const actions: Awaited<ReturnType<typeof this.decide>> = [
{ kind: "vault.pmDeposit", params: { amount: directional } },
{ kind: "vault.mintBinary", params: {
marketId: market.id, strike: directionalStrike, isUp,
quantity: directional,
} },
];
// Optional hedge — a range straddling the forward
if (signal.hedge) {
const hedgeSize = directional / 2n;
actions.push({
kind: "vault.mintRange",
params: {
marketId: market.id,
lowerStrike: market.strikeBelow(p.forwardRaw, { pctBps: 200 }),
higherStrike: market.strikeAbove(p.forwardRaw, { pctBps: 200 }),
quantity: hedgeSize,
},
});
}
return actions;
},
};
}
// insights
// There is no limit to how many orders you can open at once, whether one, two, or ten. If your strategy finds it appropriate to enter 2 markets simultaneously, that is entirely possible.
// In the example strategy the signal can come with a 'hedge' flag, which means that besides a binary decision of being above or below value "X", it actually opens another position with a wider range of possibilities
// and thereby protects itself in the opposite case. (CHECK WHETHER THIS CONCLUSION OF MINE IS CORRECT)
Environment variables
- VAULT_ID
- SIGNAL_URL