MainSDK
Search⌘K
How-to

Confirm sensitive actions with a signed approval

Make an action need a passkey signature over its exact amount and payee, and require a second approver for the most critical ones.

Planned for 1.2

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

You will mark an action so it only runs after a person signs this exact call with a passkey. It takes about 20 minutes.

You need

  • A MainSDK app with passkeys (auth with WebAuthn) and an action that moves money or changes access.

1. Mark the action

actions/payouts.ts
import { action } from "mainsdk";
import { s } from "mainsdk/schema";

export const sendPayout = action({
  name: "payouts.send",
  intent: "Send a payout to a payee",
  input: s.object({ amount: s.number(), payee: s.string() }),
  risk: "destructive",
  confirm: "signed",
  signs: (input) => ({ amount: { value: input.amount, currency: "INR" }, payee: input.payee }),
  run: async ({ input }) => ({ sent: input.payee }),
});

An action marked confirm: "signed" fails with MAIN-AUTH-0204 until the call carries an approval that was made for the same action, amount and payee. Admin role changes and Fleet's critical actions always need one.

2. Ask the person to sign

Approvals from mainsdk/confirm has begin and finish. begin returns a WebAuthn challenge that is a hash of the canonical action (name, amount, currency, payee, a nonce and an expiry), together with the sentence to show, such as "Send INR 1,25,000 to acct_7Q…". The person's passkey signs the challenge, and finish verifies the assertion and stores an approval that lives five minutes and can be spent once.

3. Add a second approver where it matters

ApprovalQuorum runs an action only after two different people have approved it. A payment in a market that requires dynamic linking (PSD2, RBI) records the assurance level of the approval in the audit event, which toOcsfApproval from mainsdk/confirm-ocsf turns into an OCSF record.

4. Agents

An agent calling the action over MCP gets an elicitation back instead of a result, naming the action and the amount to show its human. The approval comes back in _meta.approval on the retry.

Check it worked

Call the action without an approval and expect MAIN-AUTH-0204. Then approve it with the fake authenticator from @mainsdk/testing and expect it to run once, and fail the second time.

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet