How to run this toolkit safely in production. Written for the merchant / node-operator who will actually deploy it. If you follow this document, you can accept DigiDollar and DGB with a blast radius you understand and control.
New to any of this? Start with the README and the operator playbook. This page assumes you have read the "five DigiDollar facts" there — the security model follows directly from them.
DigiDollar is DigiByte's native, exact-USD instrument: 1 DD = $1.00 by
definition, denominated in integer US cents on-chain. DGB is the DigiByte coin
that pays the network fee. This toolkit lets you accept both non-custodially, from
a DigiByte Core node you run — no hosted gateway, no third party ever holding a
cent. That sovereignty is the whole point, and it is also the reason security is
your job. This guide is how you do that job well.
There is one fact you cannot design around, only mitigate:
DigiByte Core v1 has no working watch-only model for DigiDollar.
importdigidollaraddressis a no-op, and watch-only DigiDollar outputs are excluded from balance by design. The DigiDollar value lives in anOP_RETURN, not in the output's satoshi value — so the only way to detect a DigiDollar payment is to run the DigiDollar RPCs (listdigidollarunspent/listdigidollartxs) against a wallet that holds the spending keys.
Therefore: the node that watches for DigiDollar payments is a hot wallet. It has private keys, and it is online. You cannot make it an air-gapped, watch-only deposit box the way you can for BTC/LTC or even for DGB coin (which does support watch-only). This is a protocol constraint of Core v1, not a choice this toolkit made — see operator playbook §0 and the protocol docs in the DigiByte Core repo (DIGIDOLLAR_ARCHITECTURE.md, DIGIDOLLAR_WALLET_INTEGRATION.md).
Everything else in this document is about shrinking that blast radius: keep the hot balance small, sweep the rest to cold storage, lock the node down so nothing but your own scripts can reach it, and make sure a stolen secret can't redirect customer funds. If someone fully compromises the hot node, the most they can take is what is currently sitting in the hot wallet — which is why "keep it low, sweep often" is the first commandment.
The hot node is your crown jewel. Treat it like one.
# One-time: encrypt the hot DigiDollar wallet with a strong passphrase.
digibyte-cli -rpcwallet=dd_hot encryptwallet "<long random passphrase>"The wallet must be unlocked only for the two brief operations that need private keys — minting fresh receive addresses, and sweeping to cold — and locked the rest of the time:
# Unlock for 30 seconds, do the thing, then lock immediately — don't wait for timeout.
digibyte-cli -rpcwallet=dd_hot walletpassphrase "<passphrase>" 30
digibyte-cli -rpcwallet=dd_hot getdigidollaraddress "pool-refill"
digibyte-cli -rpcwallet=dd_hot walletlock- Never store the passphrase in the same place as the wallet file. Never bake it
into
digibyte.conf, a script, or a shell history. Prefer a secrets manager or an interactive prompt at unlock time. - Detection (
listdigidollarunspent,listdigidollartxs,getdigidollarbalance) does not require the wallet to be unlocked — only address generation and spending do. Keep the wallet locked during normal watching.
The node's JSON-RPC gives full control of the hot wallet. It must never be
reachable from the internet. In digibyte.conf:
server=1
digidollar=1
txindex=1 # MANDATORY — DigiDollar refuses to start without it
rpcbind=127.0.0.1 # localhost ONLY
rpcallowip=127.0.0.1 # accept RPC only from the loopback interface
rpcauth=... # strong generated credentials — see below- Generate
rpcauthwith the officialrpcauth.pytool; use a long random password. Do not userpcuser/rpcpasswordin cleartext if you can avoid it. - Do not set
rpcbindto0.0.0.0or a LAN address "just to test." If a remote process must reach the node, tunnel it over SSH — do not open the RPC port. - Only your own toolkit scripts (
walletnotify.sh,blocknotify.sh,refill-dd-pool.mjs, or the pull-modelwatcher.mjs) should ever call the RPC, and they run on the same box over127.0.0.1.
- Least-privileged OS user. Run
digibytedas a dedicated non-root user that owns only the datadir and wallet. Don't run the node as root. - Full-disk encryption on the node (LUKS / FileVault / equivalent). The wallet file is a target even at rest.
- SSH: key-only, no passwords.
PasswordAuthentication no,PermitRootLogin no, and a firewall that exposes nothing but the SSH port (and, if you must, only from known IPs). The RPC, ZMQ, and P2P ports do not need to be publicly reachable. - Keep the OS and DigiByte Core patched. Track the DigiByte Core repo for security releases.
The single most effective mitigation for a hot wallet is to not keep much in it. Received DigiDollar is spendable after 1 confirmation, so you can sweep promptly.
Policy: on a schedule (e.g. hourly) or a threshold (e.g. whenever the hot
balance exceeds $500), move confirmed DigiDollar and DGB out of dd_hot to an
offline / cold descriptor wallet, and record each sweep (BlockIndex logs these to
crypto_sweeps). The watcher and walletnotify references leave the sweep as a
stub wired to your policy — see
watcher-reference §Responsibilities and
operator playbook §6.
# Sweep example (unlock briefly, send to your cold DD address, re-lock):
digibyte-cli -rpcwallet=dd_hot walletpassphrase "<passphrase>" 60
digibyte-cli -rpcwallet=dd_hot senddigidollar "<COLD_DD_ADDRESS>" <amount_cents>
digibyte-cli -rpcwallet=dd_hot walletlock- Sweep only confirmed,
safe:trueDigiDollar (listdigidollarunspentreportsconfirmationsandsafe). Don't sweep 0-conf funds. - The cold destination should be an offline descriptor wallet (or a hardware wallet's DD address) whose keys never touch the hot node.
A hot wallet you can't recover is its own disaster. Descriptor wallets have a clean backup/restore path:
# Back up — export the wallet's descriptors (store the output somewhere safe & encrypted).
digibyte-cli -rpcwallet=dd_hot listdescriptors # public
digibyte-cli -rpcwallet=dd_hot listdescriptors true # WITH private keys — treat like a seed
# Restore — recreate the wallet and import, then rescan history.
digibyte-cli createwallet "dd_hot_restore"
digibyte-cli -rpcwallet=dd_hot_restore importdescriptors '[{"desc":"<descriptor with privkey>","active":true,"timestamp":0,"range":[0,1000]}]'
digibyte-cli -rpcwallet=dd_hot_restore rescanblockchainlistdescriptors truereveals private keys — its output is seed-equivalent. Encrypt it, store it offline, and never paste it into a chat, a ticket, or a log.- After importing,
rescanblockchainre-derives your balance from chain history. Withtxindex=1(required anyway for DigiDollar) this is straightforward.
This toolkit uses two independent shared secrets between your node and your backend. Understanding why there are two is the most important security idea here.
| Secret | Direction | Guards | Config var |
|---|---|---|---|
| Ingest secret | node → backend | payment reports ({asset, network, address, txid, vout, amount, confirmations}) reach the ingest endpoint |
INGEST_SECRET |
| DD refill secret | node → backend | fresh DigiDollar receive addresses reach the address pool | DD_REFILL_SECRET |
Both are sent in the x-crypto-ingest-secret header over TLS and verified
constant-time before any side effect. See
.env.example.
Because DigiDollar has no watch-only model, receive addresses can't be derived by your web backend from a cold xpub — they must be minted by the hot node and pushed into an address pool that invoice creation draws from (refill-dd-pool.mjs).
That makes the refill endpoint a uniquely dangerous target:
Pool poisoning: anyone who can POST to your refill endpoint can inject attacker-owned DigiDollar addresses into the pool. Your next invoice hands one of those addresses to a real customer, the customer pays, and the money goes to the attacker — not you. No wallet was hacked; the payment simply never belonged to you.
Defenses:
- Use a dedicated
DD_REFILL_SECRET, distinct fromINGEST_SECRET. The refill script falls back toINGEST_SECRETif you leave it unset — do not rely on that fallback in production. Two secrets means compromising the (more widely wired) ingest path does not hand over the ability to poison the pool. - Audit the pool. Periodically verify that every unused address in the pool is
ismineon your hot node — i.e., it was really minted by you and you hold its key. Any pool address that is notismineis an injected address; freeze invoice creation and rotate the refill secret immediately.
# For each unused pool address, confirm it belongs to your wallet:
digibyte-cli -rpcwallet=dd_hot getaddressinfo "<pool_address>" | jq .ismine
# Expect true for every one. A false is a poisoned entry — investigate now.- Rotate both secrets periodically, and immediately on any suspicion. Change them
on the backend and the node's
.envtogether, then restart the node. - Never commit a secret to git (the toolkit's
.env.exampleholds placeholders only), never bake one into an image, never paste one into an issue. - Never log a secret. The scripts send it in a header, not in a logged body; keep it that way.
- Generate them with real entropy:
openssl rand -hex 32.
Your backend exposes two secret-gated endpoints (crypto-payment-ingest and
crypto-dd-pool-refill). Harden both:
- TLS everywhere. Serve ingest and refill over HTTPS only. The secret is a bearer token — never let it cross the wire in cleartext.
- IP allowlist. These endpoints are only ever called by your node. Restrict them to your node's source IP (at the load balancer, WAF, or edge-function guard). A leaked secret is far less useful to an attacker who also has to originate from your node's address.
- Timing-safe secret comparison. Compare the presented secret with a
constant-time equality check (e.g.
crypto.timingSafeEqual), never===on strings — a naive compare leaks the secret one byte at a time under a timing attack. The toolkit's receivers already do this; keep it if you re-implement. - Reject nonsense. Reject any reported
amount <= 0, unknown asset/network, or malformed address before it touches invoice logic. - The node's RPC is not one of these endpoints. It stays on
127.0.0.1and is never exposed to the internet — not behind a "temporary" tunnel, not on a LAN IP, not ever. Only ingest and refill face outward, and only over TLS + secret + IP allowlist.
These are the rules that keep a clever customer — or a replayed request — from
crediting themselves for free. They are enforced in the unit-tested
@blockindex/crypto-payments library; if you
re-implement any of it, keep every one.
- Never trust a client-reported payment. The browser saying "I paid" means nothing. Credit an order only on a payment your node verified on-chain and reported through the secret-gated ingest path. Everything the client sends is a hint, not a fact.
- Require N confirmations before crediting. Confirmation policy is USD-tiered:
< $100→ 6 confs (~90 s at 15-second blocks),$100–$10k→ 20,> $10k→ 60 (all env-overridable). Low-confirmation credit invites a reorg to reverse a payment you already fulfilled. Treat 0-conf / RBF-pending asdouble_spend_risk, never as paid. - Idempotency by
(asset, network, txid, vout). Key every credit on the exact output. A block re-scan or a retried POST will report the same payment more than once — the credit must apply exactly once no matter how many times it arrives. - Integer cents only — never floats. DigiDollar amounts are integer US cents as
BigInt. DGB/BTC/LTC amounts are integer base units (satoshis). A sats-vs-cents or float mixup is a 10⁶-scale money bug — silent rounding will over- or under-credit by a factor of a million. NoNumbermath on money, no floating-point, ever. - One fresh address per invoice, never reused. Every invoice gets a brand-new
receive address (
getdigidollaraddressfor DD; HD-derived from a cold xpub for BTC/LTC/DGB). Reuse breaks the address→invoice match, leaks customer privacy, and can misattribute a payment. Honor the wallet's gap limit when pre-minting.
Security failures often show up first as operational weirdness. Watch:
- Pool depth. If the DigiDollar address pool runs dry, invoice creation
starts failing (a
503) and you lose sales. Alert when unused pool depth drops below a threshold, and make sure the refill cron (refill-dd-pool.mjs) is actually running. A pool that grows unexpectedly, or contains non-ismineaddresses, is a poisoning signal — see §4. - Stuck / underpaid / manual-review invoices. Payments that arrive late,
short, or after slot-expiry land in
manual_reviewrather than silently crediting or silently vanishing. Watch that queue — an unattended pile there can mean a detection problem or an attempted underpayment. - Hot-wallet balance. Alert when
getdigidollarbalance(plus the DGB balance) crosses your sweep threshold, and confirm the sweep actually fired. A hot balance that keeps climbing means your sweep is broken and your blast radius is growing.
Copy this into your runbook and tick every box before you take real money.
Node & wallet
- Hot DigiDollar wallet is encrypted (
encryptwallet); passphrase not stored with the wallet. - Wallet is unlocked only briefly (address mint / sweep), then
walletlock— never left unlocked. - RPC bound to
127.0.0.1(rpcbind/rpcallowip); strongrpcauth; never internet-exposed. -
txindex=1anddigidollar=1set; node built from the pinnedfeature/digidollar-v1RC. - Node runs as a least-privileged OS user (not root); host has full-disk encryption.
- SSH is key-only (no passwords, no root login); firewall exposes nothing but SSH.
Cold storage
- Hot balance kept low; confirmed DD/DGB swept to cold on a schedule/threshold (e.g. > $500).
- Only confirmed,
safe:truefunds are swept. - Descriptor wallet backed up (
listdescriptors true, stored offline & encrypted); restore path tested.
Secrets
- Separate
INGEST_SECRETandDD_REFILL_SECRET— not the fallback-shared value. - Secrets are long/random (
openssl rand -hex 32), never committed, never logged, rotated periodically.
Endpoints
- Ingest + refill served over TLS, with an IP allowlist for the node's source IP.
- Secret comparison is constant-time;
amount <= 0and malformed input rejected.
Payment integrity
- Every credit comes from an on-chain-verified payment — never a client claim.
- N confirmations required before credit (USD-tiered 6 / 20 / 60); 0-conf/RBF = double-spend risk.
- Credits are idempotent by
(asset, network, txid, vout). - Money is integer base units (cents / sats) as
BigInt— never floats. - One fresh address per invoice; no reuse; gap limit honored.
Pool integrity & monitoring
- Every unused pool address audited as
ismineon the hot node. - Alerts on: pool depth, hot-wallet balance, and the manual-review queue.
- DigiDollar mainnet gated behind a feature flag until BIP9 activation (DigiDollar is now live on mainnet, 2026).
In this toolkit (@blockindex/crypto-payments, the guides, the demo): please
report privately and responsibly.
- Open a private report or a security advisory via the toolkit's repository, or raise it with the maintainers through DigiByte-Core Discussions.
- Please do not open a public issue with exploit details for anything that could put merchants' funds at risk before a fix is available. Give us a reasonable window to respond and patch, then coordinate disclosure.
- Include: affected file/version, a clear reproduction, and the impact (especially anything touching funds, secrets, or the pool).
In the DigiByte Core protocol or DigiDollar consensus code (the node itself, the DigiDollar RPCs, the oracle, activation): report to the DigiByte Core project, not here. Follow its SECURITY.md in the DigiByte-Core/digibyte repository. Consensus- and wallet-level issues are handled upstream under their disclosure process — the deeper protocol security model lives in DIGIDOLLAR_ARCHITECTURE.md and DIGIDOLLAR_ORACLE_ARCHITECTURE.md.
- Operator playbook — full node + integrator setup, with its own §6 hot-wallet hardening and §9 checklist.
- Push model (walletnotify reference) and its
.env.example— the two-secret configuration in practice. - Pull model (watcher reference) — the co-located polling worker, including the sweep hook.
@blockindex/crypto-payments— where the payment-integrity invariants are enforced and unit-tested.- Demo merchant — a runnable reference implementation of the ingest contract.
BlockIndex.AI runs exactly this toolkit in production to accept DigiDollar for its $350 lifetime membership at blockindex.ai/founder ("Pay with Crypto" → DigiDollar). The security practices above are the ones that keep that live checkout safe.