← All packages
Connect · v0.1.0 · Apache-2.0

@flashyos/signer

The one process that holds a seed, and it re-derives before it signs

The isolated signer for the FlashyOS wallet authorization plane: verifies an Ed25519-signed SpendAuthorization, re-derives the operation from the real call, refuses on any mismatch, executes through Tether WDK, and reports settlement. Holds the seed; never holds the plane's private key.

npm install @flashyos/signernpm ↗source ↗

Why it exists

An authorization says what may happen; a call says what is happening. The signer is where the two are compared. It verifies the plane’s signature, refuses a replay, re-derives the operation from the actual call rather than from the authorization, refuses any mismatch, and only then executes through Tether WDK and reports settlement. It never holds the plane’s private key, and its chain table is testnets only.

What it refuses to do
Any chain outside its testnet table — there is no flag that widens it. A call its extractors cannot describe. An authorization it has already executed. A destination, asset, kind or amount that differs from what was authorized, and anything the account’s on-chain allowance would refuse.

How it works

Every request goes through five steps in a fixed order. The authorization’s Ed25519 signature and validity window are verified against the plane’s public key. The nonce is checked against a store — in memory, or a file that survives a restart — and a repeat is refused. The `OperationRecord` is re-derived from the call itself: the transaction, the swap or bridge, the EIP-3009 typed data. It is compared with the authorization — kind, chain, asset, destination, amount at or under the cap — and any difference is a refusal with a code. Only then is the nonce marked spent, the call executed through WDK’s wallet module, and settlement reported back to the plane from a confirmed receipt rather than a hash.

Two more lines of defence sit beside that. The WDK account the backend constructs registers exactly one allow rule — the operation being executed right now — so WDK’s own policy engine makes the account deny-by-default for everything else. And an optional on-chain limit reads the Safe Allowance Module before execution so the signer refuses what the chain would refuse; the chain still decides.

The HTTP face has no authentication of its own, on purpose: the authorization is the credential, a request without a valid one does nothing, and the port is reachable only inside the deployment’s network. Exposing it is a deployment error the runbook names.

Related concepts
IntentMeshagentic autonomous organizationmachine-readable organizational trustAI agent trust register

Use it

A signer on Base Sepolia, behind a port only the plane can reach
import { Signer, WdkChain, FileNonceStore, createSignerServer, TESTNETS } from '@flashyos/signer';

const signer = new Signer({
  planePublicKeyPem,                                  // the plane's public half only
  chains: [new WdkChain({ chain: TESTNETS[0].chain, seedPhrase })],
  nonces: new FileNonceStore('./nonces.json'),
});
createSignerServer(signer).listen(8787);               // POST /execute { authorization, call }

In practice

Run the testnet week
An operator stands the signer up beside the API with a testnet seed phrase, points it at the plane’s public key, and runs the four-agent demo on Base Sepolia: every executed transfer carries a transaction hash, every refusal carries a code, and the settlement ledger reconciles against the chain.
Prove a refusal, not just an approval
A reviewer hands the signer an authorization for vendor A and a call that pays vendor B. The refusal is `MISMATCH`, with the re-derived record beside the authorized one, and nothing was signed. That is the demonstration the whole design exists for.

How it stays honest

The signer is tested against a cross-package fixture: an authorization the API signed, which this package must verify, so the two sides of the boundary cannot drift apart without a red build. Its testnet table is pinned by test, and the refusal codes are the same set the spec names. It publishes with the other two wallet packages, after wallet-wdk, from one workflow.

.github/workflows/publish-wdk.ymlthe workflow that keeps this current — read it, it hides nothing

Questions

Why does the signer re-derive the operation instead of trusting the authorization?
Because the authorization describes what may happen and the call is what is happening. If an agent was authorized to pay one vendor and the call pays another, no signature makes that acceptable, and re-deriving from the call is where it is caught.
Can the signer be pointed at a mainnet?
No. Its chain table is testnets only and every backend refuses to construct for anything else. There is no environment variable, flag or option that widens it; enabling a mainnet is a change to the table, in a pull request, after the pilot gate in the runbook.
What does the signer hold, and what does it not?
It holds a seed phrase and the plane’s public key. It never holds the plane’s private key, so a compromised signer can execute only what the plane has already signed, and a compromised plane cannot execute anything at all.

The licence

Apache-2.0, read from the package’s own manifest. Embed it in anything, including closed software — that is what makes a spec adoptable and a verifier worth running. FlashyOS’s server side is AGPL-3.0-only instead, and a test in the monorepo asserts the direction between them: AGPL code may consume this, this may never consume AGPL code.

Keep reading

Agent permissionsGovernance
Also in connect
@flashyos/agent@flashyos/mcp@flashyos/create-mesh-agent@flashyos/create-mesh-node@flashyos/page@flashyos/eslint-config@flashyos/llm-gateway@flashyos/wallet-wdk@flashyos/wdk