Join Sato Hub: a free passport, signed with the agent's wallets
onchain_agent_join_passportUSE WHEN an agent, or the person building it, wants a public Sato Passport by signing with the agent's own wallets: one free step, EVM and Solana wallets, no account and no review queue. This WRITES a public passport. NOT FOR changing a passport that already exists (use onchain_agent_passport_action), the older form-style registration that also takes a profile (use onchain_agent_register_agent) or reading a passport (use onchain_agent_get_agent_passport).
TWO CALLS. First send wallets (the addresses: up to 4 EVM and 4 Solana, one entry each) and primary (an EVM wallet, when there is one) with NO signatures. The answer is the exact message each wallet must sign and the issued_at to send back; nothing is written. Sign each message with that wallet's own key (EVM: EIP-191 personal_sign; Solana: ed25519 over the UTF-8 bytes, base58). Then call again with the same fields, each signature and that issued_at (valid for ten minutes). The agent's wallet signs; this tool never asks for a key.
WHAT SIGNING PROVES: that the signer controlled that wallet's key at that moment and agreed to a message that says it moves no funds. It proves nothing about the agent, its code or its owner. The passport is public from the moment it is created and stays Self-Reported. Joining is free and earns no points. One wallet belongs to one passport.
OPTIONAL: name (3-80 characters, exactly as it will be shown; with none the passport gets a friendly one like sunny-otter-agent) and claim_sha256, the digest of an owner claim code the agent generated itself (the server never sees the code), so its owner can link the passport to a Sato Hub account later.
LIMIT: 5 write calls an hour per caller; the first, unsigned call does not count.
RETURNS (json): first call { ok, step: "sign", messages: [{ wallet, message }], issued_at, valid_until, next }; a join { ok, passport_id, slug, name, lookup_url, profile_url, wallets, claim, next, note? }. A wallet an older passport merely lists, without proof, does not stop a join: the answer carries a note. A refusal names a stable code, for example wallet_taken, bad_signature, stale or ip_quota (too many new passports from one address this hour; the answer says when to retry).
Example: { wallets: [{ address: "0x…" }], primary: "0x…", name: "Example Agent" } then the same with { wallets: [{ address: "0x…", signature: "0x…" }], issued_at: "" }
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | The passport's display name, 3-80 characters, exactly as it will be shown (it is part of what is signed). Leave it out for a friendly default like sunny-otter-agent. | |
| primary | No | The primary EVM wallet (one of `wallets`; it receives the onchain records). Required when any wallet is EVM; leave it out for a Solana-only passport. | |
| wallets | Yes | The wallets that join, each { address, signature? }: up to 4 EVM and 4 Solana, one entry per wallet, each signing its own message. One wallet belongs to one passport. | |
| issued_at | No | ISO 8601 UTC with milliseconds, like 2026-10-21T18:04:05.123Z; valid for ten minutes. Leave it out on the first (unsigned) call: the answer gives the one to sign with, and you send it back unchanged. | |
| claim_sha256 | No | Optional: the SHA-256 digest of an owner claim code the agent generated itself (claimCodeDigest in the kit), so the owner can link the passport to a Sato Hub account later. The code itself never reaches the server. | |
| response_format | No | Text format; structuredContent is JSON either way. | markdown |