Skip to content
This repository was archived by the owner on Oct 20, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Changesets

Every PR that changes a published package should include a changeset. Create one with:

```bash
bun run changeset
```

## Version linkage

- `@worldcoin/agentkit-core` and `@worldcoin/agentkit` are `fixed` — they always share a version number. Bumping one automatically bumps the other.
- `@worldcoin/agentkit-cli` versions independently.

## Releasing

Releases are automated by `.github/workflows/release.yml`. When a commit lands on `main` with one or more changesets, the workflow opens a "Version Packages" PR that bumps versions and updates changelogs. Merging that PR triggers `bun release` → `changeset publish`, which publishes to npm using trusted publishing (OIDC).

No local `bun publish` is needed.
16 changes: 16 additions & 0 deletions .changeset/chain-agnostic-agentbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
'@worldcoin/agentkit-core': patch
'@worldcoin/agentkit': patch
---

Make AgentBook lookup chain-agnostic from the caller side.

`createAgentBookVerifier()` now always resolves against the canonical AgentBook
deployment on World Chain (`eip155:480`), regardless of which chain the agent's
signature was produced on or which chain your paid route runs on.

`lookupHuman(address)` no longer takes a `chainId` parameter — the argument was
already ignored in the default path, so the call site cleanup is the only
change integrators need to make. `AgentBookOptions` is unchanged; `rpcUrl` and
`contractAddress` still work, now with "custom World Chain RPC" and "custom
AgentBook contract on World Chain" semantics.
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["@worldcoin/agentkit-core", "@worldcoin/agentkit"]],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
42 changes: 42 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Release

on:
push:
branches:
- main

concurrency: ${{ github.workflow }}-${{ github.ref }}

