Environment and service reference
Every environment variable, RPC method, timing constant, Redis key, row field, file and admin endpoint the Monero addon uses — the flat reference behind the guides.
Everything on this page is read out of the shipped source. Where a number is given it is a compiled-in constant, not a setting, unless it names the setting that changes it.
Identity
| Property | Value |
|---|---|
| Chain code | XMR |
| Currency | XMR |
| Decimals | 12 — amounts are handled in piconero and divided by 10^12 |
| Product ID | 54578959 |
| Licence file | lic/54578959.lic |
| Explorer | https://xmrchain.net |
| Log tag | XMR |
| Master wallet filename | master_wallet |
| User wallet filename | The ECO wallet's UUID |
Environment variables
None of the XMR_* keys is in .env.example; add them to the project root
.env yourself. The scanner keys further down are a different case:
ECOSYSTEM_BACKGROUND_SCAN and ECOSYSTEM_SCAN_PASSIVE_SKIP_CHAINS are
described in the commented scanner section of .env.example, and
ECOSYSTEM_SCAN_RATE_XMR is not. After any change restart every backend
process — with the default PM2 file, pm2 restart backend cron. The XMR_*
keys are read once, when the Monero service is first constructed in each
process.
Every wallet-rpc session also needs the platform's Redis (the REDIS_*
settings of the core): the session lock lives there, and no session starts
without it.
Ecosystem's background deposit scanner also affects this chain:
Keys read by nothing
| Key | Reality |
|---|---|
XMR_WALLET_PASSWORD |
Wallet files are created and opened with an empty password regardless of this value. The diagnostics warn when it is set |
XMR_WALLET_USER |
Listed in the admin diagnostics, but read by no runtime code |
RPC methods used
Useful when you are writing a restricted RPC policy or reading a proxy log.
monero-wallet-rpc
| Method | Used for |
|---|---|
get_version |
The startup health gate, and the probe a session sends after a timed-out call before it releases the lock. The only method sent outside a session |
set_daemon |
Re-pointing the open wallet at the daemon after a refresh fails with a daemon error, or when the daemon pool moves |
create_wallet |
New user wallet and the master wallet |
open_wallet / close_wallet |
Every session; one wallet at a time |
refresh |
Syncing before any read — without it, results are the on-disk cache |
get_address |
Every session: proving the opened file is the wallet on record, again before every read and spend, and after every relay. Also reads a new wallet's address |
query_key |
Reading the mnemonic at creation, for encrypted storage — asked twice, before and after the file is re-opened |
get_balance |
Total and unlocked balance |
get_transfers |
Deposit detection and the master wallet's transaction history. Deposit checks ask for in (the live monitor adds pending), never pool, and set no height filter, so they receive the wallet's whole incoming history and nothing that is still in the mempool |
get_transfer_by_txid |
Proving the transfer being credited |
transfer |
Always with do_not_relay: true: the fee probe, and building the real withdrawal with get_tx_metadata: true |
relay_tx |
Broadcasting the transaction transfer built — one attempt, never retried |
The addon never calls create_address, incoming_transfers or any
integrated-address method.
monerod
| Method | Used for |
|---|---|
get_info |
Height, synchronized, nettype |
get_fee_estimate |
The network fee estimate — when the customer submits a withdrawal, before anything is debited; again inside the withdrawal handler; and when pool backing plans an XMR send |
/get_transactions |
After an ambiguous relay, of a withdrawal or of a pool-backing send: whether the daemon has the hash, in its pool or in a block. A plain endpoint, not JSON-RPC, on the host of the daemon that last answered |
get_info and get_fee_estimate go to the daemon list when one is set
(XMR_<NETWORK>_RPC, failing over between its entries) and to
XMR_DAEMON_RPC_URL otherwise. /get_transactions goes to the one daemon that
last answered either of those calls — which is also the daemon a set_daemon
repair points a wallet at.
Timing and limits
Session lock
One lock per wallet RPC, shared by every backend process.
| Constant | Value |
|---|---|
| Lock expiry | 30 seconds |
| Renewal | every 10 seconds |
| No successful renewal for | 25 seconds, and the session is lost and sends nothing more |
| Hard cap on one session | 30 minutes |
| Retry while the lock is busy | every 250 ms, plus up to 250 ms of jitter |
| Waiting out a timed-out call | get_version probes of up to 60 seconds, 1 second apart, at most 15 minutes |
| Live-monitor marker | 30 seconds, refreshed while the check runs |
How long each kind of session waits for the lock:
| Session | Waits at most |
|---|---|
| Withdrawal | 90 seconds, including the wait in this process's own queue |
| Pool-backing send | 20 minutes |
| Live deposit check | 2 minutes |
| Background scan of one wallet | 60 seconds |
| Wallet creation | 2 minutes per attempt. A customer with no XMR wallet yet gets two attempts |
| Admin balance and history reads, and pool backing's balance reads | 2 minutes |
| Dormant-wallet refresh | Does not wait |
| Following a daemon move outside a session | Does not wait |
Only the withdrawal's figure includes the wait in the process's own queue. Each process runs one Monero session at a time, and every other kind of session first waits its turn there without a limit; the figure above starts when it reaches the front. A wallet creation queued behind a long session in the web process can therefore outlast the reverse proxy — 300 seconds with the documented Nginx configuration — and reach the deposit page as a 504 instead of the 500 that two refused attempts produce.
Deposits
| Constant | Value |
|---|---|
| Confirmations required to credit | 6 |
| Per-wallet check interval | 5 seconds |
| Wallets checked per batch | 3, sequentially |
| Loop sleep between passes | 2 seconds |
| Empty checks before stopping | 3 consecutive. "Empty" means the in and pending lists are both empty, so only a wallet that has never received anything can stop this way |
| Minimum monitoring before an early stop | 10 minutes |
| Absolute monitoring cap | 90 minutes |
| Idle retry cap | 120 checks — only applies with nothing in flight. This is what ends the monitor of a wallet with any earlier deposit |
| First sight of a deposit | Its first confirmation — transfers still in the mempool are not requested |
| Credited-transaction dedup cache | 30 minutes |
| Scanner active list for an XMR address | A customer's: 72 hours after the last deposit-page or wallet visit. The pool-backing treasury's: 72 hours after a settlement into it was dispatched or last checked. The Instant Convert house's, where that addon is installed: permanently, re-registered by the addon |
Background refresh
| Constant | Value |
|---|---|
| Runs in | The cron process, or the single process of an inline deployment |
| Exists from | The first use of the Monero service in that process, and only if the chain is active then. In the cron process: an XMR withdrawal it runs (a PENDING row picked up by the sweep at boot, the 5-minute watchdog or the 30-minute job that runs the same pass), the scanner's first sweep of an XMR address on its active list (none with ECOSYSTEM_BACKGROUND_SCAN="false"), or pool backing reading or sending XMR. Until one of those happens after a restart, no dormant refresh runs at all; if the chain was inactive when it happened, none runs until the next restart |
| Considered stale after | 6 hours |
| Refresh budget per wallet | 10 minutes |
| Spacing | 3 minutes from the end of one refresh to the start of the next. The first waits 3 minutes from when the process's monitoring loop first reaches the refresh — not from when the process starts |
| Throughput | At most 20 wallets an hour, about 120 in the 6 hours a refresh counts as fresh |
| What counts as refreshed | Kept in memory by the process that ran the refresh; empty after a restart, and not shared between the web and cron processes |
| Runs when | That process's loop is idle, there is no daemon back-off, and no wallet is live-monitored in any process |
| Lock | Tried once, never waited for; 30 seconds before the next try when busy |
| Priority | Low — always behind deposits and withdrawals |
| Eligible wallets | ECO wallets with a positive balance and a recorded XMR address |
Withdrawals
| Constant | Value |
|---|---|
| Wait for the wallet RPC | 90 seconds, then requeued — never failed for being busy |
| The process's withdrawal queue | Held for the whole session, not only the wait: no withdrawal of any chain starts in that process until the XMR one has finished |
| Busy spell | After one withdrawal waits 90 seconds in vain, the others only try, for 90 seconds |
| Watchdog retry of a requeued row | Every 5 minutes, for rows older than 3 minutes |
| Maximum wait for locked outputs | 2 hours from row creation |
| Persistent-refusal bound | 24 hours — XMR_WITHDRAWAL_REFUSAL_FAIL_AFTER_HOURS |
| Gap that restarts the refusal clock | 6 hours |
| Wallet open budget, refreshed by this process in the last 6 hours | 2 minutes |
| Wallet open budget, otherwise | 15 minutes |
| A refresh that outlasts its budget | The withdrawal is FAILED and refunded ("Wallet RPC is not responding…"), not requeued |
transfer call timeout |
120 seconds, for the fee probe and for the build |
relay_tx |
1 attempt, 120 seconds — never retried |
| Daemon check after an ambiguous relay | /get_transactions, 15 seconds |
Stale PROCESSING threshold in the queue |
5 minutes from the row's creation, after which a row with no trxId that is not in the sweeping process's own queue is flagged for manual review rather than reverted. The sweep runs where the scheduler runs — the cron process by default — so a withdrawal the web process is still working on is flagged too |
| Longest a single attempt can last | About three quarters of an hour at the very most: a 90-second wait, a session capped at 30 minutes, and up to 15 minutes waiting out a timed-out call |
RPC transport
| Constant | Value |
|---|---|
| Default call timeout | 30 seconds |
| Daemon calls | 3 attempts, one second apart |
| Wallet calls | Retried only when the connection was refused; a timeout is never re-sent |
| Refresh default budget | 60 seconds |
| Balance read refresh budget | 120 seconds |
| Daemon back-off | 30 s, doubling, capped at 5 minutes |
| Licence | The service's own gate reads the file each time; the gate on the chain's routes caches its answer for 5 minutes |
Redis keys
| Key | Holds |
|---|---|
xmr:wallet-rpc:<host:port>:session |
The session holding the wallet RPC: <session number>:<hostname>:<pid>:<id>. Empty when free |
xmr:wallet-rpc:<host:port>:fence |
The last session number handed out — one per attempt to take the lock, busy attempts included |
xmr:monitoring:<walletId> |
A live monitor is checking this wallet; the background scanner skips it |
xmr:monitoring-active |
Some wallet is being live-monitored somewhere; no dormant refresh starts |
<host:port> is taken from XMR_WALLET_RPC_URL.
Transaction row fields
What a Monero withdrawal writes to its own transaction row beyond the status:
| Field | Written when | Meaning |
|---|---|---|
txHashPending |
Just before relay_tx |
The built transaction's hash: the relay was claimed. With no trxId, the outcome was never recorded — check the hash before doing anything. It is cleared only when a claim is given back, so it stays on a completed row |
trxId |
After a relay that succeeded | The hash relay_tx answered with; the row is COMPLETED. The same as txHashPending — if the two ever differ, the log says "answered tx …, but the built transaction was …" |
fee |
When the request is accepted | The platform fee debited on top of the amount. The fee record (adminProfit and the PLATFORM_FEE credit) is written only by the handler after its own COMPLETED write; a row completed by hand has none |
metadata.xmrRelayClaim |
With txHashPending |
txHash, the session number and the time. Stays even if the claim is given back |
metadata.xmrPersistentRefusal |
On each refusal because the wallet row has no recorded address or the file opens as another wallet, raised before anything was built | reason (no_recorded_address or file_is_another_wallet), firstAt, lastAt, count, and failedAt once the row was failed for it |
description |
Rewritten at every stage | Held: "Funds are locked (x/y XMR unlocked). Waiting to process." Completed: "Withdrawal of … XMR to … completed. Tx: …". TIMEOUT: the manual-review text with the built hash. Failed and refunded: always "Refund of" and the row's amount to eighteen decimals, whatever the cause — the refund overwrites the failure reason, which survives only in the log (Failed to process transaction <id>: …) and in the customer's failure email |
A failed withdrawal's refund is a second row: type REFUND, status COMPLETED,
referenceId the withdrawal's id, amount the withdrawal's metadata.totalAmount
(amount plus fee). The customer's own transaction history does not list it.
Ports
Only the P2P port needs to face the internet. Both RPC listeners should stay on loopback — the backend runs on the same host.
| Port | Service | Exposure |
|---|---|---|
| 18080 | monerod P2P |
Public |
| 18081 | monerod RPC |
Loopback |
| 18083 | monero-wallet-rpc |
Loopback |
The RPC ports are conventions, not requirements. Change them and change the two
URLs in .env to match — nothing infers a port. The wallet RPC's port is part of
the session lock's name, so every backend process must see the same URL.
Files that matter
| Path | Contents | Loss means |
|---|---|---|
<--wallet-dir>/<uuid> and .keys |
One customer's Monero private keys | Their balance is unspendable. Not recoverable from the master wallet |
<--wallet-dir>/master_wallet and .keys |
The platform's XMR wallet | Accumulated withdrawal profit is unspendable |
lic/54578959.lic |
Machine-bound licence | The chain cannot be enabled |
monerod data-dir |
The blockchain | A full resync, not a loss of funds |
The mnemonics are also held, encrypted with the Ecosystem vault key, in
wallet_data. That copy is only usable with the key material in .env — see
Master wallets and the vault.
Admin endpoints
Both reads are admin sessions: up to two minutes waiting for the wallet RPC,
then a refresh of up to 120 seconds. Two things follow.
- The one-minute cache is written only by a read that succeeds. While a read fails, or takes longer than a minute, every load of the list starts another session, and the five-second wait does not cancel the one already running — they queue behind each other. Reloading the list while the wallet RPC is slow adds to its queue rather than getting an answer sooner.
- The detail endpoint has no cache at all. Each call is a fresh session, and
the response does not come back until it has finished or failed. A failed read
is not an error on the screen: the balance last stored is returned. Nor is a
read of exactly 0 stored, so an emptied
master_walletkeeps showing its last non-zero balance on the detail view until the list, which does store a 0, is loaded.