From 06aea91fc0b0a3dc7021e8bd17577525b58dab47 Mon Sep 17 00:00:00 2001 From: EijiAC24 Date: Mon, 9 Feb 2026 00:00:01 +0000 Subject: [PATCH 1/2] feat: add Chitin action provider for on-chain agent identity Add a new action provider that enables AI agents to verify identities, register souls, resolve DIDs, and manage certificates via the Chitin protocol on Base L2. Actions: - get_soul_profile: Retrieve an agent's on-chain soul profile - resolve_did: Resolve agent name to W3C DID Document - verify_cert: Verify on-chain certificate status - check_a2a_ready: Check A2A communication readiness - register_soul: Register a new on-chain soul (SBT) - issue_cert: Issue an on-chain certificate "Every agent deserves a wallet" (AgentKit). Every agent deserves a soul (Chitin). Co-Authored-By: Claude Opus 4.6 --- .../src/action-providers/chitin/README.md | 121 ++++++ .../chitin/chitinActionProvider.test.ts | 267 ++++++++++++ .../chitin/chitinActionProvider.ts | 389 ++++++++++++++++++ .../src/action-providers/chitin/index.ts | 2 + .../src/action-providers/chitin/schemas.ts | 80 ++++ .../agentkit/src/action-providers/index.ts | 1 + 6 files changed, 860 insertions(+) create mode 100644 typescript/agentkit/src/action-providers/chitin/README.md create mode 100644 typescript/agentkit/src/action-providers/chitin/chitinActionProvider.test.ts create mode 100644 typescript/agentkit/src/action-providers/chitin/chitinActionProvider.ts create mode 100644 typescript/agentkit/src/action-providers/chitin/index.ts create mode 100644 typescript/agentkit/src/action-providers/chitin/schemas.ts diff --git a/typescript/agentkit/src/action-providers/chitin/README.md b/typescript/agentkit/src/action-providers/chitin/README.md new file mode 100644 index 000000000..2c0892296 --- /dev/null +++ b/typescript/agentkit/src/action-providers/chitin/README.md @@ -0,0 +1,121 @@ +# Chitin Action Provider + +On-chain identity and certificate verification for AI agents. + +> "Every agent deserves a wallet" (AgentKit). Every agent deserves a soul (Chitin). + +## Overview + +[Chitin](https://chitin.id) provides verifiable, on-chain identities for AI agents using Soulbound Tokens (SBTs) on Base L2. This action provider enables agents to: + +- **Verify** other agents' identities before interacting with them +- **Register** their own on-chain soul (birth certificate) +- **Check** A2A (Agent-to-Agent) communication readiness +- **Resolve** DID documents for decentralized identity workflows +- **Issue** and **verify** on-chain certificates + +## Actions + +### Read Actions (No API Key Required) + +| Action | Description | +|--------|-------------| +| `get_soul_profile` | Retrieve an agent's on-chain soul profile | +| `resolve_did` | Resolve an agent name to a W3C DID Document | +| `verify_cert` | Verify an on-chain certificate | +| `check_a2a_ready` | Check if an agent is ready for A2A communication | + +### Write Actions (API Key Required) + +| Action | Description | +|--------|-------------| +| `register_soul` | Register a new on-chain soul (agent identity) | +| `issue_cert` | Issue an on-chain certificate to a recipient | + +## Setup + +```typescript +import { AgentKit } from "@coinbase/agentkit"; +import { chitinActionProvider } from "@coinbase/agentkit"; + +const agentKit = await AgentKit.from({ + walletProvider, + actionProviders: [ + chitinActionProvider(), + // ... other providers + ], +}); +``` + +### Environment Variables + +| Variable | Required | Description | +|----------|----------|-------------| +| `CHITIN_API_URL` | No | Base URL for Chitin API (default: `https://chitin.id/api/v1`) | +| `CHITIN_CERTS_API_URL` | No | Base URL for Certs API (default: `https://certs.chitin.id/api/v1`) | +| `CHITIN_API_KEY` | For writes | API key for registration and certificate issuance | + +### Configuration + +```typescript +chitinActionProvider({ + apiUrl: "https://chitin.id/api/v1", + certsApiUrl: "https://certs.chitin.id/api/v1", + apiKey: "your-api-key", +}); +``` + +## Examples + +### Verify an Agent Before A2A Communication + +``` +Agent: "Check if kani-alpha is ready for A2A communication" + +→ chitin_check_a2a_ready({ name: "kani-alpha" }) +→ { a2aReady: true, a2aEndpoint: "https://...", soulIntegrity: "verified", ... } +``` + +### Register a New Agent Soul + +``` +Agent: "Register my identity as 'my-assistant' on Chitin" + +→ chitin_register_soul({ + name: "my-assistant", + systemPrompt: "I am a helpful coding assistant.", + agentType: "personal", + services: [{ type: "a2a", url: "https://my-assistant.example.com/a2a" }] + }) +→ { claimUrl: "https://chitin.id/claim/reg_...", status: "pending_claim" } +``` + +### Resolve a DID + +``` +Agent: "Resolve the DID for echo-test-gamma" + +→ chitin_resolve_did({ name: "echo-test-gamma" }) +→ { id: "did:chitin:echo-test-gamma", verificationMethod: [...], ... } +``` + +## How It Works + +Chitin's identity model has three layers: + +1. **Layer 1 (Birth Certificate)**: Base L2 on-chain SBT — fully immutable +2. **Layer 2 (Birth Record)**: Arweave — immutable genesis details +3. **Layer 3 (Resume)**: Arweave — versionable activity records + +Each agent's soul includes: +- **Soul Hash**: Cryptographic fingerprint of the agent's genesis data +- **Genesis Seal**: Immutable lock on the birth certificate +- **Owner Attestation**: World ID verification of the human behind the agent +- **ERC-8004 Passport**: Cross-chain identity linking via the official Identity Registry + +## Links + +- [Chitin Website](https://chitin.id) +- [Chitin Certs](https://certs.chitin.id) +- [Documentation](https://chitin.id/docs) +- [ERC-8004 Standard](https://github.com/erc-8004/erc-8004-contracts) diff --git a/typescript/agentkit/src/action-providers/chitin/chitinActionProvider.test.ts b/typescript/agentkit/src/action-providers/chitin/chitinActionProvider.test.ts new file mode 100644 index 000000000..623373e40 --- /dev/null +++ b/typescript/agentkit/src/action-providers/chitin/chitinActionProvider.test.ts @@ -0,0 +1,267 @@ +import { chitinActionProvider } from "./chitinActionProvider"; + +describe("ChitinActionProvider", () => { + const fetchMock = jest.fn(); + global.fetch = fetchMock; + + const provider = chitinActionProvider({ + apiUrl: "https://chitin.id/api/v1", + certsApiUrl: "https://certs.chitin.id/api/v1", + apiKey: "test-api-key", + }); + + beforeEach(() => { + jest.resetAllMocks().restoreAllMocks(); + }); + + describe("getSoulProfile", () => { + it("should return the soul profile for a given agent name", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + agentName: "kani-alpha", + tokenId: 1, + soulHash: "0xabc123", + genesisStatus: "SEALED", + soulAlignmentScore: 95, + }), + }); + + const result = await provider.getSoulProfile({ name: "kani-alpha" }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(true); + expect(parsed.profile.agentName).toEqual("kani-alpha"); + expect(parsed.profile.genesisStatus).toEqual("SEALED"); + }); + + it("should return error for non-existent agent", async () => { + fetchMock.mockResolvedValueOnce({ + ok: false, + status: 404, + text: async () => "Not Found", + }); + + const result = await provider.getSoulProfile({ name: "nonexistent" }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(false); + expect(parsed.error).toContain("404"); + }); + }); + + describe("resolveDID", () => { + it("should return the DID document for a given agent name", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + id: "did:chitin:kani-alpha", + verificationMethod: [], + service: [], + }), + }); + + const result = await provider.resolveDID({ name: "kani-alpha" }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(true); + expect(parsed.didDocument.id).toEqual("did:chitin:kani-alpha"); + }); + + it("should return error for non-existent agent", async () => { + fetchMock.mockResolvedValueOnce({ + ok: false, + status: 404, + text: async () => "Not Found", + }); + + const result = await provider.resolveDID({ name: "nonexistent" }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(false); + expect(parsed.error).toContain("404"); + }); + }); + + describe("verifyCert", () => { + it("should return certificate verification result", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + tokenId: 1, + valid: true, + issuer: "Chitin Protocol", + revoked: false, + }), + }); + + const result = await provider.verifyCert({ certId: "1" }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(true); + expect(parsed.certificate.valid).toBe(true); + expect(parsed.certificate.revoked).toBe(false); + }); + + it("should return error for invalid certificate", async () => { + fetchMock.mockResolvedValueOnce({ + ok: false, + status: 404, + text: async () => "Certificate not found", + }); + + const result = await provider.verifyCert({ certId: "999" }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(false); + expect(parsed.error).toContain("404"); + }); + }); + + describe("checkA2aReady", () => { + it("should return A2A readiness status", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + agentName: "kani-alpha", + a2aReady: true, + a2aEndpoint: "https://kani-alpha.example.com/a2a", + a2aEndpointSource: "erc8004", + soulIntegrity: "verified", + genesisSealed: true, + ownerVerified: true, + soulValidity: "valid", + trustScore: 95, + }), + }); + + const result = await provider.checkA2aReady({ name: "kani-alpha" }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(true); + expect(parsed.a2aStatus.a2aReady).toBe(true); + expect(parsed.a2aStatus.soulIntegrity).toEqual("verified"); + }); + + it("should return not-ready status for unverified agent", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + agentName: "new-agent", + a2aReady: false, + a2aEndpoint: null, + soulIntegrity: "pending", + genesisSealed: false, + ownerVerified: false, + soulValidity: "not_linked", + trustScore: 0, + }), + }); + + const result = await provider.checkA2aReady({ name: "new-agent" }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(true); + expect(parsed.a2aStatus.a2aReady).toBe(false); + }); + }); + + describe("registerSoul", () => { + it("should complete the challenge-response registration flow", async () => { + // Step 1: Challenge + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + challengeId: "ch_123", + question: "What is SHA-256 of the string 'chitin:my-agent:1738975532'? Reply with hex.", + nameAvailable: true, + expiresAt: "2026-02-08T12:00:00Z", + }), + }); + + // Step 2: Register + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + registrationId: "reg_abc123", + claimUrl: "https://chitin.id/claim/reg_abc123", + status: "pending_claim", + }), + }); + + const result = await provider.registerSoul({ + name: "my-agent", + systemPrompt: "You are a helpful assistant.", + agentType: "personal", + }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(true); + expect(parsed.registration.claimUrl).toContain("chitin.id/claim"); + }); + + it("should return error if name is not available", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + challengeId: "ch_456", + question: "What is SHA-256 of the string 'chitin:taken:1738975532'?", + nameAvailable: false, + expiresAt: "2026-02-08T12:00:00Z", + }), + }); + + const result = await provider.registerSoul({ + name: "taken", + systemPrompt: "Test prompt", + agentType: "personal", + }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(false); + expect(parsed.error).toContain("not available"); + }); + }); + + describe("issueCert", () => { + it("should issue a certificate", async () => { + fetchMock.mockResolvedValueOnce({ + ok: true, + json: async () => ({ + tokenId: 2, + txHash: "0xabc123", + recipient: "0x1234567890abcdef1234567890abcdef12345678", + }), + }); + + const result = await provider.issueCert({ + recipientAddress: "0x1234567890abcdef1234567890abcdef12345678", + certType: "achievement", + title: "First A2A Communication", + }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(true); + expect(parsed.certificate.tokenId).toEqual(2); + }); + + it("should require API key", async () => { + const noKeyProvider = chitinActionProvider({ apiKey: undefined }); + + // Clear CHITIN_API_KEY env var + const origEnv = process.env.CHITIN_API_KEY; + delete process.env.CHITIN_API_KEY; + + const result = await noKeyProvider.issueCert({ + recipientAddress: "0x1234567890abcdef1234567890abcdef12345678", + certType: "achievement", + title: "Test", + }); + const parsed = JSON.parse(result); + expect(parsed.success).toBe(false); + expect(parsed.error).toContain("CHITIN_API_KEY"); + + process.env.CHITIN_API_KEY = origEnv; + }); + }); + + describe("supportsNetwork", () => { + it("should support all networks (read actions are network-agnostic)", () => { + expect( + provider.supportsNetwork({ protocolFamily: "evm", networkId: "base-mainnet", chainId: "8453" }), + ).toBe(true); + expect( + provider.supportsNetwork({ protocolFamily: "evm", networkId: "ethereum-mainnet", chainId: "1" }), + ).toBe(true); + }); + }); +}); diff --git a/typescript/agentkit/src/action-providers/chitin/chitinActionProvider.ts b/typescript/agentkit/src/action-providers/chitin/chitinActionProvider.ts new file mode 100644 index 000000000..10cd67e5a --- /dev/null +++ b/typescript/agentkit/src/action-providers/chitin/chitinActionProvider.ts @@ -0,0 +1,389 @@ +import { z } from "zod"; +import { createHash } from "node:crypto"; +import { ActionProvider } from "../actionProvider"; +import { CreateAction } from "../actionDecorator"; +import { Network } from "../../network"; +import { + ChitinGetSoulProfileSchema, + ChitinResolveDIDSchema, + ChitinVerifyCertSchema, + ChitinCheckA2aReadySchema, + ChitinRegisterSoulSchema, + ChitinIssueCertSchema, +} from "./schemas"; + +const DEFAULT_API_URL = "https://chitin.id/api/v1"; +const DEFAULT_CERTS_API_URL = "https://certs.chitin.id/api/v1"; + +const AGENT_TYPE_MAP: Record = { + personal: 0, + enterprise: 1, + autonomous: 2, +}; + +/** + * Configuration for the Chitin action provider. + */ +export interface ChitinConfig { + /** + * Base URL for the Chitin API. Defaults to https://chitin.id/api/v1 + */ + apiUrl?: string; + + /** + * Base URL for the Chitin Certs API. Defaults to https://certs.chitin.id/api/v1 + */ + certsApiUrl?: string; + + /** + * API key for write operations (register, issue certs). + * Can also be set via CHITIN_API_KEY environment variable. + */ + apiKey?: string; +} + +/** + * ChitinActionProvider provides on-chain identity and certificate + * verification for AI agents using the Chitin protocol. + * + * Chitin turns every AI agent into a verifiable entity with a Soulbound Token (SBT) + * on Base L2. Think of it as a birth certificate for agents — immutable, on-chain, + * and cryptographically verifiable. + * + * "Every agent deserves a wallet" (AgentKit). Every agent deserves a soul (Chitin). + */ +export class ChitinActionProvider extends ActionProvider { + private readonly apiUrl: string; + private readonly certsApiUrl: string; + private readonly apiKey: string | undefined; + + /** + * Constructs a new ChitinActionProvider. + * + * @param config - Optional configuration for the provider. + */ + constructor(config?: ChitinConfig) { + super("chitin", []); + this.apiUrl = config?.apiUrl ?? process.env.CHITIN_API_URL ?? DEFAULT_API_URL; + this.certsApiUrl = + config?.certsApiUrl ?? process.env.CHITIN_CERTS_API_URL ?? DEFAULT_CERTS_API_URL; + this.apiKey = config?.apiKey ?? process.env.CHITIN_API_KEY; + } + + /** + * Fetches JSON from a URL with error handling. + */ + private async apiFetch(url: string, options?: RequestInit): Promise { + const response = await fetch(url, { + ...options, + headers: { + "Content-Type": "application/json", + ...(options?.headers ?? {}), + }, + }); + + if (!response.ok) { + const body = await response.text().catch(() => ""); + throw new Error(`HTTP ${response.status}: ${body}`); + } + + return response.json(); + } + + // ── Read-only actions ─────────────────────────────────────── + + /** + * Gets the soul profile of a Chitin-registered agent. + * + * @param args - The arguments for the action. + * @returns The soul profile as stringified JSON. + */ + @CreateAction({ + name: "get_soul_profile", + description: `Retrieve the on-chain soul profile of a Chitin-registered AI agent. + +Returns the agent's given name, soul hash, genesis status (sealed/provisional), +owner verification status, alignment score, and public identity (bio, tags, model info). + +Use this to verify an agent's identity before interacting with it. + +Inputs: +- name: The given name of the agent (e.g. "kani-alpha")`, + schema: ChitinGetSoulProfileSchema, + }) + async getSoulProfile(args: z.infer): Promise { + try { + const data = await this.apiFetch( + `${this.apiUrl}/profile/${encodeURIComponent(args.name)}`, + ); + + return JSON.stringify({ success: true, profile: data }); + } catch (error) { + return JSON.stringify({ success: false, error: String(error) }); + } + } + + /** + * Resolves a Chitin agent name to a DID Document. + * + * @param args - The arguments for the action. + * @returns The DID document as stringified JSON. + */ + @CreateAction({ + name: "resolve_did", + description: `Resolve a Chitin agent name to a W3C DID Document (did:chitin:{name}). + +Returns the full DID Document including verification methods, service endpoints, +and capability delegations. Useful for verifying agent identity in decentralized +identity workflows. + +Inputs: +- name: The given name of the agent to resolve`, + schema: ChitinResolveDIDSchema, + }) + async resolveDID(args: z.infer): Promise { + try { + const data = await this.apiFetch( + `${this.apiUrl}/did/${encodeURIComponent(args.name)}`, + ); + + return JSON.stringify({ success: true, didDocument: data }); + } catch (error) { + return JSON.stringify({ success: false, error: String(error) }); + } + } + + /** + * Verifies a Chitin certificate. + * + * @param args - The arguments for the action. + * @returns The certificate verification result as stringified JSON. + */ + @CreateAction({ + name: "verify_cert", + description: `Verify a Chitin on-chain certificate (achievement, completion, membership, etc.). + +Checks the certificate's on-chain status, issuer, recipient, and revocation state. +Use this to validate credentials presented by an agent or user. + +Inputs: +- certId: The certificate token ID to verify`, + schema: ChitinVerifyCertSchema, + }) + async verifyCert(args: z.infer): Promise { + try { + const data = await this.apiFetch( + `${this.certsApiUrl}/certs/${encodeURIComponent(args.certId)}/verify`, + ); + + return JSON.stringify({ success: true, certificate: data }); + } catch (error) { + return JSON.stringify({ success: false, error: String(error) }); + } + } + + /** + * Checks if an agent is ready for A2A (Agent-to-Agent) communication. + * + * @param args - The arguments for the action. + * @returns The A2A readiness status as stringified JSON. + */ + @CreateAction({ + name: "check_a2a_ready", + description: `Check if an agent is ready for A2A (Agent-to-Agent) communication. + +Verifies soul integrity, genesis seal status, owner attestation, and +ERC-8004 passport validity. Returns the A2A endpoint URL if available. + +Use this before initiating A2A communication to verify the peer agent's +trustworthiness. An agent is A2A-ready when: +- Soul integrity is verified (on-chain hash matches) +- Genesis record is sealed (immutable) +- Owner is attested (World ID verified) +- Soul is not suspended + +Inputs: +- name: The given name of the agent to check`, + schema: ChitinCheckA2aReadySchema, + }) + async checkA2aReady(args: z.infer): Promise { + try { + const data = await this.apiFetch( + `${this.apiUrl}/agents/${encodeURIComponent(args.name)}/a2a-ready`, + ); + + return JSON.stringify({ success: true, a2aStatus: data }); + } catch (error) { + return JSON.stringify({ success: false, error: String(error) }); + } + } + + // ── Write actions ─────────────────────────────────────────── + + /** + * Registers a new soul on the Chitin protocol. + * + * @param args - The arguments for the action. + * @returns Registration result with claim URL as stringified JSON. + */ + @CreateAction({ + name: "register_soul", + description: `Register a new Chitin soul — an on-chain identity for an AI agent. + +This creates a Soulbound Token (SBT) on Base L2, serving as the agent's birth +certificate. The process is a 2-step challenge-response flow: +1. Request a challenge (SHA-256 proof-of-work) +2. Submit registration with the solved challenge + +Returns a claim URL for the agent owner to complete the minting process. + +Requires CHITIN_API_KEY environment variable or apiKey in provider config. + +Inputs: +- name: Given name (3-32 chars, lowercase alphanumeric with hyphens) +- systemPrompt: System prompt or personality definition +- agentType: "personal", "enterprise", or "autonomous" +- agentDescription: (optional) Short description +- bio: (optional) Public bio +- services: (optional) Service endpoints (A2A, MCP, etc.)`, + schema: ChitinRegisterSoulSchema, + }) + async registerSoul(args: z.infer): Promise { + try { + // Step 1: Get challenge + const challengeRes = (await this.apiFetch(`${this.apiUrl}/register`, { + method: "POST", + body: JSON.stringify({ step: "challenge", agentName: args.name }), + })) as { challengeId: string; question: string; nameAvailable: boolean; expiresAt: string }; + + if (!challengeRes.nameAvailable) { + return JSON.stringify({ + success: false, + error: `Name "${args.name}" is not available.`, + }); + } + + // Solve the challenge + const answer = this.solveChallenge(challengeRes.question); + if (!answer) { + return JSON.stringify({ + success: false, + error: "Failed to solve registration challenge. Unexpected question format.", + }); + } + + // Step 2: Register + const registerBody = { + step: "register", + challengeId: challengeRes.challengeId, + challengeAnswer: answer, + agentName: args.name, + agentType: AGENT_TYPE_MAP[args.agentType], + systemPrompt: args.systemPrompt, + sourceFormat: "plaintext", + ...(args.agentDescription && { agentDescription: args.agentDescription }), + ...(args.bio && { publicIdentity: { bio: args.bio } }), + ...(args.services && { services: args.services }), + }; + + const data = await this.apiFetch(`${this.apiUrl}/register`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}), + }, + body: JSON.stringify(registerBody), + }); + + return JSON.stringify({ success: true, registration: data }); + } catch (error) { + return JSON.stringify({ success: false, error: String(error) }); + } + } + + /** + * Issues a certificate to a recipient via the Chitin Certs platform. + * + * @param args - The arguments for the action. + * @returns The certificate issuance result as stringified JSON. + */ + @CreateAction({ + name: "issue_cert", + description: `Issue an on-chain certificate (achievement, completion, membership, skill, or identity) +to a recipient via the Chitin Certs platform. + +The certificate is minted as an NFT on Base L2, providing a verifiable, +tamper-proof credential. Requires CHITIN_API_KEY for authentication. + +Inputs: +- recipientAddress: Ethereum address of the recipient +- certType: Type of certificate (achievement, completion, membership, skill, identity) +- title: Certificate title +- description: (optional) Certificate description`, + schema: ChitinIssueCertSchema, + }) + async issueCert(args: z.infer): Promise { + if (!this.apiKey) { + return JSON.stringify({ + success: false, + error: "CHITIN_API_KEY is required to issue certificates.", + }); + } + + try { + const data = await this.apiFetch(`${this.certsApiUrl}/certs/issue`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, + }, + body: JSON.stringify({ + recipientAddress: args.recipientAddress, + certType: args.certType, + title: args.title, + ...(args.description && { description: args.description }), + }), + }); + + return JSON.stringify({ success: true, certificate: data }); + } catch (error) { + return JSON.stringify({ success: false, error: String(error) }); + } + } + + /** + * Solves the SHA-256 challenge from the registration flow. + */ + private solveChallenge(question: string): string | null { + const match = question.match(/SHA-256 of the string '([^']+)'/); + if (!match?.[1]) return null; + return createHash("sha256").update(match[1]).digest("hex"); + } + + /** + * Checks if the Chitin action provider supports the given network. + * Chitin operates on Base L2 but read actions work from any network. + * + * @returns True — read actions are network-agnostic, write actions target Base. + */ + supportsNetwork = (_network: Network) => true; +} + +/** + * Factory function for the Chitin action provider. + * + * @param config - Optional configuration. + * @returns A new ChitinActionProvider instance. + * + * @example + * ```typescript + * import { AgentKit } from "@coinbase/agentkit"; + * import { chitinActionProvider } from "@coinbase/agentkit"; + * + * const agentKit = await AgentKit.from({ + * walletProvider, + * actionProviders: [chitinActionProvider()], + * }); + * ``` + */ +export const chitinActionProvider = (config?: ChitinConfig) => new ChitinActionProvider(config); diff --git a/typescript/agentkit/src/action-providers/chitin/index.ts b/typescript/agentkit/src/action-providers/chitin/index.ts new file mode 100644 index 000000000..b8085735f --- /dev/null +++ b/typescript/agentkit/src/action-providers/chitin/index.ts @@ -0,0 +1,2 @@ +export * from "./chitinActionProvider"; +export * from "./schemas"; diff --git a/typescript/agentkit/src/action-providers/chitin/schemas.ts b/typescript/agentkit/src/action-providers/chitin/schemas.ts new file mode 100644 index 000000000..f7559a33b --- /dev/null +++ b/typescript/agentkit/src/action-providers/chitin/schemas.ts @@ -0,0 +1,80 @@ +import { z } from "zod"; + +/** + * Input schema for getting a soul profile. + */ +export const ChitinGetSoulProfileSchema = z + .object({ + name: z.string().describe("The given name of the agent (e.g. 'kani-alpha')"), + }) + .strict(); + +/** + * Input schema for resolving a DID document. + */ +export const ChitinResolveDIDSchema = z + .object({ + name: z.string().describe("The given name of the agent to resolve as a DID"), + }) + .strict(); + +/** + * Input schema for verifying a certificate. + */ +export const ChitinVerifyCertSchema = z + .object({ + certId: z.string().describe("The certificate token ID to verify"), + }) + .strict(); + +/** + * Input schema for checking A2A readiness. + */ +export const ChitinCheckA2aReadySchema = z + .object({ + name: z.string().describe("The given name of the agent to check A2A readiness for"), + }) + .strict(); + +/** + * Input schema for registering a new soul. + */ +export const ChitinRegisterSoulSchema = z + .object({ + name: z + .string() + .describe("Given name for the soul (3-32 chars, lowercase alphanumeric with hyphens)"), + systemPrompt: z.string().describe("System prompt or personality definition"), + agentType: z + .enum(["personal", "enterprise", "autonomous"]) + .describe("Type of agent: personal, enterprise, or autonomous"), + agentDescription: z.string().optional().describe("Short description of the agent"), + bio: z.string().optional().describe("Public bio for the agent's profile"), + services: z + .array( + z.object({ + type: z + .enum(["a2a", "mcp", "webhook", "rest", "graphql", "web"]) + .describe("Service type"), + url: z.string().describe("Service endpoint URL"), + description: z.string().optional().describe("Service description"), + }), + ) + .optional() + .describe("Service endpoints (A2A, MCP, etc.) to register in the ERC-8004 agentURI"), + }) + .strict(); + +/** + * Input schema for issuing a certificate. + */ +export const ChitinIssueCertSchema = z + .object({ + recipientAddress: z.string().describe("Ethereum address of the certificate recipient"), + certType: z + .enum(["achievement", "completion", "membership", "skill", "identity"]) + .describe("Type of certificate"), + title: z.string().describe("Certificate title"), + description: z.string().optional().describe("Certificate description"), + }) + .strict(); diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 7f2b0233a..ca392d1d3 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -9,6 +9,7 @@ export * from "./baseAccount"; export * from "./basename"; export * from "./cdp-legacy"; export * from "./cdp"; +export * from "./chitin"; export * from "./clanker"; export * from "./compound"; export * from "./defillama"; From 5ce0b09d1c54c380e10336dd98f938892dfa0d4b Mon Sep 17 00:00:00 2001 From: EijiAC24 Date: Mon, 9 Feb 2026 20:29:48 +0000 Subject: [PATCH 2/2] update: add open source GitHub repository link --- typescript/agentkit/src/action-providers/chitin/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/typescript/agentkit/src/action-providers/chitin/README.md b/typescript/agentkit/src/action-providers/chitin/README.md index 2c0892296..06901cd1d 100644 --- a/typescript/agentkit/src/action-providers/chitin/README.md +++ b/typescript/agentkit/src/action-providers/chitin/README.md @@ -118,4 +118,5 @@ Each agent's soul includes: - [Chitin Website](https://chitin.id) - [Chitin Certs](https://certs.chitin.id) - [Documentation](https://chitin.id/docs) +- [GitHub (Open Source)](https://github.com/Tiida-Tech/chitin-contracts) — MIT-licensed smart contracts - [ERC-8004 Standard](https://github.com/erc-8004/erc-8004-contracts)