Reading the report
verifyKyiResponse returns one report for the whole response, with a report per token inside it. This page explains each part, then walks through a real lookup.
Checks
Each token report has checks: one per verification and one per listed authority. A verification check has a proof and an at. An authority check has only an at.
proof: the timeless facts: the wallet backs the authority, the signature recovers, the authority is listed for the token. It needs no network.at[label]: the result at one moment you asked for. It has astatus, areasonwhen notvalid, itssteps, and apositionwhen the chain was read.
position is the chain position the result was read at: block and time on EVM, slot on Solana, ledger on XRPL (plus time for a past moment). Compare its time with your own freshness rule.
Each check also has a subject (the wallet or authority address) and a method, such as signature:eip191, contract_signature:eip1271/eip712, transfer:amount, admin_manual, or the authority's type.
Status
Each result has a status:
valid: every step passed.invalid: a step failed.reasonis<step> failed. On EVM, a transaction that doesn't exist, a contract call that reverts, or a call to an address with no contract also counts as a failure.unverifiable: the claim couldn't be checked, for example because the proof material is missing, the method isn't supported, or the verification has expired.
A verification's status at a moment combines its proof with that moment's state. If the proof is invalid, the verification is invalid at every moment, with the proof's reason. Otherwise the moment decides.
Reasons for moments
A failed step makes the result invalid with the reason <step> failed. These reasons come from the SDK core and apply on every chain:
reason | When |
|---|---|
verification's authority is listed for the token failed | The verification's authority isn't in the token's authorities. Makes the proof result invalid |
record postdates this moment | The moment is before verified_at or observed_at |
no recorded moment | "verified" on an authority with no observed_at |
verification expired at <expires_at> | A verification that would be valid at the moment had expired by then |
Each chain adds its own, such as online checks not requested on EVM. The Reference lists them, with when each applies: EVM, Solana and XRPL.
Steps
The same signature step can appear under both proof and at[label]. A moment's steps hold the method re-check and the chain-state steps for that moment, so at.now shows what was read without going back to proof. They don't repeat the steps that the wallet backs the authority and that the authority is listed. Those live only in proof. If the proof is invalid, every moment is invalid with the proof's reason, even when all of that moment's steps passed. On XRPL, a moment replaces the offline master-key step with the key authorization read at that ledger.
Steps include the expected and actual values and whether they ran offline or over rpc. You can log or store them as an audit trail. The Reference lists every step by name.
Evidence and summaries
The SDK sums up the checks for each token as evidence:
evidence | Meaning | verified |
|---|---|---|
confirmed | At least one verification is valid and no check is invalid | true |
contradicted | At least one check is invalid | false |
inconclusive | Nothing is invalid, but no verification could be confirmed | false |
matchesClaim compares verified with what Bluprynt claimed. It is null when Bluprynt claims the token is verified but the evidence is inconclusive. In that case, the SDK can neither confirm nor refute the claim.
There is one summary offline and one per moment:
- Top-level
evidence,verified,matchesClaim: from the verifications'proofonly. Authorities are not part of it, because they're checked only at moments. at[label]: from every check's result at that moment, verifications and authorities alike.
To show a token as verified, read a moment, not the offline summary. Showing a badge explains which fields to use.
The response has its own evidence, offline and at each moment: confirmed if any token is confirmed, otherwise contradicted if any token is contradicted, otherwise inconclusive. Its verified is true when its evidence is confirmed. Its matchesClaim compares claimed with verified and is null only when the evidence is inconclusive and Bluprynt claims the response is verified.
Expiry
Expiry applies at each moment, not to proof. A verification whose expires_at is at or before the moment is unverifiable at that moment, with the reason verification expired at <expires_at>. For "now", that's the current time. An expired proof no longer shows control, but it doesn't disprove anything either, so it can't override another, current proof. A verification that fails its check is still invalid.
Because proof ignores expiry, the offline summary can be confirmed by an expired verification. Ask for a moment when expiry matters.
One call, several chains
Each chain has its own checker: evmKyi, solanaKyi and xrplKyi. To check a response that spans chains, import verifyKyiResponse from @bluprynt/sdk and pass the checkers you need in chains. A token is routed to the checker for its chain.
import { verifyKyiResponse } from "@bluprynt/sdk";
import { evmKyi } from "@bluprynt/sdk-chain-evm";
import { solanaKyi } from "@bluprynt/sdk-chain-solana";
import { xrplKyi } from "@bluprynt/sdk-chain-xrpl";
const report = await verifyKyiResponse(response, {
chains: [evmKyi, solanaKyi, xrplKyi],
rpcUrls: {
"eip155:1": "https://mainnet-eth.rpc.example.com",
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "https://solana.rpc.example.com",
"xrpl:0": "wss://xrpl.rpc.example.com",
},
at: ["now"],
});A token on a chain with no checker in chains throws. See RPC and errors.
Example
Below is a lookup for a test token on Sepolia. Its issuer proved control of the token's controlling wallet with an EIP-191 signature.
API response
{
"kyi_verified": true,
"data": [
{
"kyi_verified": true,
"token": {
"caip19": "eip155:11155111/erc20:0xcbc76ceb0afa037cbb975fd330bd683754998cc3",
"caip2": "eip155:11155111",
"chain_slug": "sepolia",
"address": "0xCBc76CeB0Afa037cBb975Fd330Bd683754998Cc3",
"standard": null,
"decimals": null,
"verified_at": "2026-10-06T13:30:46.372Z"
},
"asset": {
"slug": "onvi-2mdu3y",
"name": "Onyx Viper",
"symbol": "ONVI",
"description": "https://coo.coo",
"website_url": "https://coo.coo",
"whitepaper_url": "https://coo.coo"
},
"issuer": {
"name": "Example Organization",
"kyb_verified": true
},
"authorities": [
{
"type": "controlled_by",
"address": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"account_type": "regular",
"signers": [],
"threshold": null,
"observed_at": "2026-10-06T13:29:21.759Z"
}
],
"verifications": [
{
"verified_at": "2026-10-06T13:30:46.372Z",
"expires_at": null,
"wallet_address": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"authority": {
"type": "controlled_by",
"address": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"account_type": "regular",
"signers": [],
"threshold": null,
"observed_at": "2026-10-06T13:29:21.759Z"
},
"method": {
"type": "signature",
"scheme": "eip191",
"message": "Please, sign this message to verify your Bluprynt KYI credentials\n\nPurpose: Verify wallet ownership\nIssued to: 1173\nAddress: 0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c::1791293432710::37ae7bb2fb93c56f09e7c5f523d8060b.b163c968e50ed343ad4aa773bce98de8dea7def92bddb4f16ebdf8265c14e062",
"signature": "0xec83133187bf78019c3c4b4137d14e1876b167c2f64ca05bfc06f5491c9c257e1477862593d19651ede7268e9f7470c773d46e1a818d2eb3b2b82ede600010141b",
"signer_public_key": null
}
}
]
}
]
}The EVM example passes it to verifyKyiResponse(lookup) with no options. Its test checks that this output stays the same. The report is under sdk.report:
Output of the EVM example
{
"independent": {
"wallet": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"recovered": "0xf748a9220B9B0D79D6eeD2D927fCdFE80055b19c",
"recoversToWallet": true,
"tamperedMessage": {
"recovered": "0x0BF280187914DFD1eF78C0afc6C29d5857eFcbcc",
"recoversToWallet": false
}
},
"sdk": {
"report": {
"claimed": true,
"evidence": "confirmed",
"verified": true,
"matchesClaim": true,
"tokens": [
{
"caip19": "eip155:11155111/erc20:0xcbc76ceb0afa037cbb975fd330bd683754998cc3",
"claimed": true,
"evidence": "confirmed",
"verified": true,
"matchesClaim": true,
"checks": [
{
"kind": "verification",
"subject": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"method": "signature:eip191",
"proof": {
"status": "valid",
"steps": [
{
"check": "wallet is the authority or one of its signers",
"via": "offline",
"ok": true,
"expected": [
"0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
],
"actual": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
},
{
"check": "eip191 signature recovers to wallet",
"via": "offline",
"ok": true,
"expected": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"actual": "0xf748a9220B9B0D79D6eeD2D927fCdFE80055b19c"
},
{
"check": "verification's authority is listed for the token",
"via": "offline",
"ok": true,
"expected": [
{
"type": "controlled_by",
"address": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
}
],
"actual": {
"type": "controlled_by",
"address": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
}
}
]
},
"at": {}
},
{
"kind": "authority",
"subject": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"method": "controlled_by",
"at": {}
}
],
"at": {}
}
],
"at": {}
},
"tamperedMessage": {
"status": "invalid",
"reason": "eip191 signature recovers to wallet failed",
"steps": [
{
"check": "wallet is the authority or one of its signers",
"via": "offline",
"ok": true,
"expected": [
"0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
],
"actual": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
},
{
"check": "eip191 signature recovers to wallet",
"via": "offline",
"ok": false,
"expected": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"actual": "0x0BF280187914DFD1eF78C0afc6C29d5857eFcbcc"
}
]
}
}
}The signature recovers to the wallet without any network access, and the verification's authority is one of the token's listed authorities, so the proof is valid. No moments were asked for, so every at is empty. One valid proof makes the offline evidence confirmed, which matches Bluprynt's claim.
The SDK has no generic on-chain check for a controlled_by authority on EVM. Asking for at: ["verified", "now"] shows what that means: at every moment, both the authority and the verification are unverifiable. The verification is read with its own authority, so it can't be confirmed at a moment either. Its proof result is still valid. This report needs no RPC calls, because a signature and a controlled_by authority read no chain state.
Report with at: ["verified", "now"]
{
"claimed": true,
"evidence": "confirmed",
"verified": true,
"matchesClaim": true,
"tokens": [
{
"caip19": "eip155:11155111/erc20:0xcbc76ceb0afa037cbb975fd330bd683754998cc3",
"claimed": true,
"evidence": "confirmed",
"verified": true,
"matchesClaim": true,
"checks": [
{
"kind": "verification",
"subject": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"method": "signature:eip191",
"proof": {
"status": "valid",
"steps": [
{
"check": "wallet is the authority or one of its signers",
"via": "offline",
"ok": true,
"expected": [
"0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
],
"actual": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
},
{
"check": "eip191 signature recovers to wallet",
"via": "offline",
"ok": true,
"expected": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"actual": "0xf748a9220B9B0D79D6eeD2D927fCdFE80055b19c"
},
{
"check": "verification's authority is listed for the token",
"via": "offline",
"ok": true,
"expected": [
{
"type": "controlled_by",
"address": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
}
],
"actual": {
"type": "controlled_by",
"address": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c"
}
}
]
},
"at": {
"verified": {
"status": "unverifiable",
"reason": "no generic EVM check for controlled_by",
"steps": [
{
"check": "eip191 signature recovers to wallet",
"via": "offline",
"ok": true,
"expected": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"actual": "0xf748a9220B9B0D79D6eeD2D927fCdFE80055b19c"
}
]
},
"now": {
"status": "unverifiable",
"reason": "no generic EVM check for controlled_by",
"steps": [
{
"check": "eip191 signature recovers to wallet",
"via": "offline",
"ok": true,
"expected": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"actual": "0xf748a9220B9B0D79D6eeD2D927fCdFE80055b19c"
}
]
}
}
},
{
"kind": "authority",
"subject": "0xf748a9220b9b0d79d6eed2d927fcdfe80055b19c",
"method": "controlled_by",
"at": {
"verified": {
"status": "unverifiable",
"reason": "no generic EVM check for controlled_by",
"steps": []
},
"now": {
"status": "unverifiable",
"reason": "no generic EVM check for controlled_by",
"steps": []
}
}
}
],
"at": {
"verified": {
"evidence": "inconclusive",
"verified": false,
"matchesClaim": null
},
"now": {
"evidence": "inconclusive",
"verified": false,
"matchesClaim": null
}
}
}
],
"at": {
"verified": {
"evidence": "inconclusive",
"verified": false,
"matchesClaim": null
},
"now": {
"evidence": "inconclusive",
"verified": false,
"matchesClaim": null
}
}
}The offline summary still confirms the claim. At each moment, the evidence is inconclusive and matchesClaim is null: nothing contradicts Bluprynt, but nothing on-chain confirms the authority either. For an owner or Safe authority, the same request reads the chain at each moment and reports the block it read. The Safe example shows one. Showing a badge suggests how to turn these summaries into a label.