Signer
Use StableOps Agent Payments Signer CLI to validate pairing challenges, run a loopback sidecar, and sign constrained payments with local keys or AWS KMS.
@stableops/agent-payments-signer keeps wallet keys in the user's environment. The user runs a CLI command for one-time wallet registration. For daily payments, MCP calls a local signing sidecar:
Agent → stableops-agent-payments-mcp → signing sidecar /v1/sign → local wallet key or AWS KMSThe sidecar signs only EIP-3009, Permit2, or Solana payments that match a short-lived StableOps execution grant. The grant binds the Agent, wallet, network, asset, recipient, amount, nonce, and validity window. Mismatches, expiration, unknown issuer keys, and replay conflicts are rejected.
Two private keys serve different purposes:
| Private key | Configured by | Where it is configured |
|---|---|---|
| Payment-wallet private key | Customer | On the signer host. Local EVM uses STABLEOPS_EVM_PRIVATE_KEY_FILE, Solana uses STABLEOPS_SOLANA_PRIVATE_KEY_FILE, and AWS KMS uses its KMS key ID. |
| Execution-grant signing private key | Control-plane operator | In the API service as AGENT_PAYMENTS_GRANT_PRIVATE_KEY_BASE64. Customers and signer sidecars never receive this private key. |
The signer automatically retrieves only the public key corresponding to the execution-grant signing key. It uses that public key to verify grants from the StableOps control plane, not to sign payments. Payments are still signed by the customer's payment-wallet private key or AWS KMS key.
Command-line interface
The npm package includes the stableops-agent-payments-signer CLI and requires Node.js 20 or later. Install it globally to invoke it directly from a terminal:
pnpm add -g @stableops/agent-payments-signerUse --help to list the commands supported by the installed version:
stableops-agent-payments-signer --helpThe CLI has two primary commands:
| Command | Lifetime | Purpose |
|---|---|---|
pair-wallet | One-shot | Validates and signs one unexpired wallet-registration challenge, writes the signature to standard output, and exits. Pass the challenge with --challenge <base64url>. When the option is omitted, the command reads the complete challenge from standard input. |
serve | Long-running | Starts a signing sidecar bound to loopback, with health-check and execution-grant-constrained payment-signing endpoints. Key, network, grant-verification, authorization-store, and service-authentication settings are read from environment variables. |
Both commands accept --signer <local|aws-kms|solana>. Alternatively, set STABLEOPS_SIGNER_TYPE. The command-line option takes precedence when both are present. local uses a local EVM test key, aws-kms uses an EVM key in AWS KMS, and solana uses a local Solana key. The CLI never accepts local private-key values as arguments or environment variables—only paths to protected key files.
A signer being able to create a signature does not mean a paid service accepts that network. Start with the Base Sepolia local EVM signer. Before choosing another signer and network, confirm that the service's payment requirement matches the supported scope, then complete a small payment that requires human approval.
Register a wallet: pair-wallet
Obtain a one-time challenge. Run the one-shot command in a trusted terminal.
MCP returns the complete challenge text. You can pass it directly to the CLI through standard input.
First, set the private-key file path in a trusted terminal:
export STABLEOPS_EVM_PRIVATE_KEY_FILE='/protected/absolute/path/evm-private-key'Choose the signer that controls the address in the challenge:
- Local EVM Sandbox: set
STABLEOPS_EVM_PRIVATE_KEY_FILEas above and use--signer local. - EVM with AWS KMS: set
STABLEOPS_KMS_KEY_IDandAWS_REGION, then use--signer aws-kms. The CLI derives the KMS key's EVM address and rejects a challenge for another address. - Solana: set
STABLEOPS_SOLANA_PRIVATE_KEY_FILEto the protected Base58 key file and use--signer solana.
Copy only the complete seven-line challenge from StableOps Agent Wallet Pairing through Expires At: .... Keep the field names at the start of each line with no added indentation or inserted line breaks. Do not copy the surrounding explanation or the separate challenge ID.
Choose one input method for your system and run the signer only once. On macOS, pipe the challenge from the clipboard:
pbpaste | stableops-agent-payments-signer pair-wallet --signer <local|aws-kms|solana>On other systems, start the interactive command:
stableops-agent-payments-signer pair-wallet --signer <local|aws-kms|solana>When the command waits for input, paste the seven lines. Press Enter after the last line, then press Ctrl-D on the empty next line to close standard input. The CLI validates the challenge format, expiry, and wallet address, then writes only the signature to standard output. Local EVM and AWS KMS signers print a hexadecimal signature beginning with 0x; the Solana signer prints a Base58 signature without that prefix. Return the output to the Agent unchanged and do not convert it to Base64.
Only use --challenge when you have first encoded the complete challenge as unpadded Base64URL:
stableops-agent-payments-signer pair-wallet --signer <local|aws-kms|solana> --challenge <complete-challenge-as-unpadded-Base64URL>pair-wallet signs only a StableOps pairing challenge whose format, expiry, and wallet address pass validation. It does not accept arbitrary messages or handle daily payments.See Quickstart: Register and bind a payment wallet for the complete flow.
Daily payments: serve
serve starts the sidecar at 127.0.0.1:8789 with GET /health and POST /v1/sign. Runtime MCP calls it with the sidecar URL and authentication token. The Agent does not execute the signer CLI directly.
Common configuration
You only need to prepare the authorization record path and sidecar token. The signer discovers execution-grant verification keys automatically:
| Variable | How to configure it |
|---|---|
STABLEOPS_AUTHORIZATION_STORE_FILE | Absolute path for the persistent JSON record. Do not create the file in advance. The signer creates it with mode 0600 when recording the first authorization. |
STABLEOPS_SIDECAR_TOKEN | A shared authentication token you generate. Generate one random value containing at least 32 bytes, then configure the exact same value in the signer and runtime MCP. |
On macOS or Linux, prepare the storage directory. Replace the example username with the actual username:
mkdir -p /Users/alice/.config/stableops/signer
chmod 700 /Users/alice/.config/stableops/signerGenerate the sidecar token once:
openssl rand -hex 32Copy the 64-character hexadecimal output and use the same value in the signer and runtime MCP. Do not run the command separately for each process, because that produces different tokens.
Then configure the signer:
export STABLEOPS_AUTHORIZATION_STORE_FILE='/Users/alice/.config/stableops/signer/grant-authorizations.json'
export STABLEOPS_SIDECAR_TOKEN='64-character hexadecimal value from openssl'Automatic execution-grant key discovery
When serve starts, it requests this public endpoint:
GET https://api.stableops.dev/v1/public/agent-payments/execution-grant-keysThe endpoint returns the current control plane's Ed25519 key IDs and public keys as JWKS. It requires no API Key and never returns the signing private key. The signer validates the key type, algorithm, and encoding before starting. It refuses to start if the endpoint is unavailable or invalid.
Verification keys are not unique to a user, organization, or wallet. Each StableOps API deployment publishes a set of verification keys shared by the organizations it serves. Every execution grant still separately binds the organization, Agent, Intent, wallet, environment, network, asset, recipient, amount, nonce, and validity window.
Configure STABLEOPS_GRANT_KEY_ID and STABLEOPS_GRANT_PUBLIC_KEY_FILE together only for offline deployments or public-key pinning. When set, the signer reads the local PEM file and does not call the discovery endpoint. Update both values together during key rotation.
Runtime MCP connects with:
{
"STABLEOPS_SIDECAR_URL": "http://127.0.0.1:8789",
"STABLEOPS_SIDECAR_TOKEN": "same-high-entropy-token-as-the-signer"
}Local EVM test signer
local is limited to Sandbox EVM test networks. Its private key must match the wallet registered with StableOps:
export STABLEOPS_EVM_PRIVATE_KEY_FILE='/protected/absolute/path/evm-private-key'
stableops-agent-payments-signer serve --signer local0x followed by 64 hexadecimal digits. It must be a regular, non-symlink file restricted to mode 0600. The CLI reports a specific error when the 0x prefix is missing.local defaults to SANDBOX, Base Sepolia network eip155:84532, and port 8789. Set STABLEOPS_SIGNER_NETWORK for another Sandbox EVM network. Local EVM mode cannot connect to Live.
Solana signer
Solana uses a Base58-encoded private key. Set the environment and Solana CAIP-2 network explicitly:
export STABLEOPS_SIGNER_ENVIRONMENT=LIVE
export STABLEOPS_SIGNER_NETWORK='solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'
export STABLEOPS_SOLANA_PRIVATE_KEY_FILE='/protected/absolute/path/solana-private-key'
stableops-agent-payments-signer serve --signer solanaThe Solana key file also requires an absolute path, a regular file, and mode 0600. The facilitator fee payer covers transaction fees, so the payment wallet does not need SOL for the x402 payment.
AWS KMS signer
A Live EVM topology should keep the private key in AWS KMS. AwsKmsSigner requires a SIGN_VERIFY key using ECC_SECG_P256K1 with ECDSA_SHA_256:
export STABLEOPS_SIGNER_ENVIRONMENT=LIVE
export STABLEOPS_SIGNER_NETWORK='eip155:8453'
export STABLEOPS_KMS_KEY_ID='replace-with-the-KMS-key-id-or-ARN'
export STABLEOPS_WALLET_ADDRESS='0x-replace-with-the-KMS-wallet-address'
export AWS_REGION='replace-with-the-KMS-region'
stableops-agent-payments-signer serve --signer aws-kmsAt startup, the signer retrieves the KMS public key and verifies its EVM address. It refuses to start when that address differs from STABLEOPS_WALLET_ADDRESS. KMS signs only payment digests that passed execution-grant validation.
AWS KMS supports EVM networks only. Live payments also require the organization mainnet risk gates.
Deployment checklist
- Bind the sidecar to loopback only. Never expose it publicly.
- Give runtime MCP only the sidecar URL and token, not the wallet key or KMS administration rights.
- Discover grant verification keys from a trusted control-plane URL. When pinning a local key, rotate the key ID and public-key file together.
- Use a separate signer, wallet, and authorization record for each network.
- A single instance may use the file authorization record. Multi-replica deployments need shared storage with atomic writes and uniqueness constraints.
- Log identifiers and error categories, never private keys, full grants, or payment signatures.
How is this guide?
Last updated
Management SDK
Use the StableOps Agent Payments management SDK in a trusted TypeScript service to configure Agents, wallets, policies, budgets, approvals, and payment queries.
Webhooks
Receive StableOps Agent Payments Webhooks for payment, resource, approval, and circuit-breaker states with signature verification, retries, and replay handling.