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
whitelisttransaction. Then you callsyncwith thetxSignatureas authorship proof.
See the wallet whitelisting flow for the full sequence.
Wallet states
| State | Meaning |
|---|---|
pending | Registered. The grant is not yet proven on-chain. |
active | The grant was confirmed and proven. It passes intent preflights only when reconciliationStatus is synced. |
revoked / banned | Set 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
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | Nora wallet-registration ID. |
address | string | Yes | Base58 Solana wallet address. |
chainId | "solana" | Yes | Chain for this registration. |
role | "minter" | "whitelister" | "buyer" | "buyerAndMinter" | "admin" | Yes | The API creates only minter rows. Other roles are read-only. |
label | string | null | Yes | Partner-defined label. |
state | "pending" | "active" | "revoked" | "banned" | Yes | Current registration state. |
statusReason | string | null | Yes | Diagnostic 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" | Yes | Agreement between the row and on-chain state. |
whitelisterWallet | string | null | Yes | Whitelister bound to the grant. |
createdAt | string | Yes | ISO 8601 creation time. |
updatedAt | string | Yes | ISO 8601 last-update time. |
Operation
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | Yes | Operation ID. |
operationType | "grant" | "regrant" | "accept_onchain" | "revoke" | "ban" | "repair" | Yes | Action represented by this operation. |
status | "prepared" | "linked_existing" | "submitted" | "confirmed" | "failed" | "expired" | Yes | Current operation state. |
txSignature | string | null | Yes | Broadcast transaction signature, when available. |
errorMessage | string | null | Yes | Failure diagnostic, when available. |
createdAt | string | Yes | ISO 8601 creation time. |
updatedAt | string | Yes | ISO 8601 last-update time. |
Transaction
transaction is null when the wallet has no current preparation.
| Field | Type | Required | Description |
|---|---|---|---|
unsignedTransaction | string | Yes | Base64-encoded Solana transaction. |
blockhash | string | Yes | Recent blockhash used by the preparation. |
lastValidBlockHeight | integer | Yes | Last block height at which the transaction is valid. |
expiresAt | string | Yes | ISO 8601 expiration time. |
signerWallet | string | Yes | Bound whitelister and transaction fee payer. |
instruction | Instruction | null | Yes | Decomposed grant instruction for transaction composition. null when the stored preparation is not decodable. |
Instruction has these fields:
| Field | Type | Required | Description |
|---|---|---|---|
programId | string | Yes | Solana program address. |
keys | InstructionKey[] | Yes | Ordered instruction accounts. |
data | string | Yes | Base64-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:
linkedExistingistruewhen Nora finds a proven on-chain grant and activates the wallet without a new broadcast.replayedistruewhen Nora returns the stored response for the sameIdempotency-Keyand request body. It isfalsefor 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.
| Query | Type | Notes |
|---|---|---|
state | enum | pending | active | revoked | banned |
role | enum | minter | 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
| Header | Required | Notes |
|---|---|---|
X-API-Key | Yes | |
Idempotency-Key | Yes | UUID. The same key with a different payload returns 409. |
Content-Type | Yes | application/json |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
address | string | Yes | Base58 wallet to whitelist as minter. |
whitelisterWallet | string | Yes | Your active whitelister. It signs the grant. |
chainId | "solana" | No | Default "solana". |
label | string | No | Up 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.
| Field | Type | Required | Description |
|---|---|---|---|
signedTransaction | string | Yes | The 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.
| Field | Type | Required | Description |
|---|---|---|---|
txSignature | string | No | The 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
| Code | Meaning |
|---|---|
WHITELISTER_NOT_FOUND | whitelisterWallet is not registered for your org and environment. |
WHITELISTER_NOT_ACTIVE | The whitelister exists and is not active. |
WALLET_ALREADY_REGISTERED | The address already has a row in your org. |
ADDRESS_UNAVAILABLE | The address cannot be registered. |
WALLET_BANNED | The address is banned on-chain. |
TRANSACTION_EXPIRED | The preparation's blockhash expired. Call prepare again. |
TRANSACTION_MISMATCH | The signed bytes do not match the preparation. |
ALREADY_ACTIVE | The wallet is already active. |
NOT_ON_CHAIN | sync found no grant, or submit could not confirm an exact minter grant. Poll the wallet or call sync. |
SYNC_PROOF_REQUIRED | On-chain state exists and authorship proof is missing. |
IDEMPOTENCY_KEY_MISMATCH | The same Idempotency-Key arrived with a different payload. |
WALLET_NOT_PENDING | Only pending registrations can be cancelled. |
WALLET_NOT_MANAGED | The wallet is not a minter registration managed by this API. |
WALLET_CAP_EXCEEDED | The 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.