nora

Wallets

Register and manage whitelisted minter wallets. Your whitelister wallet signs every grant. Nora does not sign routine grants.

A wallet needs an on-chain whitelist grant before it can receive mints or burn BRS in your flows. This surface registers minter wallets for your organization and activates their grant. The server fixes the role to minter; the request schema has no role field. You register whitelister wallets on the dashboard, not through this API.

chainId accepts only "solana".

Signing model

Your whitelister wallet signs every grant transaction. It also pays the rent and the fee. Two paths complete a registration:

  • Prepared: Nora returns an unsigned transaction. You sign it with your whitelister and return it via submit.
  • Self-built: you build and broadcast your own whitelist transaction. Then you call sync with the txSignature as authorship proof.

See the wallet whitelisting flow for the full sequence.

Wallet states

StateMeaning
pendingRegistered. The grant is not yet proven on-chain.
activeThe grant was confirmed and proven. It passes intent preflights only when reconciliationStatus is synced.
revoked / bannedSet by Nora operations. Read-only on this surface.

statusReason: "awaiting_sync_proof" marks a pending wallet with observed on-chain state and no authorship proof. Call sync with the grant's txSignature to activate it.

Response schemas

Wallet

FieldTypeRequiredDescription
idstring (uuid)YesNora wallet-registration ID.
addressstringYesBase58 Solana wallet address.
chainId"solana"YesChain for this registration.
role"minter" | "whitelister" | "buyer" | "buyerAndMinter" | "admin"YesThe API creates only minter rows. Other roles are read-only.
labelstring | nullYesPartner-defined label.
state"pending" | "active" | "revoked" | "banned"YesCurrent registration state.
statusReasonstring | nullYesDiagnostic for a pending registration or an on-chain drift. It can include "awaiting_sync_proof", "awaiting_onchain_confirmation", "address_unavailable", or "not_on_chain".
reconciliationStatus"synced" | "missing_onchain" | "drifted"YesAgreement between the row and on-chain state.
whitelisterWalletstring | nullYesWhitelister bound to the grant.
createdAtstringYesISO 8601 creation time.
updatedAtstringYesISO 8601 last-update time.

Operation

FieldTypeRequiredDescription
idstring (uuid)YesOperation ID.
operationType"grant" | "regrant" | "accept_onchain" | "revoke" | "ban" | "repair"YesAction represented by this operation.
status"prepared" | "linked_existing" | "submitted" | "confirmed" | "failed" | "expired"YesCurrent operation state.
txSignaturestring | nullYesBroadcast transaction signature, when available.
errorMessagestring | nullYesFailure diagnostic, when available.
createdAtstringYesISO 8601 creation time.
updatedAtstringYesISO 8601 last-update time.

Transaction

transaction is null when the wallet has no current preparation.

FieldTypeRequiredDescription
unsignedTransactionstringYesBase64-encoded Solana transaction.
blockhashstringYesRecent blockhash used by the preparation.
lastValidBlockHeightintegerYesLast block height at which the transaction is valid.
expiresAtstringYesISO 8601 expiration time.
signerWalletstringYesBound whitelister and transaction fee payer.
instructionInstruction | nullYesDecomposed grant instruction for transaction composition. null when the stored preparation is not decodable.

Instruction has these fields:

FieldTypeRequiredDescription
programIdstringYesSolana program address.
keysInstructionKey[]YesOrdered instruction accounts.
datastringYesBase64-encoded instruction data.

Each InstructionKey has pubkey (string), isSigner (boolean), and isWritable (boolean). Preserve the array order when you build a transaction.

Idempotency flags

Create and submit responses include two booleans:

  • linkedExisting is true when Nora finds a proven on-chain grant and activates the wallet without a new broadcast.
  • replayed is true when Nora returns the stored response for the same Idempotency-Key and request body. It is false for the first execution.

GET /v2/wallets

List your registered wallets. Whitelister rows appear read-only.

The list returns only minter and whitelister rows. A detail request can also return a legacy read-only role.

QueryTypeNotes
stateenumpending | active | revoked | banned
roleenumminter | whitelister

Response 200

{ wallets: Wallet[] }

GET /v2/wallets/:id

Wallet detail.

Response 200

{ wallet: Wallet, transaction: Transaction | null, operations: Operation[] }

transaction holds the current preparation (unsignedTransaction, blockhash, lastValidBlockHeight, expiresAt, signerWallet, instruction) or null. instruction is the decomposed grant instruction (programId, keys, data in base64) for partners who compose their own transaction. operations contains at most 25 records, newest first. Poll this endpoint to observe state transitions; do not use it as an audit-history feed.

POST /v2/wallets

