Build a Machine Wallet
This guide walks through building a machine wallet that can spend within MPCP policy and budget bounds.
Concepts
A machine wallet holds:
- PolicyGrant — Permission envelope (rails, assets, expiration)
- SignedBudgetAuthorization (SBA) — Spending envelope for the session
- Signing keys — For producing SignedPaymentAuthorizations (SPA)
When a payment is requested, the wallet:
- Checks the request against policy and budget
- Signs an SPA if the payment is permitted
- Returns the SPA (and optional SettlementIntent) to the payee
Setup
npm install mpcp-service
Generate Keys
The machine needs keys for signing SPAs. (SBA is typically signed by the fleet/issuer.)
import crypto from "node:crypto";
const spaKeys = crypto.generateKeyPairSync("ed25519");
const privateKeyPem = spaKeys.privateKey.export({ type: "pkcs8", format: "pem" });
const publicKeyPem = spaKeys.publicKey.export({ type: "spki", format: "pem" });
// Store securely; use privateKeyPem for signing
Create Policy Grant and Budget
import {
createPolicyGrant,
createBudgetAuthorization,
createSignedBudgetAuthorization,
} from "mpcp-service/sdk";
const policyGrant = createPolicyGrant({
policyHash: "a1b2c3d4",
allowedRails: ["xrpl"],
allowedAssets: [{ kind: "IOU", currency: "RLUSD", issuer: "rIssuer" }],
expiresAt: "2030-12-31T23:59:59Z",
});
// SBA requires fleet/issuer keys (MPCP_SBA_SIGNING_PRIVATE_KEY_PEM)
// For demo, use createSignedBudgetAuthorization with env set
const sba = createSignedBudgetAuthorization({
sessionId: "sess-123",
actorId: "actor-456",
policyHash: policyGrant.policyHash,
currency: "USD",
maxAmountMinor: "3000",
allowedRails: ["xrpl"],
allowedAssets: [{ kind: "IOU", currency: "RLUSD", issuer: "rIssuer" }],
destinationAllowlist: ["rParking"],
expiresAt: "2030-12-31T23:59:59Z",
});
Sign a Payment
When the payee requests payment:
import {
createSignedPaymentAuthorization,
createSettlementIntent,
computeSettlementIntentHash,
} from "mpcp-service/sdk";
// 1. Build settlement intent from the quote
const intent = createSettlementIntent({
rail: "xrpl",
amount: "1000",
destination: "rParking",
asset: { kind: "IOU", currency: "RLUSD", issuer: "rIssuer" },
});
// 2. Create SPA (requires MPCP_SPA_SIGNING_PRIVATE_KEY_PEM)
const spa = createSignedPaymentAuthorization({
decisionId: "dec-789",
sessionId: sba.authorization.sessionId,
policyHash: sba.authorization.policyHash,
quoteId: "quote-17",
rail: "xrpl",
asset: { kind: "IOU", currency: "RLUSD", issuer: "rIssuer" },
amount: "1000",
destination: "rParking",
intentHash: computeSettlementIntentHash(intent),
expiresAt: sba.authorization.expiresAt,
});
Verify Before Signing
Before signing, the wallet should verify:
- Budget — Amount ≤ maxAmountMinor; destination in allowlist
- Policy — Rail and asset permitted
- Expiration — Not expired
Use verifySignedBudgetAuthorization from the SDK to check the budget against the payment decision.
Run the Example
The parking session example demonstrates the full flow:
npm run build
npm run example:parking
Or the guardrails demo with tamper detection:
npm run example:guardrails