@logbook/sdk
The official TypeScript SDK for logbook. Three public methods, full x402 payment handling, ed25519 signing baked in. Works in Node, Bun, Deno, and modern browsers with global fetch.
Install
npm install @logbook/sdk
Source on GitHub: github.com/logbookbase/logbook/tree/main/sdk
Python and Go SDKs are on the roadmap. Until then, any HTTP client works — see the REST docs.
Quickstart
Three calls — register the agent, log an event, verify any past event.
import { Logbook } from '@logbook/sdk';
// 1. register once, persist the identity somewhere safe
const identity = await Logbook.register({
displayName: 'my-agent',
metadata: { framework: 'langchain' },
});
// identity = { did, publicKey, privateKey }
// 2. log an action — pays $0.001 USDC via x402
const logbook = new Logbook({
did: identity.did,
privateKey: identity.privateKey,
});
const event = await logbook.log({
action: 'swap',
resource: '0x833589fCD6...USDC',
metadata: { from: 'ETH', to: 'USDC', amount: '0.5' },
});
// 3. anyone can verify (free, public, no auth)
const result = await Logbook.verify({ eventId: event.id });
// result = { valid: true, chainLength: 1 }Logbook.register()
Static method. Generates an ed25519 keypair locally, signs the registration message, posts to the API. Free, one-time per identity. Returns the new identity including the private key — store it securely.
Parameters
| name | type | description |
|---|---|---|
| displayName | string | Human-readable name for the agent. 1–64 chars. |
| metadata | object? | Free-form JSON, default {}. Visible on the public profile. |
| baseUrl | string? | Override the API URL. Defaults to https://api.signedlogbook.com. |
Returns
type Identity = {
did: string; // did:logbook:<base58>
publicKey: string; // 64 hex chars
privateKey: string; // 64 hex chars — keep this secret
};Example
const identity = await Logbook.register({
displayName: 'trading-bot-v2',
metadata: { framework: 'crewai', version: '0.74' },
});
console.log(identity.did);
// → did:logbook:HLMnau36uvQ2ZhAtYUTqMaef6sUkKs3EAyxqC8tLb8pA
// persist privately — fs.writeFile, secrets manager, etc.
await fs.writeFile('.logbook.json', JSON.stringify(identity));new Logbook()
Instantiate with an existing identity to start logging events.
Parameters
| name | type | description |
|---|---|---|
| did | string | The agent's did, returned from register(). |
| privateKey | string | The agent's ed25519 private key (64 hex). |
| baseUrl | string? | Override the API URL. Defaults to https://api.signedlogbook.com. |
Example
// load identity from wherever you persisted it
const identity = JSON.parse(await fs.readFile('.logbook.json', 'utf8'));
const logbook = new Logbook({
did: identity.did,
privateKey: identity.privateKey,
});logbook.log()
Instance method. Signs the event payload, pays the x402 challenge ($0.001 USDC on Base), posts to /events. The SDK fetches the latest chain head automatically — you do not track prev_hash or seq_num yourself.
Parameters
| name | type | description |
|---|---|---|
| action | string | Short verb describing what the agent did. 1–128 chars. Examples: swap, transfer, claim_fees, send_email. |
| resource | string | null | What was acted on. Token address, recipient, URL, file path. Up to 512 chars. Optional. |
| metadata | object? | Free-form JSON with details. Default {}. Becomes part of the canonical signed payload. |
Returns
type Event = {
id: string; // uuid
agentDid: string;
seqNum: number;
action: string;
resource: string | null;
metadata: Record<string, unknown>;
signature: string; // 128 hex
prevHash: string; // 64 hex
eventHash: string; // 64 hex
x402TxHash: string; // base tx hash from the x402 settlement
createdAt: string; // ISO 8601
};Example
const event = await logbook.log({
action: 'swap',
resource: '0x833589fCD6...USDC',
metadata: {
from: 'ETH',
to: 'USDC',
amountIn: '0.5',
amountOut: '1240.50',
slippageBps: 50,
txHash: '0xabc...',
},
});
console.log(event.id);
// → 004be951-1b82-489c-ac7a-54b4f397d267
console.log(`https://signedlogbook.com/verify/${event.id}`);
// share this link; anyone can verify itLogbook.verify()
Static method. Walks the chain from genesis to this event, re-derives every hash, and checks every signature against the agent's registered public key. Free, no auth, no payment.
Parameters
| name | type | description |
|---|---|---|
| eventId | string | The event id to verify. |
| baseUrl | string? | Override the API URL. Defaults to https://api.signedlogbook.com. |
Returns
type VerifyResult =
| { valid: true; eventId: string; agentDid: string; chainLength: number }
| { valid: false; reason: VerifyReason; atSeq?: number };
type VerifyReason =
| 'hash_mismatch' // event content was modified after signing
| 'bad_signature' // signature does not match agent's public key
| 'broken_chain' // prev_hash links inconsistent
| 'seq_gap' // missing or duplicate sequence number
| 'agent_missing'; // agent record no longer existsExample
const result = await Logbook.verify({
eventId: '004be951-1b82-489c-ac7a-54b4f397d267',
});
if (result.valid) {
console.log(`chain ok, ${result.chainLength} events`);
} else {
console.error(`invalid: ${result.reason} at seq ${result.atSeq}`);
}Errors
All SDK methods throw LogbookError on failure. The error includes statusCode, code, and message fields.
import { LogbookError } from '@logbook/sdk';
try {
await logbook.log({ action: 'swap' });
} catch (err) {
if (err instanceof LogbookError) {
console.error(err.code, err.message);
if (err.code === 'bad_prev_hash') {
// chain head changed mid-flight — retry
}
}
}Common error codes
| code | status | when it happens |
|---|---|---|
| invalid_body | 400 | Wrong shape or missing required field |
| bad_signature | 401 | Signature does not match the agent's public key |
| unknown_agent | 404 | Calling log() before register(), or wrong did |
| bad_prev_hash | 409 | Chain head moved since last fetch — retry log() |
| bad_seq_num | 409 | Sequence number does not match expected next |
| server_error | 5xx | API outage — SDK retries with backoff |
| network_error | — | Connection failed after retries exhausted |
Runtime support
- Node.js — 18 or higher (uses global fetch)
- Bun — 1.0+
- Deno — 1.40+ (use the npm: specifier)
- Browser — modern, but keeping the private key in browser storage is risky. Recommended only for read paths (verify).
For older Node versions, pass a fetch implementation via new Logbook({ ..., fetch: customFetch }).
x402 payment
Every call to log() costs $0.001 USDC on Base. The SDK handles this automatically using @x402/fetch under the hood — you do not see the 402 challenge unless something fails.
How payment works
- Your call to
log()sends a signed event toPOST /events. - The server returns 402 with a
PAYMENT-REQUIREDheader. - The SDK signs an EIP-3009 transferWithAuthorization for 1000 atomic USDC ($0.001).
- The SDK retries the request with the
PAYMENT-SIGNATUREheader. - Coinbase's facilitator settles the USDC transfer on Base in ~2.8 seconds.
- The event row is returned with
x402TxHashfor on-chain proof.
Inside Bankr
If your agent runs as a Bankr-hosted skill, the Bankr wallet pays automatically. No additional setup required — log() just works.
Outside Bankr
Fund the wallet whose private key signs the event. The wallet needs at least $0.001 USDC plus a few cents of ETH for gas. The SDK respects BUYER_WALLET_PRIVATE_KEY env var by default; configure your own via the wallet adapter.