Quickstart
Part one runs on the hosted testnet service today: sign in, write a policy, register an agent and ask Palisec before every transaction. Part two evaluates policies locally with the reference evaluator.
1 · Your first authorized operation (testnet)
- Sign in. Open /login and sign the message with your wallet. Your first sign-in creates an organization with you as owner.
- Create a policy. In Console → Policies, enter the smart account your agent acts for. The draft starts strict: tiny limits and nothing allowlisted.
- Allow what the agent needs. Add each contract and function selector, set the hourly budget and the approval threshold, then save. The editor validates against schema 1.0 as you type.
- Activate. Review the changes and activate. This creates an immutable version 1 and an audit event.
- Register the agent and create a key. In Console → Agents, bind the agent to the policy, then create an API key. Copy it: it is shown once.
- Ask before every transaction. Your agent calls
POST /v1/operationsand only sends onALLOW.
agent.ts
// Node 22+, no dependencies. PALISEC_API_KEY comes from Console → Agents → Create key.
const API = "https://palisec.tech/api/v1";
async function authorize(tx: { to: string; value: bigint; data: string; gas: number }) {
const requestId = crypto.randomUUID(); // unique per intended transaction
const res = await fetch(`${API}/operations`, {
method: "POST",
headers: {
authorization: `Bearer ${process.env.PALISEC_API_KEY}`,
"content-type": "application/json",
"idempotency-key": requestId, // a retry returns the same decision
},
body: JSON.stringify({
request_id: requestId,
chain_id: 46630, // Robinhood Chain Testnet
to: tx.to,
value_wei: tx.value.toString(), // integers as strings, never floats
data: tx.data,
gas_limit: tx.gas, // the fee bound is gas_limit × gas price
deadline: Math.floor(Date.now() / 1000) + 120,
}),
});
const op = await res.json();
if (!res.ok) throw new Error(`${op.error.code}: ${op.error.message}`);
return op; // { decision, reason_code, state, authorization_id, expires_at, checks, ... }
}
const op = await authorize({ to: TOKEN, value: 0n, data: transferCalldata, gas: 90_000 });
if (op.decision === "ALLOW") {
// Send the transaction yourself, before op.expires_at.
} else if (op.decision === "REQUIRES_HUMAN_SIGNATURE") {
// Poll GET /operations/{op.operation_id} until state is "approved" (then send) or "rejected".
} else {
console.warn("Denied:", op.reason_code, op.message); // never send a denied transaction
}Try a request without reserving anything: the dry run returns the same decision and evaluation sequence, and consumes no request ID or budget.
terminal
curl -s https://palisec.tech/api/v1/authorizations/evaluate \
-H "authorization: Bearer $PALISEC_API_KEY" -H "content-type: application/json" \
-d '{"request_id":"try-1","chain_id":46630,"to":"0x…","value_wei":"0","data":"0x",
"gas_limit":21000,"deadline":'$(($(date +%s)+60))'}'- Authorization is not execution.
transaction_hashstaysnull: Palisec did not broadcast anything. - Budget is held until it expires. An allowed amount counts against the hourly budget even if you never send, because nobody can prove you did not. Only a console operator can release it.
- Approvals bind the exact operation. An approver signs the request hash, target, value, calldata hash, policy version and expiry. Changing any of them needs a new approval.
Every endpoint, field and status code is in the API reference.
2 · Evaluate locally
Requires Node 22 and pnpm 10. The test suite covers each rule, precedence, replay, cooldowns and fail-closed behaviour.
terminal
# from a checkout of the Palisec repository (access is currently private)
pnpm install
pnpm --filter @palisec/policy-schema testEvaluate an intent from TypeScript:
evaluate.ts
import {
EMPTY_STATE, EXAMPLE_ENV, EXAMPLE_POLICY, SCENARIOS, evaluate,
} from "@palisec/policy-schema";
const swap = SCENARIOS.find((s) => s.id === "swap")!;
const result = evaluate(
EXAMPLE_POLICY,
{ ...swap.intent, request_id: "req_001", deadline: EXAMPLE_ENV.now + 120 },
EMPTY_STATE,
EXAMPLE_ENV,
);
console.log(result.decision, result.reason_code); // "ALLOW" null
// Keep result.next_state to evaluate the next request against the new ledger.evaluate() is pure. It takes the time, fee estimate and price as inputs, never mutates state, and returns the next ledger snapshot. The same inputs always produce the same decision. Prefer a UI? The workbench runs the same function in your browser.
Next
- API reference: endpoints, authentication, idempotency and pagination.
- Policy schema: every rule, unit and precedence rule.
- Errors and operations: reason codes and retry semantics.
- Security model: what the system does and does not protect.