{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"VerifyResult","type":"object","description":"Signed B20 legitimacy attestation for a single address on Base.","x-attestationVerification":{"note":"Recover the signer AND recompute attestationId from the payload. If you only recover the signer over attestationId, a tampered body with the original signature would still pass.","steps":["1. payload = this response object with the `attestation` field removed.","2. canonicalPayload = the payload serialized per `canonicalization` below (sorted keys, UTF-8, NO unicode escaping). This is language-agnostic; test your implementation against `testVector` first.","3. payloadHash = keccak256(utf8Bytes(canonicalPayload)).","4. message = { token: address, isB20, verdict: verdictCodes[verdict], payloadHash, pinnedBlock: attestation.pinnedBlock, issuedAt: attestation.issuedAt, expiresAt: attestation.expiresAt }.","5. attestationId = EIP-712 hashTypedData(domain, types, primaryType, message). It MUST equal attestation.attestationId.","6. Recover the signer from (attestationId, attestation.signature). It MUST equal attestation.signer and the published signer 0x1388d1e3751512955A53C7c2795Eaf8a3B029e6A.","7. Freshness: check now (unix seconds) < attestation.expiresAt. Steps 1-6 prove authenticity, not currency: a genuine but EXPIRED attestation still passes them, and the issuer's powers may have changed since (TTL is ~10 min). If expired, refetch.","If step 5, 6, or 7 fails, do not act on the verdict."],"canonicalization":{"spec":"JSON with (a) every object's keys sorted ascending by Unicode code point, recursively; arrays keep their order; (b) no insignificant whitespace (item separator ',', key/value separator ':'); (c) UTF-8 output with NO \\u escaping of non-ASCII characters (only the JSON-mandatory escapes for '\"', '\\\\', and U+0000-U+001F control chars). This matters: token names on impostors often contain non-ASCII homoglyphs, and a verifier that \\u-escapes them (e.g. Python json.dumps default) computes a different hash and falsely reports 'tampered'.","python":"json.dumps(payload, sort_keys=True, ensure_ascii=False, separators=(',', ':')).encode('utf-8')","javascript":"JSON.stringify(payload) after recursively sorting object keys (JSON.stringify already emits raw UTF-8 with minimal escaping).","go":"encoding/json with sorted keys (Go emits raw UTF-8 by default; set SetEscapeHTML(false))."},"testVector":{"note":"Run your canonicalization on `input`; the UTF-8 bytes must equal `canonical` and keccak256 must equal `payloadHash`. If it does not, your JSON encoder is escaping non-ASCII (see canonicalization.spec).","input":{"symbol":"USDТ","name":"Тether Gоld","z":1,"a":[3,2]},"canonical":"{\"a\":[3,2],\"name\":\"Тether Gоld\",\"symbol\":\"USDТ\",\"z\":1}","payloadHash":"0x01f7ebfa8cf7cc1f16d5b9ddf5c1eb4a06c99cf95a61ca31bdd8e8f3e196b064"},"eip712":{"primaryType":"B20Attestation","domain":{"name":"B20 Check Attestation","version":"1","chainId":"the chain in the response: 8453 (mainnet) or 84532 (sepolia)","verifyingContract":"the token address being attested (used for per-token replay separation; NOT a service contract)"},"types":{"B20Attestation":[{"name":"token","type":"address"},{"name":"isB20","type":"bool"},{"name":"verdict","type":"uint8"},{"name":"payloadHash","type":"bytes32"},{"name":"pinnedBlock","type":"uint64"},{"name":"issuedAt","type":"uint64"},{"name":"expiresAt","type":"uint64"}]}},"verdictCodes":{"ok":0,"caution":1,"danger":2,"not_applicable":3}},"required":["address","network","isB20","verdict","flags","attestation"],"properties":{"address":{"type":"string","description":"Checksummed EVM address inspected."},"network":{"type":"string","enum":["mainnet","sepolia"]},"isB20":{"type":"boolean","description":"True only for a factory-native B20 token."},"variant":{"type":["string","null"],"enum":["asset","stablecoin",null]},"creation":{"type":["object","null"],"description":"Creation tx/block/creator and decoded create params (null if not a native B20)."},"issuerPowers":{"type":["object","null"],"description":"Current issuer capabilities: canMint, canBurn, canPause, canFreeze, canSeize, adminActive, supplyCap, transferRestrictions."},"issuerActivity":{"type":["object","null"],"description":"Observed on-chain issuer actions: mints, burns, freezes, seizures, pauses, memos, roleChanges, lastActionBlock."},"flags":{"type":"array","description":"Risk flags.","items":{"type":"object","properties":{"code":{"type":"string"},"detail":{"type":"string"},"severity":{"type":"string","enum":["info","low","medium","high"]}}}},"verdict":{"type":"string","enum":["ok","caution","danger","not_applicable"],"description":"ok = safe positive (a clean B20, or a recognized canonical asset). caution / danger = risk. not_applicable = the address is not a B20 and not a recognized canonical asset, so there is nothing B20-specific to vouch for; do NOT read it as 'safe'. isB20 is the machine-readable in-scope signal."},"attestation":{"type":"object","description":"EIP-712 attestation. attestationId is a deterministic content id (always present, but NOT an integrity proof on its own); integrity requires signed=true and recovering signer from signature. NOTE: domain.verifyingContract is the ADDRESS OF THE TOKEN being attested, not a B20 Check contract (there is no on-chain verifier); it is used for per-token domain separation so a signature cannot be replayed across tokens. To verify, reconstruct the EIP-712 domain with the token address as verifyingContract.","properties":{"pinnedBlock":{"type":"integer"},"issuedAt":{"type":"integer"},"expiresAt":{"type":"integer"},"attestationId":{"type":"string"},"signature":{"type":["string","null"]},"signer":{"type":["string","null"]},"signed":{"type":"boolean"},"domain":{"type":"object","description":"EIP-712 domain. verifyingContract = the token address (per-token replay separation), NOT a service contract."}}}},"example":{"address":"0xb200000000000000000000f52267Ce6274432c30","network":"mainnet","isB20":true,"variant":"asset","creation":{"txHash":"0x9a67ce…","block":48380900,"timestamp":1783491000,"creator":null,"txOrigin":"0x52c8ff…","viaLauncher":true,"launcher":"0x77132d…","params":{"variant":"asset","name":"Example Token","symbol":"EXTKN","decimals":18,"currency":null}},"issuerPowers":{"canMint":false,"canBurn":false,"canPause":false,"canFreeze":false,"canSeize":false,"adminActive":false,"freezeConfigured":false,"supplyCap":"1000000000000000000000000000","supplyCapKnown":true,"transferRestrictions":false},"issuerActivity":{"mints":1,"burns":0,"freezes":0,"seizures":0,"pauses":0,"memos":0,"roleChanges":0,"lastActionBlock":48380900},"flags":[{"code":"VIA_LAUNCHER","detail":"Created through an intermediary launcher contract, not a direct factory call.","severity":"info"}],"verdict":"ok","attestation":{"pinnedBlock":48443612,"issuedAt":1783670000,"expiresAt":1783670600,"attestationId":"0x3e7197…","signature":"0x8f2a…","signer":"0x1388d1e3751512955A53C7c2795Eaf8a3B029e6A","signed":true,"domain":{"name":"B20 Check Attestation","version":"1","chainId":8453,"verifyingContract":"0xb200000000000000000000f52267Ce6274432c30"}}}}