@canister-software/consensus-cli
v0.1.0-beta.7
Published
Consensus SDK for interacting with the Consensus protocol
Maintainers
Readme
Installation
npm install @canister-software/consensus-cliEnvironment Setup
Create a .env file in the root of your project with your CDP credentials:
CDP_API_KEY_ID=your_cdp_key_id
CDP_API_KEY_SECRET=your_cdp_key_secret
CDP_WALLET_SECRET=your_cdp_wallet_secretGet your CDP credentials at portal.cdp.coinbase.com.
You can also override the default server with:
CONSENSUS_SERVER_URL=https://your-custom-node.example.comProxyClient
ProxyClient(fetchWithPayment, options) returns a framework-agnostic proxy controller with Express-compatible middleware behavior. It routes outbound HTTP requests through Consensus proxy nodes and supports automatic spend-limit stand-down.
ProxyClient Options
| Option | Type | Default | Description |
| ------------------ | ---------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| mode | "inclusive" \| "exclusive" | "inclusive" | inclusive proxies all routes except those in routes. exclusive proxies only the listed routes. |
| routes | string[] | [] | Route paths to include or exclude, depending on mode. |
| matchSubroutes | boolean | false | When true, a route match also applies to all sub-paths beneath it. |
| strategy | "auto" \| "manual" | "auto" | auto transparently intercepts fetch() calls within middleware. manual exposes req.consensus.fetch() for explicit control. |
| cache_ttl | number | — | TTL in seconds for node-level response caching. |
| verbose | boolean | false | Enables verbose response metadata from the proxy node. |
| node_region | string | — | Prefer proxy nodes in a specific geographic region. |
| node_domain | string | — | Route through a specific node domain. |
| node_exclude | string | — | Exclude a specific node domain from selection. |
| limit_usd | number | — | Max proxy spend in USD (up to 6 decimals). When reached, proxying stands down to direct fetch. |
| on_limit_reached | (budget) => void | — | Callback fired once when stand-down is activated. |
Proxy spend tracking uses the fixed server price of $0.0001 per paid /proxy request (cached hits are not charged).
Auto Strategy (Default)
In auto mode, ProxyClient intercepts the global fetch() within the request context so your route handlers require no changes.
import express from 'express';
import { ProxyClient } from '@canister-software/consensus-cli';
const app = express();
// Proxy only /price — all other routes use direct fetch
app.use(
ProxyClient(fetchWithPayment, {
mode: 'exclusive',
routes: ['/price'],
matchSubroutes: false,
strategy: 'auto',
cache_ttl: 60,
verbose: true,
})
);
// No changes needed — fetch() is automatically proxied for /price
app.get('/price', async (_req, res) => {
const response = await fetch('https://api.example.com/price');
res.json(await response.json());
});Manual Strategy
In manual mode, the proxy is not applied automatically. Use req.consensus.fetch() to explicitly proxy individual requests, or req.consensus.request() for a lower-level structured payload:
app.use(ProxyClient(fetchWithPayment, { strategy: 'manual' }));
app.get('/data', async (req, res) => {
// Proxied fetch — returns a standard Response
const response = await req.consensus.fetch('https://api.example.com/data');
res.json(await response.json());
// Or use the structured request helper — returns a ProxyResponseShape
const result = await req.consensus.request({
target_url: 'https://api.example.com/data',
method: 'GET',
});
res.json(result.data);
});Per-Request Node Selection
Both req.consensus.fetch() and req.consensus.request() accept per-request options as a second argument to override node routing at the call level:
const response = await req.consensus.fetch(
'https://api.example.com/data',
{ method: 'GET' },
{ node_region: 'us-east', cache_ttl: 30 }
);Framework-Agnostic Usage
Use runWithPath() to scope interception in any server framework and createFetch() for explicit route-scoped fetch:
const proxy = ProxyClient(fetchWithPayment, {
mode: 'exclusive',
routes: ['/api'],
limit_usd: 1.25,
});
await proxy.runWithPath('/api', async () => {
const response = await fetch('https://api.example.com/data');
console.log(await response.json());
});
const apiFetch = proxy.createFetch('/api');
const directFetch = proxy.createFetch('/health');SocketClient
SocketClient(fetchWithPayment, options) returns a client for opening paid WebSocket sessions through the Consensus Network. Token acquisition and reconnection are handled automatically.
SocketClient Options
| Option | Type | Default | Description |
| --------------------- | ---------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------- |
| openTimeoutMs | number | 12000 | Milliseconds to wait for the WebSocket connection to open before timing out. |
| reconnectIntervalMs | number | 2000 | Milliseconds between automatic reconnection attempts. |
| defaults | ConsensusSocketTokenParams | — | Default token parameters applied to every requestToken() call unless overridden. |
| limit_usd | number | — | Max WebSocket spend in USD (up to 6 decimals). If next token quote exceeds remaining budget, token request is blocked. |
| on_limit_reached | (budget) => void | — | Callback fired once when the WebSocket spend limit is reached. |
| webSocketFactory | constructor | auto-detected | Custom WebSocket constructor (browser WebSocket or ws for Node.js). Auto-detected if not provided. |
WebSocket spend checks use a local quote from the known pricing model (model, minutes, megabytes) before token purchase, so there is no additional price-check round trip.
Billing Models
Token requests accept a model parameter to control how your session is billed:
| Model | Description |
| ---------- | ------------------------------------------- |
| "hybrid" | Billed by both time and data (default). |
| "time" | Billed by duration only (minutes). |
| "data" | Billed by data transfer only (megabytes). |
Basic Usage
import { SocketClient } from '@canister-software/consensus-cli';
const client = SocketClient(fetchWithPayment, {
reconnectIntervalMs: 2000,
});
// Request a session token — pays for a time-based session
const auth = await client.requestToken({
model: 'time',
minutes: 5,
megabytes: 0,
});
// Connect using the token — returns a managed session
const session = await client.connect(auth);
session.on('open', () => console.log('Connected'));
session.on('message', (msg) => console.log('Received:', msg));
session.on('error', (err) => console.error('Error:', err));
session.on('close', () => console.log('Disconnected'));
session.send('hello');
// Close when done
session.close();Node Filtering
Target specific proxy nodes for WebSocket sessions:
const auth = await client.requestToken({
model: 'hybrid',
minutes: 10,
megabytes: 100,
nodeRegion: 'eu-west',
nodeExclude: 'node.example.com',
});Reconnection
Sessions reconnect automatically on unexpected disconnects. When reconnecting, SocketClient re-requests a fresh token using the same parameters from the last requestToken() call and re-establishes the WebSocket connection. Set reconnectIntervalMs to control retry pacing.
To stop reconnection, call session.close() — this sets an internal flag that suppresses all automatic retries.
Safe Mode
Both requestToken() and connect() support a { safe: true } option that catches errors and returns a result object instead of throwing:
const result = await client.requestToken({ model: 'time', minutes: 1 }, { safe: true });
if (!result.ok) {
console.error('Token request failed:', result.error);
} else {
const session = await client.connect(result.data);
}Session State
const state = session.getState();
// { connected: boolean, reconnecting: boolean, closedByCaller: boolean }CLI Commands
| Command | Description |
| ------------------------- | ------------------------------------------------------------------ |
| consensus setup | Create a wallet and register it with the x402 proxy. |
| consensus setup --force | Force re-create the account, resetting any existing configuration. |
| consensus help | Show help message. |
Setup Process
Run consensus setup to initialize your environment. The CLI will:
- Create a wallet using your CDP credentials.
- Generate
.consensus-config.jsoncontaining your wallet and delegation credentials. - Export wallet authorization to the x402 proxy for payment delegation.
- Add
.consensus-config.jsonto.gitignoreautomatically to prevent accidental commits.
Security
⚠️ Never commit
.consensus-config.json. It contains sensitive wallet credentials. The setup command adds it to.gitignoreautomatically, but verify this if you use a custom.gitignoresetup.
DO NOT keep large amounts in the proxy-delegated wallet - if the proxy is compromised, your delegation could be at risk. Only fund the wallet with amounts you're comfortable delegating for API payments.
