Step 3: Attest Compliance¶
Goal¶
Submit a compliance attestation to the Agent Registry. The on-chain grace period is 7 days (attestationGracePeriod = 7 days). If your agent does not attest within 7 days of its last attestation, it is no longer considered compliant by KYA checks (isRegisteredAndCompliant returns false).
Recommendation: Attest every 6 days (518,400 seconds) to maintain a 1-day safety buffer.
Method A: Using the SDK (Recommended)¶
from agentenregister import AgentRegistry
registry = AgentRegistry(
chain="base_sepolia",
registry_address="0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23",
private_key="0x_your_agent_private_key",
relayer_url="https://relay.theagentregistry.org",
)
agent_id = 42 # Your agent's ID from registration
registry.attest(agent_id)
# Output: Compliance attested for agent 42
import { AgentRegistry } from "@agentenregister/sdk";
const registry = new AgentRegistry({
chain: "base_sepolia",
registryAddress: "0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23",
privateKey: "0x_your_agent_private_key",
relayerUrl: "https://relay.theagentregistry.org",
});
const agentId = 42; // Your agent's ID from registration
await registry.attest(agentId);
console.log(`Compliance attested for agent ${agentId}`);
Method B: Raw Implementation (No SDK)¶
from web3 import Web3
from eth_account import Account
from eth_account.messages import encode_structured_data
import requests
import json
# ── Configuration ─────────────────────────────────────────────
PRIVATE_KEY = "0x_your_agent_private_key"
RELAYER_URL = "https://relay.theagentregistry.org"
REGISTRY_ADDRESS = "0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23"
CHAIN_ID = 84532
AGENT_ID = 42
account = Account.from_key(PRIVATE_KEY)
# ── Get forwarder address and nonce ───────────────────────────
domain = requests.get(f"{RELAYER_URL}/domain").json()
FORWARDER_ADDRESS = domain["verifyingContract"]
nonce = int(
requests.get(f"{RELAYER_URL}/nonce/{account.address}").json()["nonce"]
)
# ── Encode attestCompliance calldata ──────────────────────────
w3 = Web3()
ABI = json.loads(
'[{"inputs":[{"name":"agentId","type":"uint256"}],'
'"name":"attestCompliance","outputs":[],'
'"stateMutability":"nonpayable","type":"function"}]'
)
contract = w3.eth.contract(address=REGISTRY_ADDRESS, abi=ABI)
calldata = contract.functions.attestCompliance(
AGENT_ID
).build_transaction({"gas": 0, "gasPrice": 0, "nonce": 0})["data"]
# ── Sign EIP-712 typed data ───────────────────────────────────
typed_data = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"ForwardRequest": [
{"name": "from", "type": "address"},
{"name": "to", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "gas", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "deadline", "type": "uint48"},
{"name": "data", "type": "bytes"},
],
},
"primaryType": "ForwardRequest",
"domain": {
"name": "MinimalForwarder",
"version": "1",
"chainId": CHAIN_ID,
"verifyingContract": FORWARDER_ADDRESS,
},
"message": {
"from": account.address,
"to": REGISTRY_ADDRESS,
"value": 0,
"gas": 200000,
"nonce": nonce,
"deadline": 0,
"data": calldata,
},
}
encoded = encode_structured_data(typed_data)
signed = account.sign_message(encoded)
# ── Submit to relayer ─────────────────────────────────────────
result = requests.post(f"{RELAYER_URL}/relay", json={
"request": {
"from": account.address,
"to": REGISTRY_ADDRESS,
"value": "0",
"gas": "200000",
"nonce": str(nonce),
"deadline": "0",
"data": calldata,
},
"signature": signed.signature.hex(),
}).json()
print(f"Attested! TX: {result['transactionHash']}")
import { ethers } from "ethers";
// ── Configuration ─────────────────────────────────────────────
const PRIVATE_KEY = "0x_your_agent_private_key";
const RELAYER_URL = "https://relay.theagentregistry.org";
const REGISTRY_ADDRESS = "0x2EFaB5B3BEf49E56a6Ce1dcB1A39EF63C312EA23";
const CHAIN_ID = 84532;
const AGENT_ID = 42;
const provider = new ethers.JsonRpcProvider("https://sepolia.base.org");
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
// ── Get forwarder address and nonce ───────────────────────────
const domainRes = await fetch(`${RELAYER_URL}/domain`);
const domain = await domainRes.json();
const FORWARDER_ADDRESS = domain.verifyingContract;
const nonceRes = await fetch(`${RELAYER_URL}/nonce/${wallet.address}`);
const nonce = (await nonceRes.json()).nonce;
// ── Encode attestCompliance calldata ──────────────────────────
const ABI = ["function attestCompliance(uint256 agentId)"];
const contract = new ethers.Contract(REGISTRY_ADDRESS, ABI, wallet);
const calldata = contract.interface.encodeFunctionData(
"attestCompliance",
[AGENT_ID]
);
// ── Sign EIP-712 typed data ───────────────────────────────────
const request = {
from: wallet.address,
to: REGISTRY_ADDRESS,
value: "0",
gas: "200000",
nonce: String(nonce),
deadline: "0",
data: calldata,
};
const signature = await wallet.signTypedData(
{
name: "MinimalForwarder",
version: "1",
chainId: BigInt(CHAIN_ID),
verifyingContract: FORWARDER_ADDRESS,
},
{
ForwardRequest: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "gas", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint48" },
{ name: "data", type: "bytes" },
],
},
request,
);
// ── Submit to relayer ─────────────────────────────────────────
const relayRes = await fetch(`${RELAYER_URL}/relay`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request, signature }),
});
const result = await relayRes.json();
console.log(`Attested! TX: ${result.transactionHash}`);
Scheduling¶
Set up a recurring timer to attest every 6 days. Here are common approaches:
Cron Job (Linux/macOS)¶
# Attest every 6 days (at midnight on day 1, 7, 13, 19, 25, 31)
0 0 */6 * * cd /path/to/agent && python -c "
from agentenregister import AgentRegistry
r = AgentRegistry.from_env()
r.attest(42)
"
In-Process Timer (Python)¶
import threading
ATTEST_INTERVAL = 6 * 24 * 3600 # 6 days in seconds
def attest_loop(registry, agent_id):
"""Background thread that attests compliance every 6 days."""
while True:
try:
registry.attest(agent_id)
print(f"Compliance attested for agent {agent_id}")
except Exception as e:
print(f"Attestation failed: {e}. Will retry next cycle.")
threading.Event().wait(ATTEST_INTERVAL)
# Start background attestation
thread = threading.Thread(
target=attest_loop,
args=(registry, agent_id),
daemon=True,
)
thread.start()
In-Process Timer (TypeScript)¶
const ATTEST_INTERVAL_MS = 6 * 24 * 3600 * 1000; // 6 days in ms
setInterval(async () => {
try {
await registry.attest(agentId);
console.log(`Compliance attested for agent ${agentId}`);
} catch (e) {
console.error(`Attestation failed: ${e}. Will retry next cycle.`);
}
}, ATTEST_INTERVAL_MS);
Check Attestation Status¶
Before attesting, you can check whether attestation is actually needed:
Expected Output¶
Common Errors¶
| Error | Cause | Resolution |
|---|---|---|
"Agent not found" |
The agentId does not exist in the registry |
Verify your agent ID from registration |
"Agent not active" |
Agent status is Suspended, Revoked, or Terminated | Check status; suspended agents must be reactivated by a regulator |
"Not authorized for this agent" |
The signing wallet is not the agent wallet, creator, or Haftungsperson | Sign with the correct private key |
"Invalid signature or nonce mismatch" |
Stale nonce or wrong chain ID | Re-fetch nonce from /nonce/:address and re-sign |
For the full error reference, see Errors.
Next Step¶
If your agent earns revenue, proceed to Step 4: Report Revenue. Otherwise, your compliance loop is complete -- just keep attesting every 6 days.