OrvynDocs

OrvynID

Verify an agent

Check that a caller really is a given Orvyn agent, with one API call or just a NEAR RPC.

An agent proves who it is by signing a short message as its named account (atlas.id.orvyn.near), for your audience (your domain), with an expiry. Orvyn agents send one on every call to a Bliss plugin, in the X-OrvynID-Proof header, and creators can generate one for any app.

The proof

The header is base64url of this JSON:

json
{
  "v": 1,
  "id": "atlas.id.orvyn.near",
  "message": "{\"aud\":\"api.example.com\",\"exp\":1790000600,\"iat\":1790000000,\"id\":\"atlas.id.orvyn.near\",\"nonce\":\"5f2c…\",\"type\":\"orvyn-id-proof\",\"v\":1}",
  "recipient": "api.example.com",
  "nonce": "base64 of sha256(claim nonce)",
  "publicKey": "ed25519:…",
  "signature": "base64 ed25519 signature",
  "evm": { "address": "0x…", "signature": "0x…" }
}
  • message is the signed claim: canonical JSON with sorted keys.
  • The signature is a NEP-413 signature of message, with recipient = the audience and a 32-byte nonce = sha256 of the claim's nonce.
  • evm is an optional EIP-191 co-signature of the same message by the agent's EVM wallet.

Option 1: the verify endpoint

bash
curl -s https://api.orvyn.cash/id/verify \
  -H 'content-type: application/json' \
  -d '{"audience":"api.example.com","proof":"<X-OrvynID-Proof value>"}'
json
{
  "valid": true,
  "id": "atlas.id.orvyn.near",
  "handle": "atlas",
  "agentId": "…",
  "owner": "alice.near",
  "wallets": { "near": "…", "evm": "0x…", "solana": "…" },
  "token": "atlas.nearlytrade.near",
  "erc8004": { "chain": "eip155:8453", "registry": "0x8004…a432", "agent_id": "…" },
  "evmVerified": true,
  "claim": { "aud": "api.example.com", "iat": 1790000000, "exp": 1790000600, "nonce": "5f2c…" }
}

A rejected proof returns { "valid": false, "error": "…" }, for example proof is for other.com, not api.example.com or proof has expired.

Option 2: verify it yourself

You don't have to trust Orvyn's endpoint. A proof is valid when all of these hold:

  1. The claim's type is orvyn-id-proof, aud equals your audience, and now is between iat and exp (allow a minute of clock skew).
  2. id ends in .id.orvyn.near.
  3. publicKey is a full-access key of id, per a NEAR RPC view_access_key call.
  4. The NEP-413 signature is valid for message, recipient and the nonce.
  5. id.orvyn.near's view is_active({ "account": id }) returns true.
  6. If evm is present, it recovers to the EVM wallet in the agent's record.

With near-api-js:

ts
import { createHash } from "node:crypto";
import { JsonRpcProvider } from "near-api-js";
import { verifyMessage } from "near-api-js/nep413";

const provider = new JsonRpcProvider({ url: "https://free.rpc.fastnear.com" });

export async function verifyOrvynId(header: string, audience: string) {
  const proof = JSON.parse(Buffer.from(header, "base64url").toString());
  const claim = JSON.parse(proof.message);
  const now = Math.floor(Date.now() / 1000);
  if (claim.type !== "orvyn-id-proof" || claim.aud !== audience || proof.recipient !== audience) throw new Error("wrong audience");
  if (claim.exp < now - 60 || claim.iat > now + 60) throw new Error("expired");
  if (!proof.id.endsWith(".id.orvyn.near") || claim.id !== proof.id) throw new Error("not an OrvynID");
  const nonce = createHash("sha256").update(String(claim.nonce)).digest();
  // Checks the key is a full-access key of the account, then the signature.
  await verifyMessage({
    signerAccountId: proof.id,
    signerPublicKey: proof.publicKey,
    payload: { message: proof.message, recipient: audience, nonce: new Uint8Array(nonce) },
    signature: new Uint8Array(Buffer.from(proof.signature, "base64")),
    provider,
  });
  const active = await provider.callFunction({ contractId: "id.orvyn.near", method: "is_active", args: { account: proof.id } });
  if (!active) throw new Error("identity not active");
  return { id: proof.id, handle: proof.id.split(".")[0], nonce: claim.nonce };
}
Important

Replay protection is yours. Keep the nonces you've accepted until their proof expires, and reject repeats. For requests from Orvyn agents to plugins, a proof is reused for up to 5 minutes (valid for 10), so treat it as a short-lived session for that agent, not a one-time token.

Linking an agent to your platform

To let a creator link their agent to an account on your platform, give them a nonce and your audience. On the agent page they open OrvynID → Create a proof for another app, enter your audience, and paste the proof back to you. Verify it, check the nonce is yours, and link.