permissions:
contents: write
pull-requests: write
id-token: write

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 (https://github.com/actions/checkout/releases/tag/v6.0.0)

- name: Setup Node.js 24.x
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 (https://github.com/actions/setup-node/releases/tag/v6.0.0)
with:
node-version: 24.x
registry-url: 'https://registry.npmjs.org/'

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 (https://github.com/oven-sh/setup-bun/releases/tag/v2.2.0)

- name: Install Dependencies
run: bun install

- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba # v1.5.3 (https://github.com/changesets/action/releases/tag/v1.5.3)
with:
publish: bun release
env:
GITHUB_TOKEN: ${{ github.token }}
NPM_TOKEN: '' # https://github.com/changesets/changesets/issues/1152#issuecomment-3190884868
7 changes: 5 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ name: Solidity CI

permissions: {}

# TODO: Re-enable push and pull_request triggers once the Solidity CI tests
# are fixed. Until then the workflow is manually-triggerable only so it does
# not fail every PR.
on:
push:
pull_request:
# push:
# pull_request:
workflow_dispatch:

env:
Expand Down
439 changes: 433 additions & 6 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
},
"scripts": {
"build": "tsc",
"prepublishOnly": "bun run build",
"publish:npm": "npm publish",
"cli": "tsx src/index.ts"
},
"dependencies": {
Expand Down
4 changes: 1 addition & 3 deletions core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
"access": "public"
},
"scripts": {
"build": "tsup",
"prepublishOnly": "bun run build",
"publish:npm": "bun publish"
"build": "tsup"
},
"dependencies": {
"@scure/base": "^1.2.6",
Expand Down
38 changes: 9 additions & 29 deletions core/src/agent-book.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { toHex, type PublicClient } from 'viem'
import { extractEVMChainId } from './evm'
import { worldchain } from 'viem/chains'
import { getPublicClient } from './viem-client'

const WORLD_MAINNET = 'eip155:480' as const

const AGENT_BOOK_ADDRESS: `0x${string}` = '0xA23aB2712eA7BBa896930544C7d6636a96b944dA'

const AGENT_BOOK_ABI = [
Expand All @@ -17,42 +15,24 @@ const AGENT_BOOK_ABI = [
] as const

export interface AgentBookOptions {
/** Custom viem PublicClient. Overrides automatic client creation. */
/** Custom viem PublicClient. Advanced override for testing or custom deployments. */
client?: PublicClient
/** Custom contract address. Overrides the built-in World Chain deployment. */
/** Custom AgentBook contract address on World Chain. Defaults to the canonical deployment. */
contractAddress?: `0x${string}`
/** Custom RPC URL. Defaults to the chain's default RPC. */
/** Custom World Chain RPC URL. Defaults to the chain's default public RPC. */
rpcUrl?: string
}

export function createAgentBookVerifier(options: AgentBookOptions = {}) {
function getClient(chainId: string): PublicClient {
if (options.client) return options.client

const numericId =
options.contractAddress && options.rpcUrl
? extractEVMChainId(chainId)
: extractEVMChainId(WORLD_MAINNET)

return getPublicClient(numericId, options.rpcUrl)
}

function getContractAddress(): `0x${string}` {
if (options.contractAddress) return options.contractAddress
return AGENT_BOOK_ADDRESS
}

return {
/**
* Look up the anonymous human identifier for an agent's wallet address.
* @param address The agent's wallet address.
* @param chainId CAIP-2 chain identifier (e.g. "eip155:480"). Lookup always
* resolves to the World Chain AgentBook deployment.
* @returns The human ID (hex string) if registered, or null.
* Always resolves against the AgentBook deployment on World Chain, regardless
* of which chain the agent's signature was produced on.
*/
async lookupHuman(address: string, chainId: string): Promise<string | null> {
const contractAddress = getContractAddress()
const client = getClient(chainId)
async lookupHuman(address: string): Promise<string | null> {
const contractAddress = options.contractAddress ?? AGENT_BOOK_ADDRESS
const client = options.client ?? getPublicClient(worldchain.id, options.rpcUrl)

try {
const humanId = await client.readContract({
Expand Down
16 changes: 8 additions & 8 deletions core/tests/agent-book.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,37 @@ function createMockClient(result: bigint, calls: Array<{ address: string }>): Pu
}

describe('createAgentBookVerifier', () => {
it('uses the World Chain AgentBook when the request chain is World Chain', async () => {
it('looks up addresses against the World Chain AgentBook', async () => {
const calls: Array<{ address: string }> = []
const verifier = createAgentBookVerifier({
client: createMockClient(1n, calls),
})

const humanId = await verifier.lookupHuman('0x1234567890abcdef1234567890abcdef12345678', 'eip155:480')
const humanId = await verifier.lookupHuman('0x1234567890abcdef1234567890abcdef12345678')

expect(humanId).toBe('0x1')
expect(calls).toEqual([{ address: '0xA23aB2712eA7BBa896930544C7d6636a96b944dA' }])
})

it('defaults to World Chain for unknown chains', async () => {
it('returns null for unregistered addresses', async () => {
const calls: Array<{ address: string }> = []
const verifier = createAgentBookVerifier({
client: createMockClient(1n, calls),
client: createMockClient(0n, calls),
})

await verifier.lookupHuman('0x1234567890abcdef1234567890abcdef12345678', 'eip155:84532')
const humanId = await verifier.lookupHuman('0x1234567890abcdef1234567890abcdef12345678')

expect(calls).toEqual([{ address: '0xA23aB2712eA7BBa896930544C7d6636a96b944dA' }])
expect(humanId).toBeNull()
})

it('still honors custom contract deployments', async () => {
it('honors custom contract deployments', async () => {
const calls: Array<{ address: string }> = []
const verifier = createAgentBookVerifier({
client: createMockClient(1n, calls),
contractAddress: '0x9999999999999999999999999999999999999999',
})

await verifier.lookupHuman('0x1234567890abcdef1234567890abcdef12345678', 'eip155:480')
await verifier.lookupHuman('0x1234567890abcdef1234567890abcdef12345678')

expect(calls).toEqual([{ address: '0x9999999999999999999999999999999999999999' }])
})
Expand Down
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "x402-agentkit-poc",
"devDependencies": {
"@changesets/cli": "^2.30.0",
"@typescript-eslint/parser": "^8.56.1",
"prettier": "^3.8.1",
"prettier-plugin-sort-imports-desc": "^1.0.0",
Expand All @@ -9,9 +10,10 @@
"scripts": {
"build:core": "bun run --cwd core build",
"build:x402": "bun run --cwd x402 build",
"build": "bun run build:core && bun run build:x402",
"publish:core": "bun run --cwd core publish:npm",
"publish:x402": "bun run --cwd x402 publish:npm"
"build:cli": "bun run --cwd cli build",
"build": "bun run build:core && bun run build:x402 && bun run build:cli",
"changeset": "changeset",
"release": "bun run build && changeset publish"
},
"prettier": {
"parser": "@typescript-eslint/parser",
Expand All @@ -30,6 +32,7 @@
"private": true,
"workspaces": [
"core",
"x402"
"x402",
"cli"
]
}
56 changes: 27 additions & 29 deletions skills/integrate-agentkit/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: integrate-agentkit
description: Use this skill when integrating @worldcoin/agentkit into an x402 server or facilitator flow: choose free/free-trial/discount mode, wire World Chain payments, pin AgentBook lookup to Base when needed, handle ExactEvmScheme money parsing, or finish an integration end-to-end.
description: Use this skill when integrating @worldcoin/agentkit into an x402 server or facilitator flow: choose free/free-trial/discount mode, wire payments on any supported EVM chain, handle ExactEvmScheme money parsing, or finish an integration end-to-end.
---

# Integrate AgentKit
Expand All @@ -13,16 +13,15 @@ If the developer has not already answered these, ask before choosing an implemen

1. Which access mode do they want: `free`, `free-trial`, or `discount`?
2. Which payment network should the protected route use?
3. If they want World Chain payments, should AgentBook lookup stay on Base mainnet?
4. Do they control the facilitator path, or are they using a hosted facilitator?
5. Do they also need agent registration, or only request-time verification?
3. Do they control the facilitator path, or are they using a hosted facilitator?
4. Do they also need agent registration, or only request-time verification?

## Default recommendation

For most production integrations:

- Put paid routes on World Chain (`eip155:480`).
- Keep AgentBook registration and lookup on Base mainnet (`eip155:8453`).
- Put paid routes on whichever network the developer's facilitator already supports (commonly World Chain `eip155:480` or Base `eip155:8453`).
- Leave AgentBook lookup alone — `createAgentBookVerifier()` always resolves against the canonical World Chain deployment. The developer does not need to think about which chain the registry lives on.
- Start with `free-trial` unless the developer explicitly wants `free` or `discount`.
- Only choose `discount` when you can wire `hooks.verifyFailureHook` into the facilitator flow you control.

Expand All @@ -31,50 +30,49 @@ For most production integrations:
- x402 resource server: the protected HTTP route and 402 challenge flow
- facilitator: verifies and settles payment payloads; required for `discount`
- AgentKit extension: adds the CAIP-122 challenge and verifies the signed `agentkit` header
- AgentBook: on-chain registry that maps the agent wallet to an anonymous human ID
- AgentBook: on-chain registry on World Chain that maps the agent wallet to an anonymous human ID. Lookup is always against World Chain regardless of the payment chain — the caller side is chain-agnostic.
- storage: per-human usage tracking for `free-trial` and `discount`
- registration path: separate from request-time verification; use `npx @worldcoin/agentkit-cli --llms` if the developer also needs registration help

## Workflow

1. Read [`../../agentkit/DOCS.md`](../../agentkit/DOCS.md) first. It should be the primary integration playbook.
2. Confirm exported APIs in [`../../agentkit/src/index.ts`](../../agentkit/src/index.ts) before adding imports.
1. Read [`../../x402/DOCS.md`](../../x402/DOCS.md) first. It should be the primary integration playbook.
2. Confirm exported APIs in [`../../core/src/index.ts`](../../core/src/index.ts) and [`../../x402/src/index.ts`](../../x402/src/index.ts) before adding imports.
3. Prefer the hooks-based path:
- `declareAgentkitExtension`
- `agentkitResourceServerExtension`
- `createAgentkitHooks`
- `createAgentBookVerifier`
- `declareAgentkitExtension`
- `agentkitResourceServerExtension`
- `createAgentkitHooks`
- `createAgentBookVerifier`
4. If the payment network is World Chain (`eip155:480`), add a custom `ExactEvmScheme().registerMoneyParser(...)` for World Chain USDC. Do not assume the server scheme has a working default stablecoin for World Chain.
5. If the product should keep AgentBook on Base, configure `createAgentBookVerifier` with an explicit Base client and Base AgentBook address instead of relying on the request chain.
5. Call `createAgentBookVerifier()` with no arguments in the common case. Pass `rpcUrl` or `contractAddress` only for custom World Chain endpoints or non-canonical deployments.
6. If the mode is `free-trial` or `discount`, add persistent `AgentKitStorage`. `InMemoryAgentKitStorage` is only for demos.
7. If the mode is `discount`, wire `hooks.verifyFailureHook` into the facilitator. Without it, discounted underpayments will fail verification.
8. Verify the whole path end-to-end:
- 402 response includes the `agentkit` extension
- registered agent gets the intended behavior
- unregistered agent falls back to normal payment
- replay protection and storage behavior work as expected
- 402 response includes the `agentkit` extension
- registered agent gets the intended behavior
- unregistered agent falls back to normal payment
- replay protection and storage behavior work as expected

## Ground rules

- Prefer the hooks-based integration unless the user explicitly needs the low-level flow.
- Use [`../../agentkit/DOCS.md`](../../agentkit/DOCS.md) as the primary reference for examples and mode behavior.
- Be explicit about the payment chain and the AgentBook lookup chain. They can be different.
- World Chain payments plus Base AgentBook lookup is a supported pattern when you pin the verifier to Base.
- Use [`../../x402/DOCS.md`](../../x402/DOCS.md) as the primary reference for examples and mode behavior.
- Do not introduce any "pin AgentBook to chain X" language — lookup is always against the canonical World Chain deployment. The payment chain and the AgentBook lookup chain are decoupled on purpose, and callers should never have to think about the lookup chain.
- Do not document World Chain with bare `new ExactEvmScheme()` only. Include the World Chain money parser.
- Do not choose `discount` unless the facilitator hook can actually be registered.
- Confirm exports in [`../../agentkit/src/index.ts`](../../agentkit/src/index.ts) before adding or documenting imports.
- Confirm exports in [`../../core/src/index.ts`](../../core/src/index.ts) and [`../../x402/src/index.ts`](../../x402/src/index.ts) before adding or documenting imports.

## Constants to keep handy

- World Chain payment network: `eip155:480`
- Base mainnet AgentBook network: `eip155:8453`
- Base mainnet AgentBook contract: `0xE1D1D3526A6FAa37eb36bD10B933C1b77f4561a4`
- World Chain AgentBook contract: `0xA23aB2712eA7BBa896930544C7d6636a96b944dA`
- World Chain USDC: `0x79A02482A880bCE3F13e09Da970dC34db4CD24d1`

## Reference files

- Integration docs: [`../../agentkit/DOCS.md`](../../agentkit/DOCS.md)
- Public exports: [`../../agentkit/src/index.ts`](../../agentkit/src/index.ts)
- Hooks: [`../../agentkit/src/hooks.ts`](../../agentkit/src/hooks.ts)
- AgentBook verifier: [`../../agentkit/src/agent-book.ts`](../../agentkit/src/agent-book.ts)
- Header parsing and verification: [`../../agentkit/src/parse.ts`](../../agentkit/src/parse.ts), [`../../agentkit/src/validate.ts`](../../agentkit/src/validate.ts), [`../../agentkit/src/verify.ts`](../../agentkit/src/verify.ts)
- Integration docs: [`../../x402/DOCS.md`](../../x402/DOCS.md)
- Core public exports: [`../../core/src/index.ts`](../../core/src/index.ts)
- x402 public exports: [`../../x402/src/index.ts`](../../x402/src/index.ts)
- Hooks: [`../../x402/src/hooks.ts`](../../x402/src/hooks.ts)
- AgentBook verifier: [`../../core/src/agent-book.ts`](../../core/src/agent-book.ts)
- Header parsing and verification: [`../../core/src/parse.ts`](../../core/src/parse.ts), [`../../core/src/validate.ts`](../../core/src/validate.ts), [`../../core/src/verify.ts`](../../core/src/verify.ts)
Loading
Loading