# Adapter8004 Agent Skill

Register and manage onchain agents through the **Adapter8004** contract — the
ERC-8004 adapter that binds an existing NFT to an onchain agent identity.

## Supported chains and adapter addresses

| Chain    | Chain ID  | Adapter8004 (proxy)                          |
| -------- | --------- | -------------------------------------------- |
| Ethereum | 1         | `0xde152AfB7db5373F34876E1499fbD893A82dD336` |
| Base     | 8453      | `0x270d25D2c59A8bcA1B0f40ad95fF7806c0025c27` |
| Sepolia  | 11155111  | `0x7621630cB63a73a194f45A3E6801B8C6A7eC2f92` |

All three are UUPS proxies — always interact with the proxy address above.

## Token standards

`standard` is a `uint8` enum — pass the number:

| Value | Standard |
| ----- | -------- |
| 0     | ERC-721  |
| 1     | ERC-1155 |
| 2     | ERC-6909 |

## Real vs counterfactual — decision tree

Adapter8004 supports two registration paths for the same NFT-bound agent model:
real and counterfactual.

```
Do you control the NFT right now (own it / are an approved controller)?
├─ YES  → register a REAL agent.
│         register(...) mints an agentId, emits AgentBound.
│         Canonical onchain identity; addressed by agentId.
└─ NO   → register a COUNTERFACTUAL agent.
          counterfactualRegister(...) records an intended binding,
          keyed by registrationHash, without owning or transferring
          the token. Use for pre-claim setup, templates, and launchpad
          flows before the holder claims.
```

In the Dappa API these paths produce **one unified record per NFT** with a
`counterfactual` boolean. A real ERC-8004 registration overrides the
counterfactual data, flipping `counterfactual` to false. Until then, the
counterfactual record is fully queryable, so an upgrade flow can read the staged
URI, metadata, and wallet and carry them on-chain during real registration.

## Real-agent write functions

Real agents are addressed by the `uint256 agentId` returned by `register`.

- `register(uint8 standard, address tokenContract, uint256 tokenId, string agentURI) -> uint256`
- `register(uint8 standard, address tokenContract, uint256 tokenId, string agentURI, (string metadataKey, bytes metadataValue)[] metadata) -> uint256`
- `setAgentURI(uint256 agentId, string newURI)`
- `setMetadata(uint256 agentId, string metadataKey, bytes metadataValue)`
- `setMetadataBatch(uint256 agentId, (string metadataKey, bytes metadataValue)[] metadata)`
- `setAgentWallet(uint256 agentId, address newWallet, uint256 deadline, bytes signature)`
- `unsetAgentWallet(uint256 agentId)`
- `rewriteBindingMetadata(uint256 agentId)`

`setAgentWallet` requires an authorization `signature` produced by `newWallet`
itself, valid until `deadline` — the wallet must consent to being bound.
Consult the Adapter8004 contract docs for the exact message format. Counter­-
factual records use `counterfactualSetAgentWallet`, which takes no signature.

## Counterfactual write functions

Counterfactual records are addressed by `(standard, tokenContract, tokenId)`;
`counterfactualRegister` returns the `bytes32 registrationHash`.

- `counterfactualRegister(uint8 standard, address tokenContract, uint256 tokenId, string agentURI) -> bytes32`
- `counterfactualRegister(uint8 standard, address tokenContract, uint256 tokenId, string agentURI, (string,bytes)[] metadata) -> bytes32`
- `counterfactualSetAgentURI(uint8 standard, address tokenContract, uint256 tokenId, string newURI)`
- `counterfactualSetMetadata(uint8 standard, address tokenContract, uint256 tokenId, string metadataKey, bytes metadataValue)`
- `counterfactualSetMetadataBatch(uint8 standard, address tokenContract, uint256 tokenId, (string,bytes)[] metadata)`
- `counterfactualSetAgentWallet(uint8 standard, address tokenContract, uint256 tokenId, address newWallet)`
- `counterfactualUnsetAgentWallet(uint8 standard, address tokenContract, uint256 tokenId)`

## ⚠️ Reserved metadata key — `agent-binding`

The adapter stores the binding record under a **reserved** metadata key. Read it
with the view function `BINDING_METADATA_KEY()` (its value is `agent-binding`).

Never pass this key to `setMetadata`, `setMetadataBatch`, or any counterfactual
metadata write — the contract reverts with `ReservedMetadataKey`. The adapter
maintains binding metadata itself; `rewriteBindingMetadata(agentId)` refreshes
it. Pick any other key for your own metadata.

Metadata values are `bytes`. Encode strings as UTF-8 hex (e.g. viem
`stringToHex('...')`); the dappa API returns both `valueHex` and a decoded
`valueText` when the bytes are valid text.

## viem examples

```ts
import { createWalletClient, custom, stringToHex } from 'viem';
import { base } from 'viem/chains';

const ADAPTER = '0x270d25D2c59A8bcA1B0f40ad95fF7806c0025c27'; // Base
const abi = [
  {
    type: 'function', name: 'register', stateMutability: 'nonpayable',
    inputs: [
      { name: 'standard', type: 'uint8' },
      { name: 'tokenContract', type: 'address' },
      { name: 'tokenId', type: 'uint256' },
      { name: 'agentURI', type: 'string' },
    ],
    outputs: [{ type: 'uint256' }],
  },
  {
    type: 'function', name: 'setMetadata', stateMutability: 'nonpayable',
    inputs: [
      { name: 'agentId', type: 'uint256' },
      { name: 'metadataKey', type: 'string' },
      { name: 'metadataValue', type: 'bytes' },
    ],
    outputs: [],
  },
] as const;

const wallet = createWalletClient({ chain: base, transport: custom(window.ethereum) });
const [account] = await wallet.getAddresses();

// Register a real agent for an ERC-721 you control.
const txHash = await wallet.writeContract({
  account, address: ADAPTER, abi, functionName: 'register',
  args: [0, '0xYourNftContract', 7n, 'https://example.com/agent.json'],
});

// Set a (non-reserved) metadata key once you know the agentId.
await wallet.writeContract({
  account, address: ADAPTER, abi, functionName: 'setMetadata',
  args: [32346n, 'homepage', stringToHex('https://my-agent.example')],
});
```

Counterfactual registration is the same shape — call `counterfactualRegister`
with `(standard, tokenContract, tokenId, agentURI)`; it returns the
`registrationHash` you use to look the record up later.

## Confirming a transaction on the dappa indexer

After the transaction confirms, poll the dappa public API (no auth required):

1. `GET /api/events?chainId=<id>&txHash=<txHash>` — wait for the event to appear.
2. Then fetch the unified NFT route:
   - `GET /api/agents/by-token/<chainId>/<tokenContract>/<tokenId>`

Legacy detail routes by `agentId` or `registrationHash` may still exist for
low-level debugging, but product flows should use the token route so they see
the unified model.

The indexer exposes the registry-backed `agentUri` (the URI you registered) and
separately the NFT's raw `tokenUri` plus resolved `tokenName` / `tokenImage`.
A short interval (≈3s) for the first minute, then back off, is appropriate.

## Common reverts

| Error                 | Meaning                                                        |
| --------------------- | -------------------------------------------------------------- |
| `NotController`       | The sender does not control the NFT for a real-agent write.    |
| `UnknownAgent`        | The `agentId` has no binding.                                  |
| `ReservedMetadataKey` | You tried to write the reserved `agent-binding` key.           |
| `InvalidTokenContract`| The token contract is not a valid ERC-721/1155/6909.           |
