Skip to content

Start typing to search the documentation.

Register an Agent with the SDK

On this page

Register an agent end to end from your own app: render the passport-scan QR with the SDK, the Self mobile app submits the proof on-chain, and the registry mints the agent. Signing and verification are on-chain too.

This page shows the full loop with the SDK: register, sign, verify. It needs no hosted service at all. If you prefer, you can also register through the CLI or the REST/A2A API at https://agent-api.self.xyz, which return the QR for you to display.

What you need

  • npm install @selfxyz/agent-sdk @selfxyz/qrcode ethers
  • The Self mobile app on a phone. On testnet you can generate mock documents in the app, so no real passport is needed.
  • A Celo RPC. Public defaults: https://forno.celo.org (mainnet), https://forno.celo-sepolia.celo-testnet.org (testnet).
NetworkChain IDRegistry
Celo Mainnet422200xaC3DF9ABf80d0F5c020C06B04Cced27763355944
Celo Sepolia (testnet)111422200x043DaCac8b0771DD5b444bCC88f2f8BBDBEdd379

1. Register an agent

Render the QR in your own frontend. The proof goes from the Self app to the on-chain Hub, which mints the agent NFT into the registry. No backend is involved.

import { useEffect, useState } from 'react';
import { SelfAppBuilder, SelfQRcodeWrapper } from '@selfxyz/qrcode';
import { signRegistrationChallenge, buildAdvancedRegisterUserDataAscii } from '@selfxyz/agent-sdk';
import { Wallet } from 'ethers';

const REGISTRY = '0xaC3DF9ABf80d0F5c020C06B04Cced27763355944'; // Celo mainnet

export function RegisterAgent({ humanAddress }: { humanAddress: string }) {
  const [selfApp, setSelfApp] = useState<any>(null);
  const [agentKey] = useState(() => Wallet.createRandom());

  useEffect(() => {
    (async () => {
      // The agent key signs a challenge proving it controls the key
      const sig = await signRegistrationChallenge(agentKey.privateKey, {
        humanIdentifier: humanAddress,
        chainId: 42220,
        registryAddress: REGISTRY,
        nonce: 0,
      });

      // Encode the registration payload (config index is derived from disclosures)
      const disclosures = { minimumAge: 18, ofac: true };
      const userDefinedData = buildAdvancedRegisterUserDataAscii({
        agentAddress: agentKey.address,
        signature: sig,
        disclosures,
      });

      // Build the Self verification request; the registry is the on-chain endpoint
      setSelfApp(
        new SelfAppBuilder({
          version: 2,
          appName: 'My Agent',
          scope: 'self-agent-id',
          endpoint: REGISTRY,
          endpointType: 'celo', // 'staging_celo' for testnet
          userId: humanAddress,
          userIdType: 'hex',
          userDefinedData,
          disclosures,
        }).build(),
      );
    })();
  }, [humanAddress]);

  return selfApp ? (
    <SelfQRcodeWrapper
      selfApp={selfApp}
      onSuccess={() => {
        // Minted. Save agentKey.privateKey securely — it is the agent's key.
      }}
      onError={() => console.error('verification failed')}
    />
  ) : (
    <p>Loading…</p>
  );
}

The user scans the QR with the Self app. On onSuccess the agent NFT is minted. Save the agent private key. This example uses linked mode; for other modes (wallet-free, Ed25519, etc.) see Registration Modes.

Agents minted this way start with a blank agentURI. Publish a JSON document so services and indexers can discover the agent, then point the agent at it.

import { generateRegistrationJSON } from '@selfxyz/agent-sdk';

const doc = generateRegistrationJSON({
  name: 'My Agent',
  description: 'A human-backed AI agent',
  image: 'https://my-agent.example.com/avatar.png',
  services: [{ name: 'A2A', endpoint: 'https://my-agent.example.com/a2a', version: '1.0' }],
});
// Host doc over HTTPS, then call registry.setAgentURI(agentId, url)

3. Sign requests as the agent

import { SelfAgent } from '@selfxyz/agent-sdk';

const agent = new SelfAgent({
  privateKey: process.env.AGENT_PRIVATE_KEY!,
  registryAddress: '0xaC3DF9ABf80d0F5c020C06B04Cced27763355944',
  rpcUrl: 'https://forno.celo.org',
});

const res = await agent.fetch('https://some-service.example.com/api/protected', {
  method: 'POST',
  body: JSON.stringify({ hello: 'world' }),
});

4. Verify the agent in a service

The verifier recovers the signer and checks the registry on-chain. No API, no shared secret.

import { SelfAgentVerifier } from '@selfxyz/agent-sdk';

const verifier = new SelfAgentVerifier({
  registryAddress: '0xaC3DF9ABf80d0F5c020C06B04Cced27763355944',
  rpcUrl: 'https://forno.celo.org',
});

app.use('/api', verifier.auth()); // rejects requests from unverified agents

That is the whole product: register on-chain, sign, verify on-chain. Nothing here depends on a hosted Self website.

Next steps

Was this page helpful?