Everything you need to build on Anonyma — SDK integration, JSON-RPC API, WebSocket subscriptions, chain architecture, and proof-of-concept validation.
Connect your dApp to Anonyma with the browser wallet extension SDK.
Users install the Anonyma Wallet Chrome extension from the Chrome Web Store. The extension injects the provider at window.anonyma.
Wait for the anonyma#initialized event or check if window.anonyma exists. The provider is ready when window.anonyma.isAnonyma === true.
Call connect() to request wallet access. Once approved, use the full SDK to send transactions, sign messages, and query balances.
// Wait for wallet extension to initialize
function getProvider() {
if ('anonyma' in window) {
return window.anonyma;
}
return new Promise(resolve => {
window.addEventListener('anonyma#initialized', () => {
resolve(window.anonyma);
}, { once: true });
});
}
// Connect to wallet
const provider = await getProvider();
const { address, publicKey } = await provider.connect();
console.log('Connected:', address);
Complete reference for the window.anonyma provider injected by the wallet extension.
isAnonyma
boolean
Always true. Use this to verify the Anonyma provider is present.
if (window.anonyma?.isAnonyma) {
// Anonyma wallet is available
}
getState()
async
→ { isLocked, hasWallet, address }
Returns the current wallet state. Use this to check if the wallet is locked, whether a wallet exists, and the current address.
| Return Field | Type | Description |
|---|---|---|
isLocked | boolean | Whether the wallet is currently locked |
hasWallet | boolean | Whether a wallet has been created |
address | string | null | Current wallet address, or null if locked |
const state = await window.anonyma.getState();
// { isLocked: false, hasWallet: true, address: "5Kd7..." }
connect()
async
→ { address, publicKey }
Requests wallet connection. Opens an approval popup for the user to confirm. Throws if the user rejects.
| Return Field | Type | Description |
|---|---|---|
address | string | Base58-encoded wallet address |
publicKey | string | Base58-encoded Ed25519 public key |
try {
const { address, publicKey } = await window.anonyma.connect();
console.log('Wallet connected:', address);
} catch (err) {
console.log('User rejected connection');
}
disconnect()
async
→ void
Disconnects the current site from the wallet. Emits the 'disconnect' event.
await window.anonyma.disconnect();
getWallet()
async
→ { address, publicKey, secretKey }
Returns full wallet keypair including the secret key. Use with extreme caution — only request this if your app needs to sign transactions client-side.
| Return Field | Type | Description |
|---|---|---|
address | string | Base58-encoded address |
publicKey | string | Base58-encoded public key |
secretKey | string | Base58-encoded Ed25519 secret key (64 bytes) |
const wallet = await window.anonyma.getWallet();
// ⚠️ Never expose secretKey to servers or third parties
signAndSendTransaction(tx)
async
→ { hash }
Signs a transaction with the wallet key and broadcasts it to the network. Opens an approval popup for user confirmation.
| Parameter | Type | Required | Description |
|---|---|---|---|
tx.to | string | Yes | Recipient address (Base58) |
tx.amount | number | Yes | Amount in drops (1 ANM = 1,000,000 drops) |
tx.data | string | No | Arbitrary data payload (max 16,384 bytes) |
// Send 1.5 ANM
const { hash } = await window.anonyma.signAndSendTransaction({
to: '5Kd7Rv3gmYxBaQEKbr9tcBQ5o3PdFuoCEm34fE2dajXW',
amount: 1_500_000, // 1.5 ANM in drops
});
console.log('TX sent:', hash);
// Send with data payload
const { hash: h2 } = await window.anonyma.signAndSendTransaction({
to: '5Kd7Rv3gmYxBaQEKbr9tcBQ5o3PdFuoCEm34fE2dajXW',
amount: 0,
data: 'Hello from Anonyma!',
});
signMessage(message)
async
→ { signature }
Signs an arbitrary string message with the wallet's Ed25519 private key. Useful for authentication and verification without sending a transaction.
| Parameter | Type | Description |
|---|---|---|
message | string | The message to sign |
const { signature } = await window.anonyma.signMessage('Prove wallet ownership');
console.log('Signature:', signature); // Base58-encoded Ed25519 signature
getBalance()
async
→ { balance, balanceANM }
Returns the connected wallet's current balance in drops and ANM.
| Return Field | Type | Description |
|---|---|---|
balance | number | Balance in drops (smallest unit) |
balanceANM | number | Balance in ANM (balance / 1,000,000) |
const { balance, balanceANM } = await window.anonyma.getBalance();
console.log(`Balance: ${balanceANM} ANM`);
on(event, callback)
subscribe
off(event, callback)
unsubscribe
Subscribe or unsubscribe to wallet events.
| Event | Callback Args | Description |
|---|---|---|
'connect' | { address } | Wallet connected to site |
'disconnect' | — | Wallet disconnected |
'accountChanged' | { address } | Active account changed |
// Listen for account changes
window.anonyma.on('accountChanged', ({ address }) => {
console.log('Account changed:', address);
});
// Listen for disconnect
window.anonyma.on('disconnect', () => {
console.log('Wallet disconnected');
});
Direct HTTP JSON-RPC 2.0 interface to the Anonyma chain node.
https://rpc.anonyma.network
All requests use POST with Content-Type: application/json. Body follows the JSON-RPC 2.0 specification.
// Base request format
{
"jsonrpc": "2.0",
"id": 1,
"method": "anm_chainInfo",
"params": {}
}
anm_chainInfo
GET
Returns chain metadata: height, block hash, pending transactions, account count, fee recycling stats.
{
"jsonrpc": "2.0",
"id": 1,
"method": "anm_chainInfo"
}
{
"chainId": "anonyma-mainnet-1",
"chainName": "Anonyma",
"height": 42069,
"latestBlock": "a1b2c3...",
"baseFee": 1,
"dropsPerAnm": 1000000,
"pendingTxs": 0,
"accounts": 128,
"totalFeesRecycled": 5000,
"totalFeesRecycledANM": 0.005
}
anm_getBalance
GET
Returns the balance for a given address in both drops and ANM.
{
"jsonrpc": "2.0",
"id": 1,
"method": "anm_getBalance",
"params": {
"address": "5Kd7Rv..."
}
}
{
"address": "5Kd7Rv...",
"balance": 1500000000,
"balanceANM": 1500.0
}
anm_getAccount
GET
Returns full account info including balance, nonce, and ANM balance.
{
"jsonrpc": "2.0",
"id": 1,
"method": "anm_getAccount",
"params": {
"address": "5Kd7Rv..."
}
}
{
"address": "5Kd7Rv...",
"balance": 1500000000,
"balanceANM": 1500.0,
"nonce": 42
}
anm_getNonce
GET
Returns the next nonce to use for a transaction from the given address. Includes pending (mempool) transactions.
{
"jsonrpc": "2.0",
"id": 1,
"method": "anm_getNonce",
"params": {
"address": "5Kd7Rv..."
}
}
{
"address": "5Kd7Rv...",
"nonce": 43
}
anm_sendTransaction
SEND
Submit a signed transaction to the network mempool. The transaction must be pre-signed with a valid Ed25519 signature.
{
"jsonrpc": "2.0",
"id": 1,
"method": "anm_sendTransaction",
"params": {
"transaction": {
"from": "5Kd7Rv...",
"to": "3xQ9Fm...",
"amount": 1000000,
"fee": 1,
"nonce": 0,
"timestamp": 1700000000000,
"data": "",
"hash": "abc123...",
"signature": "def456..."
}
}
}
// Success
{
"success": true,
"hash": "abc123..."
}
// Error
{
"success": false,
"error": "Insufficient balance"
}
anm_getTransaction
GET
Retrieve a transaction by its hash. Returns null if not found.
{
"jsonrpc": "2.0",
"id": 1,
"method": "anm_getTransaction",
"params": {
"hash": "abc123..."
}
}
{
"transaction": {
"from": "5Kd7Rv...",
"to": "3xQ9Fm...",
"amount": 1000000,
"fee": 1,
"hash": "abc123...",
...
},
"blockIndex": 1234
}
anm_getTransactions
GET
Get recent transactions for an address. Default limit is 50.
// Params
{ "address": "5Kd7Rv...", "limit": 20 }
anm_searchTransactions
GET
Search transactions for an address with optional data content filter. Default limit is 100.
// Params
{ "address": "5Kd7Rv...", "dataContains": "hello", "limit": 50 }
anm_getBlock
GET
Retrieve a full block by index, including all transactions.
// Params
{ "index": 1234 }
anm_getBlockCount
GET
Returns the current block height.
// Response
{ "height": 42069 }
anm_getLatestBlock
GET
Returns the latest block header (without full transaction data).
// Response
{
"index": 42069,
"timestamp": 1700000000000,
"hash": "a1b2c3...",
"previousHash": "d4e5f6...",
"validator": "5Kd7Rv...",
"txCount": 3
}
The RPC endpoint is rate-limited to 100 requests per 10 seconds per IP address. Request body size is limited to 256 KB. Exceeding these limits returns HTTP 429.
Subscribe to live block production and address-specific transaction events.
wss://rpc.anonyma.network
Connect via WebSocket to the same RPC endpoint. Send JSON messages to subscribe/unsubscribe to event channels.
Subscribe to Blocks
Receive a notification every time a new block is produced (~400ms).
// Send
{ "action": "subscribe", "channel": "block" }
// Unsubscribe
{ "action": "unsubscribe", "channel": "block" }
{
"event": "newBlock",
"data": {
"index": 42069,
"timestamp": 1700000000000,
"hash": "a1b2c3...",
"validator": "5Kd7...",
"txCount": 3
}
}
Subscribe to Address Transactions
Receive a notification whenever a transaction involves a specific address (as sender or recipient).
// Watch incoming/outgoing txs
{
"action": "subscribe",
"channel": "tx:5Kd7Rv..."
}
{
"event": "newTransaction",
"data": {
"transaction": { ... },
"blockIndex": 42069
}
}
const ws = new WebSocket('wss://rpc.anonyma.network');
ws.addEventListener('open', () => {
// Subscribe to new blocks
ws.send(JSON.stringify({ action: 'subscribe', channel: 'block' }));
// Subscribe to txs for a specific address
ws.send(JSON.stringify({
action: 'subscribe',
channel: 'tx:5Kd7Rv3gmYxBaQEKbr9tcBQ5o3PdFuoCEm34fE2dajXW'
}));
});
ws.addEventListener('message', (e) => {
const msg = JSON.parse(e.data);
if (msg.event === 'newBlock') {
console.log('New block:', msg.data.index, 'txs:', msg.data.txCount);
}
if (msg.event === 'newTransaction') {
console.log('New tx:', msg.data.transaction.hash);
}
});
How Anonyma's Proof-of-Authority consensus, fee recycling, and cryptography work.
Anonyma uses a single trusted validator to produce blocks. This eliminates energy-intensive mining and provides instant block finality at ~400ms intervals. The validator node is configured at genesis and signs every block with its Ed25519 key.
Transaction fees (1 drop per TX) are recycled back to the validator rather than burned. This creates a sustainable economic model where no tokens are permanently removed from circulation.
Fee recycling maintains total supply at exactly 1 billion ANM. The validator is compensated for operating the network infrastructure while preserving the token economy's stability.
Every transaction contains the following fields. The hash is computed from the raw fields, then signed with the sender's Ed25519 private key.
| Field | Type | Description |
|---|---|---|
from | string | Sender address (Base58) |
to | string | Recipient address (Base58) |
amount | number | Amount in drops |
fee | number | Fee in drops (must be ≥ BASE_FEE = 1) |
nonce | number | Sequential counter (prevents replay attacks) |
timestamp | number | Unix timestamp in milliseconds |
data | string | Optional arbitrary data (max 16,384 bytes) |
hash | string | SHA-256 hash of raw transaction fields |
signature | string | Ed25519 signature of the hash (Base58) |
// Transaction hash is computed as:
// SHA-256( from + to + amount + fee + nonce + timestamp + data )
// Then signed with Ed25519: sign(hash, secretKey)
| Field | Type | Description |
|---|---|---|
index | number | Block height (0 = genesis) |
timestamp | number | Block creation time (unix ms) |
previousHash | string | Hash of the previous block |
hash | string | SHA-256 of block header |
txRoot | string | Merkle-like root of included transactions |
stateRoot | string | Hash of state after applying block |
validator | string | Address of the block producer |
txCount | number | Number of transactions in block |
transactions | array | Array of signed transactions |
All keys, transaction signatures, and block signatures use Ed25519 elliptic-curve cryptography via tweetnacl. Keys are 32-byte public / 64-byte secret.
All hashing — transaction hashes, block hashes, state roots — uses SHA-256 from the Web Crypto API for performance and security.
End-to-end encrypted messaging in Palavia uses tweetnacl-js box. Messages are encrypted with the recipient's public key and can only be decrypted by the recipient.
All addresses, public keys, signatures, and hashes are encoded in Base58 (Bitcoin-style) for human-readability and URL safety.
How Anonyma ensures every transaction is valid and the chain is tamper-proof.
Every transaction includes an Ed25519 signature. The chain verifies that the signature matches the sender's public key before accepting the transaction. Invalid signatures are rejected immediately.
Transaction hashes are recomputed and compared. A transaction with a modified field will produce a different SHA-256 hash, causing signature verification to fail — making transactions tamper-proof.
Each block references the previous block's hash, forming an immutable linked chain. Altering any historical block would invalidate all subsequent block hashes.
Nonces are sequential per account, preventing replay attacks. A transaction with a duplicate or out-of-order nonce is rejected by the mempool and block validator.
Before inclusion in a block, every transaction is validated against the sender's current balance. The amount + fee must not exceed available balance. Overdrafts are impossible.
Each block includes a state root — a hash of the entire account state after applying all transactions. This allows any node to verify state consistency independently.
import nacl from 'tweetnacl';
import bs58 from 'bs58';
function verifyTransaction(tx) {
// Decode the signature and public key from Base58
const signature = bs58.decode(tx.signature);
const publicKey = bs58.decode(tx.from); // address = public key
const hashBytes = bs58.decode(tx.hash);
// Verify Ed25519 signature
const valid = nacl.sign.detached.verify(hashBytes, signature, publicKey);
return valid; // true if signature is authentic
}
// Verify a message signature
function verifyMessage(message, signature, address) {
const msgBytes = new TextEncoder().encode(message);
const sigBytes = bs58.decode(signature);
const pubKey = bs58.decode(address);
return nacl.sign.detached.verify(msgBytes, sigBytes, pubKey);
}
| Code | Constant | Description |
|---|---|---|
-32700 | PARSE_ERROR | Invalid JSON received |
-32600 | INVALID_REQUEST | Not a valid JSON-RPC 2.0 request |
-32601 | METHOD_NOT_FOUND | Unknown method name |
-32602 | INVALID_PARAMS | Missing or invalid parameters |
-32603 | INTERNAL_ERROR | Internal server error |
-32000 | TX_INVALID | Transaction validation failed |
-32001 | INSUFFICIENT_BAL | Insufficient balance for tx + fee |
-32002 | INVALID_NONCE | Nonce mismatch (too low or too high) |
-32003 | INVALID_SIG | Ed25519 signature verification failed |
-32004 | DATA_TOO_LARGE | Data payload exceeds 16,384 bytes |
Complete example: detect wallet, connect, send a transaction, and listen for confirmation.
// ── 1. Detect & Connect ──
async function initAnonyma() {
// Wait for extension to inject provider
const provider = window.anonyma ?? await new Promise(r =>
window.addEventListener('anonyma#initialized', () => r(window.anonyma), { once: true })
);
// Check wallet state
const state = await provider.getState();
if (!state.hasWallet) {
alert('Please create a wallet in the Anonyma extension');
return;
}
// Request connection
const { address } = await provider.connect();
console.log('Connected:', address);
return provider;
}
// ── 2. Send Transaction ──
async function sendPayment(provider, recipient, amountANM) {
const amountDrops = amountANM * 1_000_000;
const { hash } = await provider.signAndSendTransaction({
to: recipient,
amount: amountDrops,
});
console.log('TX submitted:', hash);
return hash;
}
// ── 3. Listen for Confirmation via WebSocket ──
function waitForConfirmation(txHash, address) {
return new Promise((resolve) => {
const ws = new WebSocket('wss://rpc.anonyma.network');
ws.addEventListener('open', () => {
ws.send(JSON.stringify({
action: 'subscribe',
channel: `tx:${address}`,
}));
});
ws.addEventListener('message', (e) => {
const msg = JSON.parse(e.data);
if (msg.data?.transaction?.hash === txHash) {
console.log('Confirmed in block', msg.data.blockIndex);
ws.close();
resolve(msg.data);
}
});
});
}
// ── 4. Usage ──
const provider = await initAnonyma();
const hash = await sendPayment(provider, 'RecipientAddress...', 10);
const confirmation = await waitForConfirmation(hash, 'SenderAddress...');
console.log('All done!', confirmation);
Install the wallet extension, connect to the RPC, and ship your first dApp.