Register a minter wallet. The response stages the unsigned grant transaction.

Headers

HeaderRequiredNotes
X-API-KeyYes
Idempotency-KeyYesUUID. The same key with a different payload returns 409.
Content-TypeYesapplication/json

Request body

FieldTypeRequiredDescription
addressstringYesBase58 wallet to whitelist as minter.
whitelisterWalletstringYesYour active whitelister. It signs the grant.
chainId"solana"NoDefault "solana".
labelstringNoUp to 64 characters.

Response 200 / 201

{ wallet: Wallet, operation: Operation | null, transaction: Transaction | null, linkedExisting: boolean, replayed: boolean }

When transaction is present, transaction.unsignedTransaction is base64. Its feePayer is your whitelister. transaction.instruction carries the same grant decomposed (programId, keys, data) for your own composition. If a proven grant already exists on-chain, the wallet activates without a broadcast, transaction is null, and linkedExisting is true.

POST /v2/wallets/:id/prepare

Return the current preparation, including its decomposed instruction. While blockHeight is at or below lastValidBlockHeight, the endpoint returns the same preparation. It generates a new one only after expiry. The bound signer never changes.

Response 200

{ wallet: Wallet, transaction: Transaction | null, activated: boolean }

transaction is null when Nora observes an on-chain grant. If activated is false, call sync with the grant transaction signature to provide authorship proof.

POST /v2/wallets/:id/submit

Broadcast your signed grant. Requires Idempotency-Key, with the same rules as create.

FieldTypeRequiredDescription
signedTransactionstringYesThe prepared transaction, signed, base64.

Nora verifies the signed bytes against the preparation. The fee payer, the blockhash, and the instructions must match. You may add only bounded ComputeBudget instructions. A mismatch returns details.code of TRANSACTION_MISMATCH. An expired preparation returns details.code of TRANSACTION_EXPIRED: call prepare, re-sign, and submit again with a new Idempotency-Key.

Response 200

{ wallet: Wallet, operation: Operation, linkedExisting: boolean, replayed: boolean }

POST /v2/wallets/:id/sync

Reconcile the wallet from on-chain state. Use it after you broadcast your own transaction, or anytime as a drift re-check.

FieldTypeRequiredDescription
txSignaturestringNoThe grant transaction's signature. Required to activate a grant Nora did not broadcast.

Activation requires authorship proof. The grant must bind to this registration's own preparation, or the txSignature's WhitelistUpdatedEvent.updated_by must be one of your active whitelisters. Without proof the call returns 422 with details.code of SYNC_PROOF_REQUIRED. The wallet stays pending.

Response 200

{ outcome: "activated" | "already_synced" | "not_on_chain", wallet: Wallet, operation: Operation | null }

operation is null when the result is already_synced or not_on_chain.

DELETE /v2/wallets/:id

Cancel a pending registration. This API cannot delete active wallets.

Response 200

{ id: string }

Error envelope

Every wallet endpoint returns this shape for non-2xx responses:

{
  "code": "CONFLICT",
  "message": "The prepared transaction has expired.",
  "details": { "code": "TRANSACTION_EXPIRED" }
}

code and message are always present. validationErrors is optional and contains field-level validation failures. details is optional. For a wallet-specific error, use details.code for recovery. Use the top-level code only for the general error category.

Machine error codes

CodeMeaning
WHITELISTER_NOT_FOUNDwhitelisterWallet is not registered for your org and environment.
WHITELISTER_NOT_ACTIVEThe whitelister exists and is not active.
WALLET_ALREADY_REGISTEREDThe address already has a row in your org.
ADDRESS_UNAVAILABLEThe address cannot be registered.
WALLET_BANNEDThe address is banned on-chain.
TRANSACTION_EXPIREDThe preparation's blockhash expired. Call prepare again.
TRANSACTION_MISMATCHThe signed bytes do not match the preparation.
ALREADY_ACTIVEThe wallet is already active.
NOT_ON_CHAINsync found no grant, or submit could not confirm an exact minter grant. Poll the wallet or call sync.
SYNC_PROOF_REQUIREDOn-chain state exists and authorship proof is missing.
IDEMPOTENCY_KEY_MISMATCHThe same Idempotency-Key arrived with a different payload.
WALLET_NOT_PENDINGOnly pending registrations can be cancelled.
WALLET_NOT_MANAGEDThe wallet is not a minter registration managed by this API.
WALLET_CAP_EXCEEDEDThe organization reached its active and pending minter-wallet limit.

Rate limits

prepare, submit, and sync share a dedicated limit of 20 requests per minute per API key. This limit is in addition to the global per-key limit. Poll GET /v2/wallets/:id for status. Do not poll sync.

On this page