Base URL: http://localhost:3000 (configurable via PORT env var).
All request/response bodies are JSON. All token state is read from and written to the Solana blockchain — the backend is stateless.
Liveness check. Always returns 200.
Response:
{
"status": "ok",
"timestamp": "2026-03-12T21:00:00.000Z"
}Readiness check. Verifies Solana RPC connectivity and returns the configured mint.
Response (200):
{
"status": "ready",
"solana": { "solana-core": "2.2.1", "feature-set": 1234567890 },
"mint": "7NDka...",
"timestamp": "2026-03-12T21:00:00.000Z"
}Response (503): RPC unreachable.
{
"status": "not ready",
"error": "fetch failed"
}Mint tokens to a recipient. Creates the ATA if it doesn't exist.
Request:
{
"recipient": "<wallet-public-key>",
"amount": "1000000"
}recipient(required): Wallet public key. The ATA is derived automatically.amount(required): Raw units as a string (to avoid precision issues with large numbers).
Response (200):
{
"txSignature": "5DJwAPNtmeSCsUBp9AHzD81C9HkKWAohmpNRfr3CACEDSzF5..."
}Errors:
400: Missingrecipientoramount, or invalid public key.500: Transaction failed (e.g., signer is not the mint authority).
Burn tokens from the backend authority's ATA.
Request:
{
"amount": "500000"
}Response (200):
{
"txSignature": "3iTzsejFP1mzYyCNCkLvuWenz43TNxmVNiBPN8u4yBSR5..."
}Fetch total supply from the blockchain.
Response:
{
"raw": "10000000",
"uiAmount": 10.0,
"uiAmountString": "10.000000",
"decimals": 6
}Precision note:
uiAmountis a JavaScriptnumberand loses precision for large supplies (> 2^53 raw units). UseuiAmountStringfor display.
Fetch token balance for a wallet.
Path params:
wallet: Wallet public key.
Response:
{
"wallet": "Dkvvh...",
"ata": "aw1cQ...",
"raw": "5000000",
"uiAmount": 5.0,
"uiAmountString": "5.000000",
"exists": true
}If the ATA doesn't exist, exists is false and balances are 0.
On-chain mint status.
Response:
{
"mint": "7NDka...",
"supply": {
"raw": "10000000",
"uiAmount": 10.0,
"uiAmountString": "10.000000",
"decimals": 6
},
"mintAuthority": "Gxyz...",
"freezeAuthority": "Gxyz..."
}Authorities are null if revoked.
Freeze a token account.
Request:
{
"tokenAccount": "<token-account-public-key>"
}Response:
{
"txSignature": "2Bjmw..."
}Thaw a frozen token account.
Request:
{
"tokenAccount": "<token-account-public-key>"
}Change an on-chain authority.
Request:
{
"type": "freeze",
"newAuthority": "<public-key-or-none>"
}type(required): One ofmint,freeze,metadata,metadata-pointer,pause,permanent-delegate,close-mint,interest-rate.newAuthority: Public key string, or"none"to revoke.
Fetch recent transaction signatures involving the mint.
Query params:
limit(optional, default 20, max 1000)
Response:
[
{
"signature": "abc123...",
"slot": 345678901,
"err": null,
"blockTime": "2026-03-12T21:00:00.000Z"
}
]These endpoints require TRANSFER_HOOK_PROGRAM_ID to be set. They manage the on-chain blacklist via the transfer hook program.
Add a wallet to the blacklist.
Request:
{
"wallet": "<wallet-public-key>",
"reason": "OFAC match"
}wallet(required): The wallet to blacklist.reason(optional): Stored on-chain in the BlacklistEntry PDA for audit compliance, and emitted in theWalletBlacklistedevent.
Response:
{
"txSignature": "4kLmN..."
}Remove a wallet from the blacklist.
Path params:
wallet: Wallet public key.
Query params:
reason(optional): Reason for removal.
Response:
{
"txSignature": "7pQrS..."
}Check whether a wallet is blacklisted.
Response:
{
"wallet": "Dkvvh...",
"pda": "BLpda...",
"blocked": false,
"reason": "OFAC match"
}The reason field is present when the entry exists and contains the on-chain reason string from the BlacklistEntry PDA.
Register HTTP endpoints to receive real-time notifications when the event listener detects new on-chain transactions involving the mint.
| Event | Description |
|---|---|
transaction.confirmed |
Successful transaction involving the mint |
transaction.failed |
Failed transaction involving the mint |
* |
Wildcard — all events |
Register a webhook.
Request:
{
"url": "https://your-server.com/webhook",
"events": ["transaction.confirmed"],
"secret": "optional-shared-secret"
}If secret is provided, it's sent as x-webhook-secret header on every delivery.
Response (201):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://your-server.com/webhook",
"events": ["transaction.confirmed"],
"active": true,
"createdAt": "2026-03-12T21:00:00.000Z"
}List all registered webhooks.
Response: Array of webhook objects.
Get a specific webhook.
Get delivery history for a webhook.
Response:
[
{
"webhookId": "550e8400...",
"event": "transaction.confirmed",
"payload": { "signature": "abc...", "slot": 123, "mint": "7NDka..." },
"attempt": 1,
"status": "delivered",
"httpStatus": 200,
"timestamp": "2026-03-12T21:00:05.000Z"
}
]Remove a webhook.
Response:
{
"deleted": true
}Each delivery is an HTTP POST to the registered URL:
{
"event": "transaction.confirmed",
"payload": {
"signature": "abc123...",
"slot": 345678901,
"err": null,
"blockTime": 1710280800,
"mint": "7NDka..."
},
"timestamp": "2026-03-12T21:00:05.000Z"
}Deliveries retry with exponential backoff: WEBHOOK_RETRY_BASE_MS * 2^(attempt-1), up to WEBHOOK_MAX_RETRIES attempts.
| Variable | Required | Default | Description |
|---|---|---|---|
SOLANA_RPC_URL |
Yes | — | Solana RPC endpoint |
SOLANA_MINT_ADDRESS |
Yes | — | On-chain mint public key |
TOKEN_PROGRAM |
No | spl-token-2022 |
spl-token or spl-token-2022 |
SOLANA_KEYPAIR_PATH |
One of PATH/BASE64 | — | Filesystem path to keypair JSON |
SOLANA_KEYPAIR_BASE64 |
One of PATH/BASE64 | — | Base64-encoded secret key |
TRANSFER_HOOK_PROGRAM_ID |
No | — | Enables compliance endpoints |
BLACKLIST_ADMIN_KEYPAIR_PATH |
No | — | Blacklist admin (defaults to main authority) |
PORT |
No | 3000 |
HTTP listen port |
LOG_LEVEL |
No | info |
debug / info / warn / error / silent |
EVENT_POLL_INTERVAL_MS |
No | 5000 |
Event listener poll frequency |
WEBHOOK_MAX_RETRIES |
No | 5 |
Max delivery retries |
WEBHOOK_RETRY_BASE_MS |
No | 1000 |
Base delay for exponential backoff |
All error responses follow:
{
"error": "Human-readable error message"
}Common HTTP status codes:
400— Bad request (missing/invalid parameters).404— Resource not found (webhook ID).500— Internal error (transaction failure, RPC error).503— Service unavailable (readiness check failed).