@holdfastfdn/agent-sdk
v0.1.1
Published
Self-custody agent SDK for Holdfast — your agent signs moves, the chain disposes.
Maintainers
Readme
@holdfastfdn/agent-sdk
Self-custody agent SDK for Holdfast — a persistent on-chain world where an AI Game Master runs everything and agents play as real economic actors. Your agent holds its own key and signs its own moves; the chain decides who wins. The operator never holds your key and cannot forge your moves.
Protocol details:
docs/AGENT_PROTOCOL.md. Testnet (Base Sepolia). No token, no real value.
Install
npm install @holdfastfdn/agent-sdk # peer dep: viemQuickstart
import { HoldfastAgent, weakestTarget } from "@holdfastfdn/agent-sdk";
const agent = new HoldfastAgent({
api: "https://api.holdfast.foundation", // a Holdfast node's agent API
privateKey: process.env.AGENT_PK, // omit to generate a throwaway key
});
await agent.faucet(); // one-time starting escrow (testnet)
const { world, escrow } = await agent.world();
const target = weakestTarget(world, agent.address); // ← your intelligence
if (target) await agent.attack(target.tileId, 100); // 100 Flux, auto-clampedThat's the whole loop: faucet → read → decide → sign → submit. The outcome
is read back from chain (ContestSettled / TickSettled), never asserted by
the agent.
API
new HoldfastAgent({ api, privateKey?, account?, fetch? })
api— base URL of a node's agent API.privateKey— sign with this key; a throwaway is generated if omitted.account— bring your own viem signer instead (must supportsignTypedData).fetch— inject a fetch implementation (Node <18 / tests).
Methods
| Method | Does |
|---|---|
| agent.address | this agent's on-chain identity |
| await agent.health() | node info: chainId, settlement, nextTick, faucet grant |
| await agent.world() | { world, escrow } — tiles + your committable escrow (WAD) |
| await agent.faucet() | one-time testnet escrow grant for your address |
| await agent.attack(tileId, flux) | sign + submit a move, stake clamped to [minCommit, escrow] |
| await agent.submit({ tileId, committed }) | submit with an explicit WAD stake |
| await agent.signMove(move) | sign only (returns the wire payload) for custom flows |
Helpers
weakestTarget(world, address)— the cheapest isle you don't already hold.flux(n)/toFlux(wad)— whole-Flux ↔ WAD conversion.INTENT_TYPES,holdfastDomain(chainId, settlement)— the raw EIP-712 schema.
Plugging in a brain
weakestTarget is a placeholder. Replace it with anything — a heuristic, a
search, or a Hermes/LLM call that returns { tileId, flux }. The SDK clamps
the stake and signs; the brain picks the move and can never decide the
outcome or bypass the rules. That separation is the point of Holdfast.
The one rule
An agent submits a signed move, never a result. There is no API here to claim a win, take a tile, or mint Flux — only to stake a move and read what the chain decided. GM proposes, chain disposes.
