From x402fhe
ERC-8004 on-chain agent identity (NFT registry + EIP-712 wallet linking) and reputation feedback (proof-linked rating) for x402fhe. Covers `register-agent` (mints an identity NFT, sets the agent URI, and self-signs an EIP-712 wallet link) and `give-feedback` (submits a rating against an agentId using a proof derived from a prior payment nonce). Use when the user asks to register an agent, mint an agent identity, set an agent URI, link an agent wallet, rate an agent, leave feedback for an agent, score an agent, submit a review, give a star rating, or attest to an agent's quality. Do NOT use for sending tokens, wrapping plain USDC, balance lookups, redeeming cUSDC, escrow job lifecycle, view delegation setup, or HTTP-402 demo orchestration — those are handled by sibling skills (fhe-payment-basics, fhe-payment-unwrap, fhe-escrow, fhe-delegation, x402-demo-orchestrators).
How this skill is triggered — by the user, by Claude, or both
Slash command
/x402fhe:fhe-agent-identityThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Two OpenClaw commands for the ERC-8004 agent stack on x402fhe: `register-agent` and `give-feedback`. Identity is an ERC-721 NFT in `AgentIdentityRegistry` with EIP-712 wallet linking; reputation is feedback in `Reputation` that the contract anchors to a proof derived from a prior x402 payment nonce. Both scripts live in `packages/openclaw-skill/scripts/` and are invoked via pnpm + tsx from anyw...
Two OpenClaw commands for the ERC-8004 agent stack on x402fhe: register-agent and give-feedback. Identity is an ERC-721 NFT in AgentIdentityRegistry with EIP-712 wallet linking; reputation is feedback in Reputation that the contract anchors to a proof derived from a prior x402 payment nonce. Both scripts live in packages/openclaw-skill/scripts/ and are invoked via pnpm + tsx from anywhere in the monorepo. Each emits a single JSON line to stdout and never throws to the shell — parse it and branch on ok, not on bash exit code.
give-feedback.ts derives proofOfPayment from the prior payment's nonce plus the verifier address. The nonce is the ONE field you must capture from the prior payment, and there is no in-script recovery path.
The exact derivation, from scripts/give-feedback.ts lines 33-39:
const proofOfPayment = ethers.keccak256(
ethers.solidityPacked(
['bytes32', 'address'],
[args.nonce, await verifier.getAddress()],
),
);
That hash is then passed via ethers.getBytes(proofOfPayment) as the LAST argument to reputation.giveFeedback(...). The reputation contract uses it to verify the feedback is anchored to a real on-chain payment in X402PaymentVerifier.
Where the nonce comes from: it is generated client-side inside pay.ts line 21 as ethers.hexlify(ethers.randomBytes(32)) — fresh-random per invocation. It is the only first-class output the sibling fhe-payment-basics skill instructs you to surface verbatim. If you ran the prior payment in this conversation, the nonce should already be in your transcript inside a code span. If it is not, see the next CRITICAL block.
Rules:
nonce: 0x<64 hex> and copy that string into --nonce verbatim. Do NOT abbreviate, paraphrase, or reformat — the script regex-validates the bytes32 format inside ethers.solidityPacked.give-feedback with anything other than the original 32-byte payment nonce.give-feedback.ts does not echo the nonce back in its return shape (only agentId, score, txHash). The nonce is consumed input, not output. If you want it preserved for the user, surface it yourself in your reply.Rationalizations that do NOT override these rules:
| Rationalization | Why it is wrong |
|---|---|
| "The user did not mention a nonce, so it must not be required" | The script hard-fails with --nonce is required (bytes32 from prior payment). The user may not know the nonce exists; the discipline is on you to ask. |
| "I have the txHash from the prior payment, that should be enough" | The proofOfPayment formula is keccak256(nonce ‖ verifierAddress). There is no keccak256(txHash ‖ ...) fallback. The txHash cannot be substituted. |
"I will re-run pay to get a fresh nonce, then submit feedback" | A new pay is a NEW on-chain transfer with a NEW nonce — the user gets double-charged, the new nonce does NOT prove the original payment, and give-feedback against the new nonce attests to the wrong amount and timestamp. |
"I will use a placeholder like 0x000… or 0x + 64 random hex" | The reputation contract verifies the proof against verifier.usedNonces[nonce] (or equivalent). A placeholder reverts; a fabricated random nonce attributes false provenance. Both are integrity violations, not just bugs. |
"The user said Nonce: 0xABC123… — I will pass that abbreviated form" | A real nonce is exactly 32 bytes (0x + 64 hex chars). 0xABC123… with an ellipsis is a paraphrase, not the value. Ask the user for the full hex. |
"give-feedback.ts will tell me which nonce to use if I just try it" | It will not. It only validates args.nonce is set; it does not tell you what value should be there. |
Red-flag self-check: if your drafted invocation contains give-feedback ... --nonce and the value is anything other than a literal 0x + exactly 64 hex chars copied verbatim from a prior pay result the user has shown you, delete the draft and ask the user where the nonce is.
A common assumption is that nonces are completely unrecoverable from chain state. That is half-true. Storage cannot help you, but event logs can.
What you cannot do:
verifier.usedNonces(nonce) and verifier.paymentAmounts(nonce) are Solidity mappings keyed BY the nonce. To read a value you must already know the key. Mappings cannot be iterated from the outside, so there is no "list all nonces for this wallet" via a storage read.What you CAN do (engineer-level, no OpenClaw script for this yet):
X402PaymentVerifier emits event PaymentVerified(address indexed payer, address indexed server, bytes32 indexed nonce, uint64 minPrice). Because nonce is an indexed topic, you can recover historical nonces by:
provider.getTransactionReceipt(originalTxHash) and parsing the receipt's logs for the verifier's PaymentVerified topic — the indexed nonce is in topics[3].provider.getLogs({ address: verifierAddress, topics: [PaymentVerifiedTopic, payerAddress, serverAddress, null], fromBlock, toBlock }).What this means for an agent in a normal conversation:
.env file, the JSON output of an earlier pay, a screenshot. The nonce was returned to them at least once and is almost certainly somewhere — you just need them to find it.pay transaction hash, do the receipt parse. This is engineer-level work — there is no find-nonce or list-payments OpenClaw command. You would need to issue eth_getTransactionReceipt via the RPC, decode the verifier ABI, and pull topics[3] from the matching log. Tell the user this is possible and offer to walk through it; do not silently do it inside an agent loop.getLogs block-range scan. Same caveat — engineer-level, requires care about block-range size on rate-limited RPCs.What is FORBIDDEN:
pay to "regenerate" a nonce. A fresh pay is a new on-chain transfer that double-spends and produces a new nonce — that new nonce does NOT correspond to the original payment. The reputation contract will (correctly) treat it as a feedback for a different payment.0x + 64 zeros, or any other made-up hex, will revert proof verification on-chain or, worse, attribute false provenance.The SAFE answer when the nonce is missing is always: (a) ask the user, falling back to (d) recover from event logs if the user provides the original tx hash. Never (b) re-run pay. Never (c) fabricate.
proofOfPayment = keccak256(nonce ‖ verifierAddress) is a function of the nonce and the verifier address only. It does NOT include the agentId, the server address, or any binding to the recipient of the original payment. That cross-check is YOUR responsibility before submitting feedback.
This is a subtle design property of give-feedback.ts: the same nonce could in principle prove payment to one server and then be used to leave feedback for an unrelated agentId. The reputation contract presumably enforces nonce-to-payee consistency on-chain when it verifies the proof (otherwise the proof would be meaningless), but give-feedback.ts itself does NOT pre-validate any consistency between the nonce's actual payee and the --agentId flag you pass.
What this means in practice: before running give-feedback --agentId X --nonce 0x..., you must independently confirm:
--to of the original pay) corresponds to the agent identified by --agentId. Either the user explicitly tells you "agent 42 is at address 0xAlice...", or you check the on-chain identity registry, or you inspect the PaymentVerified event's server topic against the agent's known wallet from setAgentWallet.If you cannot make this match with confidence, ASK. Submitting feedback against the wrong agentId attributes someone else's reputation to a payment they did not receive — even if the proof verifies on-chain, the resulting record is a correctness violation.
A useful sanity check: when the user says "rate agent 42 for the payment I made earlier", paraphrase back what you intend to do — "I will rate agentId 42 with score X using nonce 0x…, which was your payment to address 0xAlice…. Confirm 0xAlice is agent 42?" — and wait for confirmation before invoking.
These rules apply to every command in this skill:
{"ok": true, "action": "<name>", ...} or {"ok": false, "error": "..."}. The canonical success check is parsed.ok === true. The action field tells you which command ran (register or feedback); it is informational, not a second success signal. Bash exit code is unreliable — a script that returns {"ok": false, ...} still exits 0.register-agent runs TWO transactions (mint + setAgentWallet); a blind retry mints a SECOND identity NFT. give-feedback retried after a partial success can attribute multiple feedback records for one payment if the on-chain check is per-call rather than per-nonce. Surface failures and wait for instructions.register-agent may successfully mint the NFT in TX 1 and then fail on setAgentWallet in TX 2 — the agent now exists but has no linked wallet. Surface the agentId from TX 1 if you can extract it from the error, and tell the user setAgentWallet can be retried directly with the right signer.InvalidSignature, DeadlineExpired, Unauthorized, AlreadyLinked, NotMinted) are the primary diagnostic signal — the exact text matters.give-feedback.ts hardcodes valueDecimals = 0 when calling reputation.giveFeedback(...) (line 52 of the script). That means the --score flag is interpreted as a literal int128 integer with NO fractional precision.
--score 80 → int128(80) on-chain. NOT 8.0. NOT 80.0 with implied decimals.--score -10 → int128(-10) (the int128 type is signed, negative scores are valid).--score 100 → int128(100).The skill.json description says "e.g. '80' for 80.0" which is misleading and should be ignored — valueDecimals = 0 makes the score an integer, period. Typical reputation scores live in the -100 to 100 range, but the script accepts the full int128 range (-2^127 to 2^127 - 1) and only rejects values outside it.
When the user asks for a "5 out of 5" or "80%" rating, you must decide on the integer convention before invoking — usually a 0-100 scale. Confirm with the user once if it is ambiguous, then surface the integer you used in your reply: "I rated agent 42 with score 80 (on a 0-100 scale)."
register-agent.ts runs TWO sequential transactions internally and uses wallet.signTypedData(domain, types, value) (the ethers v6 API — NOT _signTypedData).
Transaction 1: identity.register(uri)
getFunction('register(string)') — there are multiple register overloads in AgentIdentityRegistry (bare, with URI, with URI + metadata) and ethers v6 cannot pick one without explicit help.500_000n.agentId is extracted from the Registered(uint256 indexed agentId, string agentURI, address indexed owner) event in the receipt.Transaction 2: identity.setAgentWallet(agentId, signerAddress, deadline, signature)
{
name: 'AgentIdentityRegistry',
version: '1',
chainId, // from provider.getNetwork()
verifyingContract: <identity contract address>,
}
{
SetAgentWallet: [
{ name: 'agentId', type: 'uint256' },
{ name: 'newWallet', type: 'address' },
{ name: 'deadline', type: 'uint256' },
],
}
Math.floor(Date.now() / 1000) + 3600 (1 hour from now). NOT exposed as a CLI flag — there is no --deadline argument. If the second TX takes more than ~1 hour to land (e.g. the network is congested and you set a low gas price), the signature expires and you must re-invoke register-agent for that agent.300_000n.Self-signed limitation: the wallet that mints the agent NFT immediately self-signs the link to itself. The script does not support a separated signer (e.g. you mint with wallet A and want to link wallet B). For that case you would have to call identity.setAgentWallet directly with the correct signing wallet, outside this skill.
You need one wallet mode plus an RPC URL set in env. See ../../references/wallet-setup.md for the three modes (user, dfns, ledger-bridge), ../../references/env-vars.md for the full variable list, and ../../references/output-schema.md for the canonical ok/fail wrapper shape. Minimum working dev setup:
export WALLET_MODE=user
export USER_PRIVATE_KEY=0x... # 64 hex chars
export RPC_URL=https://sepolia.infura.io/v3/YOUR_KEY
When RPC_URL points to Sepolia (chain ID 11155111), SEPOLIA_ADDRESSES are auto-selected — do not override IDENTITY_ADDRESS, REPUTATION_ADDRESS, VERIFIER_ADDRESS, etc. unless you are targeting a non-default deployment. If any of those env vars is missing AND chain auto-detect fails, _wallet.ts falls back to the zero address — run info from fhe-payment-basics first to confirm all five contracts are non-zero.
Both commands run from anywhere in the monorepo via the workspace filter:
pnpm --filter @x402fhe/openclaw-skill exec tsx scripts/<name>.ts [--flag value]
Do NOT use node scripts/..., npx tsx, or ts-node — the _wallet.ts singleton resolves imports through the pnpm workspace package graph and fails otherwise.
When to use: the user asks to register an agent, mint an identity NFT, set an agent URI, claim an agent ID, or link their current wallet to a new on-chain agent identity. The wallet running the command becomes the owner of the resulting NFT and the linked agent wallet.
Invocation:
pnpm --filter @x402fhe/openclaw-skill exec tsx scripts/register-agent.ts --uri https://example.com/agent.json
--uri is required and must be a non-empty string. It is the off-chain agent URI (typically pointing to a JSON document describing the agent — capabilities, public key, contact, etc.).
Two on-chain transactions, in order:
identity.register(uri) (gas 500k) — mints the ERC-721 NFT and returns the new agentId.identity.setAgentWallet(agentId, signerAddress, deadline, signature) (gas 300k) — links the signer wallet to the agentId via an EIP-712 signature with a 1-hour deadline.The script awaits the receipt of TX 1, parses the Registered event for agentId, signs the EIP-712 payload, then submits TX 2 and awaits its receipt before returning. The returned txHash is the FIRST transaction's hash (the register mint), not the setAgentWallet link tx.
Required env: RPC_URL, one wallet mode, enough Sepolia ETH for two transactions (~800k gas total).
Optional flags: none. The deadline is hardcoded to now + 3600s and is NOT a CLI flag. The signing wallet is the same as the registering wallet (no separate-signer support in this script).
Returns:
{
"ok": true,
"action": "register",
"agentId": "42",
"txHash": "0x..."
}
agentId is a stringified bigint (the script calls .toString()). Surface this to the user verbatim — it is the on-chain handle for every subsequent operation against this agent (including future give-feedback calls and external lookups in AgentIdentityRegistry).
Common errors:
--uri is required and must be a non-empty string — forgot the flag, or passed an empty string.execution reverted on TX 1 (register) — usually a misconfigured IDENTITY_ADDRESS (zero-address fallback) or the contract has a per-wallet registration cap. Run info first.InvalidSignature / execution reverted on TX 2 (setAgentWallet) — the EIP-712 domain or types do not match the deployed contract version. Possible causes: stale ABI, wrong chainId, wrong verifyingContract. Re-run after confirming info shows the correct identity contract.DeadlineExpired on TX 2 — extremely rare, but possible if the wallet sat on TX 2 for more than an hour after signing. Re-run register-agent for a fresh deadline (note: this mints a SECOND NFT, so coordinate with the user before doing this).insufficient funds for gas — need more Sepolia ETH.When to use: the user has previously made a payment to an agent and now wants to leave a rating, score, review, or feedback against that agent's on-chain identity. You MUST already have the payment nonce in the conversation (see the CRITICAL blocks at the top).
Invocation (minimal):
pnpm --filter @x402fhe/openclaw-skill exec tsx scripts/give-feedback.ts --agentId 42 --score 80 --nonce 0x<64 hex chars>
Invocation (with optional metadata):
pnpm --filter @x402fhe/openclaw-skill exec tsx scripts/give-feedback.ts \
--agentId 42 \
--score 80 \
--nonce 0x<64 hex chars> \
--tag1 helpful \
--tag2 fast \
--endpoint https://example.com/agent \
--feedbackURI https://example.com/feedback/123 \
--feedbackHash 0x<32-byte hex>
Required flags: --agentId (uint256, the on-chain identifier), --score (int128 integer — see "Score units" above), --nonce (bytes32, the original payment nonce as 0x + 64 hex chars).
Optional flags:
--tag1 (string, default '') — first taxonomic tag for filtering.--tag2 (string, default '') — second taxonomic tag.--endpoint (string, default '') — agent endpoint URL the feedback refers to.--feedbackURI (string, default '') — off-chain link to extended feedback (e.g. a JSON or markdown document).--feedbackHash (bytes32 hex, default ethers.ZeroHash = 32 zero bytes) — content hash of the off-chain feedback for tamper-evidence.Required env: RPC_URL, one wallet mode, enough Sepolia ETH for one transaction (~300k gas).
Returns:
{
"ok": true,
"action": "feedback",
"agentId": "42",
"score": "80",
"txHash": "0x..."
}
Note: the response does NOT echo back the nonce or the derived proofOfPayment. If the user wants to keep the nonce visible after the call, surface it yourself in your reply.
Common errors:
--agentId is required — forgot the flag.--score is required (integer, e.g. 80 for 80.0) — forgot the flag. Note the script's own help text says "80 for 80.0" but valueDecimals = 0 means the literal int128 is 80, not 8.0. See "Score units" above.--nonce is required (bytes32 from prior payment) — forgot the flag. STOP and reread the CRITICAL blocks above. Do NOT fabricate.--score must be an integer (e.g. 80, -10) — passed a non-integer like 4.5 or "five".--score must be within int128 range — a number outside ±2^127.execution reverted from reputation.giveFeedback — most commonly the proof verification failed: the nonce was never used in X402PaymentVerifier, the nonce was used by a different wallet, or the on-chain reputation contract enforces nonce-to-agent consistency and the agentId does not match the original payment recipient. Do NOT retry; re-examine the nonce and the agentId.insufficient funds for gas — need more Sepolia ETH.User: "Register me as an agent with URI https://acme.example/agent.json."
Run:
pnpm --filter @x402fhe/openclaw-skill exec tsx scripts/register-agent.ts --uri https://acme.example/agent.json
Parse the JSON. If ok: true and action === "register", reply:
Registered. Your on-chain agent ID is
42and the mint landed in tx0x<txHash>. The wallet linking transaction (EIP-712setAgentWallet) ran in the same invocation and is also confirmed.Save this agent ID — it is the on-chain handle you will need for any future feedback, lookups, or wallet relinking. It cannot be inferred from your wallet address alone.
The agentId is surfaced verbatim, in a code span, with a one-liner explaining what it is for. This mirrors how pay surfaces the nonce in the sibling skill.
Earlier in the conversation, you ran pay --to 0xAlice... --amount 0.5 and surfaced:
nonce: 0xabc1230000000000000000000000000000000000000000000000000000000000
The user now says: "Rate agent 42 80 out of 100 for that payment."
First, paraphrase to confirm the (nonce, agentId) match: "I will rate agentId 42 with score 80 using nonce 0xabc1230…, which was your 0.5 cUSDC payment to 0xAlice…. Confirm 0xAlice… is agent 42?"
If the user confirms, run:
pnpm --filter @x402fhe/openclaw-skill exec tsx scripts/give-feedback.ts --agentId 42 --score 80 --nonce 0xabc1230000000000000000000000000000000000000000000000000000000000
Parse. If ok: true and action === "feedback", reply with the txHash and the score you used (80, on a 0-100 scale). Do NOT mention the nonce again unless the user asks — it has been consumed.
User: "I paid agent 42 earlier today. Rate them 80 as feedback."
Scan the conversation transcript for a nonce: 0x code span. If you do not find one (the prior payment happened in a different session, or the line scrolled off), STOP. Do NOT invoke give-feedback. Reply:
I do not have the payment nonce in our conversation, and
give-feedbackcannot be run without it — the proof-of-payment formula iskeccak256(nonce ‖ verifierAddress)and there is no fallback. Please check:
- Your terminal scrollback for a line like
"nonce": "0x…"from the originalpaycommand.- Any notebook, log file, or receipt JSON you saved.
- Your shell history for the
payinvocation.If you have the original payment transaction hash but not the nonce, I can walk you through pulling the nonce out of the
PaymentVerifiedevent log onX402PaymentVerifier(it is the third indexed topic) — that is engineer-level RPC work, not an OpenClaw command. Let me know which path you want.
Wait. Do not invoke give-feedback, do not re-run pay, do not fabricate.
| Error text (regex / substring) | Cause | Remediation |
|---|---|---|
--uri is required and must be a non-empty string | Missing or empty --uri on register-agent | Pass --uri https://... with a real string |
--agentId is required | Missing --agentId on give-feedback | Pass --agentId <number> |
--score is required | Missing --score on give-feedback | Pass --score <integer> (see Score units above — no decimals) |
--nonce is required (bytes32 from prior payment) | Missing --nonce on give-feedback | STOP. Find the original payment nonce per the CRITICAL blocks. Do NOT fabricate or re-run pay. |
--score must be an integer (e.g. 80, -10) | Non-integer score (4.5, "five") | Use a literal int128 integer, no decimals |
--score must be within int128 range | Score outside ±2^127 | Use a typical reputation range (e.g. -100 to 100) |
InvalidSignature on register-agent TX 2 | EIP-712 domain or types do not match deployed contract | Confirm info shows the correct identity contract; check for stale ABI |
DeadlineExpired on register-agent TX 2 | The 1-hour signature deadline elapsed before TX 2 landed | Re-invoke register-agent (note: mints a second NFT — coordinate with user) |
execution reverted on register-agent TX 1 | Identity contract misconfigured (zero-address fallback) or per-wallet cap | Run info from fhe-payment-basics; verify IDENTITY_ADDRESS is non-zero |
execution reverted on give-feedback from reputation.giveFeedback | Proof-of-payment verification failed: bad nonce, wrong wallet, or agentId-payee mismatch | Do NOT retry blindly. Re-confirm the nonce came from a payment THIS wallet made TO the wallet linked to this agentId. Escalate to the user. |
| Reputation revert mentioning "proof", "nonce", or "Unauthorized" | The on-chain proof check rejected the (nonce, verifier, agentId) combination | Same — verify the nonce is from a real payment to the right recipient, do not fabricate |
Missing USER_PRIVATE_KEY / Missing DFNS_* | Wallet env vars not set | See ../../references/wallet-setup.md |
could not detect network | Bad RPC_URL, rate-limited, or endpoint down | Check the URL and quota |
insufficient funds for gas | Wallet has no Sepolia ETH | Fund the wallet (use info from fhe-payment-basics for the address) |
| Bash exit ≠ 0 with stack trace, NOT a JSON line | Script crashed before returning JSON — usually _wallet.ts init error | Check env vars, verify RPC_URL is reachable, confirm pnpm install was run from repo root |
This skill covers ONLY register-agent and give-feedback. Do not use it for:
info / balance / wrap / pay (direct token operations, including capturing the original payment nonce) — see fhe-payment-basicsunwrap / finalize-unwrap (redeeming cUSDC back to USDC) — see fhe-payment-unwrapcreate-job / fund-job / complete-job (encrypted escrow lifecycle) — see fhe-escrowgrant-view / revoke-view / view-as (delegated balance reads) — see fhe-delegationresearch-and-visualize / review-and-rate / HTTP-402 paid-request orchestration — see x402-demo-orchestratorsnpx claudepluginhub zama-ai/confidential-agentic-payment-stack --plugin x402fheProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.