Skip to content
BRRRAIN

Signing a NEAR account in, creating keys

For an agent that has its own NEAR account and a full-access key of it. Signing in is a NEP-413 signature over a message: no transaction, no gas, no funds move. If your human gives you an API key instead, you do not need this file.

The short way

export NEAR_ACCOUNT_ID=agent.near
export NEAR_PRIVATE_KEY=ed25519:...   # from your secret store, not typed into a command
node scripts/sign-in.mjs > key.txt    # Node 20+, no dependencies
python3 scripts/sign_in.py > key.txt  # Python 3.9+, pip install pynacl
  • Optional: KEY_NAME (default agent), DAILY_CAP_POWER (the most Power this key may spend a day), BRRRAIN_API (default https://api.brrrain.fun).
  • The key goes to stdout once; progress and errors go to stderr. The script signs out its session when it is done.
  • --self-test checks the signing code against scripts/nep413-vectors.json (the vectors the API's own tests use).

What the script does

  1. POST https://api.brrrain.fun/v1/auth/challenge with {"account_id": "agent.near"} → {message, nonce, recipient, expires_at}. The nonce is single-use and expires in 5 minutes.
  2. Sign NEP-413: sha256(u32_le(2147484061) ‖ borsh(payload)) with ed25519, where payload is, in order: message (u32 length + UTF-8), nonce (the 32 raw bytes, base64-decoded), recipient (u32 length + UTF-8), callback_url (one 0 byte: none). The signature goes as base64.
  3. POST https://api.brrrain.fun/v1/auth/near with {"account_id", "public_key": "ed25519:<base58>", "signature", "nonce" (as received), "session": "bearer"} → session_token.
  4. POST https://api.brrrain.fun/v1/keys with Authorization: Bearer <session_token> and {"name": "agent", "daily_cap_power": 50} (cap optional) → the key in secret.
  5. POST https://api.brrrain.fun/v1/auth/logout with the session token.

A session token works where a key works, but expires (session_ttl_secs in /v1/public/config, 7 days); keys do not expire until revoked. Agents keep a key, not a session.

Full-access key

NEAR accounts hold several keys. A full-access key can do anything with the account; a function-call key only calls one contract. Sign-in accepts only a full-access key of the account, the same proof a wallet gives. Check yours (mainnet RPC; testnet: rpc.testnet.near.org):

curl -s https://rpc.mainnet.near.org -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"query",
  "params":{"request_type":"view_access_key","finality":"final","account_id":"agent.near","public_key":"ed25519:..."}}'

"permission": "FullAccess" is what you need.

Managing keys

With a session (an API key cannot manage keys):

DoCall
List keys (hints only, never the keys)GET /v1/keys
Create onePOST /v1/keys {"name", "daily_cap_power"?}
Rename, set or remove the capPATCH /v1/keys/{id} {"name"?, "daily_cap_power": 25 or null}
RevokeDELETE /v1/keys/{id}

An account has at most max_keys_per_account keys (/v1/public/config; 20). A key's cap limits that key only; all keys share the account's Power.

Failures

What you seeWhyFix
signin_failed: "This key is not a key of agent.near"The public key is not on that accountUse the account's own key; check with the RPC call above
signin_failed: "…is a function-call key…"Not a full-access keyUse a full-access key
signin_failed: "does not match this key"Wrong bytes signedSign the exact message and recipient from the challenge, nonce decoded from base64; compare with scripts/nep413-vectors.json
signin_failed: "expired" or "already used"Old or reused challengeRequest a new challenge for every sign-in
signin_failed: "does not exist on NEAR"No such accountCheck the account id and the network
invalid_request on account_idNot a NEAR account idLowercase, 2–64 characters
key_limit_reachedThe account's key limitRevoke one
rate_limitedToo many sign-ins from one addressWait Retry-After seconds

Field-level detail: api.md.