Skip to content

Fix build, harden payment processing, and make deployment fork-agnostic - #1

Open
jzbz wants to merge 8 commits into
decred:masterfrom
jzbz:master
Open

Fix build, harden payment processing, and make deployment fork-agnostic#1
jzbz wants to merge 8 commits into
decred:masterfrom
jzbz:master

Conversation

@jzbz

@jzbz jzbz commented Jun 30, 2026

Copy link
Copy Markdown
Member

Summary

Brings the plugin to a working, deployable state against BTCPay Server >= 2.1.0, verified end-to-end on a fresh server running a Decred-only (no-Bitcoin) deployment. Fixes a build break, two payment-correctness bugs, a CSRF gap and a send-authorization gap, hardens the dcrwallet RPC integration, and removes fork-specific references from the deployment files.

Security

  • Validate antiforgery tokens on the settings and wallet-send POST endpoints. BTCPay has no global antiforgery filter, so the rendered tokens were never checked; the send endpoint was CSRF-reachable under cookie auth.
  • Restrict wallet send to server admins. The dcrwallet instance is shared by the whole server, so store-settings permission on any store must not be able to spend it.
  • Optional TLS pinning of dcrwallet's rpc.cert via BTCPAY_DCR_RPC_CERT (fails closed if the file cannot be loaded); accept-any remains the default for the private compose network.

Payment correctness

  • Sum transaction outputs paying the same address in all detection paths. Wallet RPCs return one entry per output and deduplication was by txid, so a payment split across outputs of one transaction was undercounted and the invoice never settled.
  • Use dcrwallet's syncstatus RPC for the synced flag instead of blocks > 0, which reported synced during initial SPV sync.
  • Send from the store's configured account via sendfrom; sendtoaddress only spends the default account while receives can land in a custom one, and the balance check was wallet-wide.
  • Honor the store speed policy for the settlement confirmation threshold (mirrors the core Bitcoin listener mapping) instead of hardcoding 1 confirmation.
  • Record payment time from the transaction, not detection time.

Plugin / runtime

  • Fix build error: missing using System.Collections.Concurrent in DecredRpcProvider (the project does not enable ImplicitUsings).
  • Fix account selection, harden the JSON-RPC client (non-JSON transport errors, result null-guard, 15s timeout so an unreachable wallet cannot stall the poll loop), drop dead code.
  • Use the Kraken DCR/USD rate and fix the Decred logo.
  • Rootpath-safe URLs: RedirectToAction and anchor tag helpers instead of hardcoded /stores/... paths, which broke under BTCPAY_ROOTPATH.

Deployment & CI

  • Parameterize the wallet image in the compose fragment: ${BTCPAY_DCR_IMAGE:-ghcr.io/decred/decred:2.1.5} - the default is the Decred org image, deployers can override without editing tracked files.
  • docker.yml: build/tag the image from a DECRED_VERSION env instead of the plugin's git tag, so cutting a vX.Y.Z plugin release no longer tries to fetch a non-existent Decred release.
  • Run dcrwallet with --gaplimit=200: invoice addresses are generated past the gap limit, so restores from seed with the default limit would miss funds.
  • README: document that BTCPAY_DCR_WALLET_PASSPHRASE must be written to $BTCPAY_BASE_DIRECTORY/.env (btcpay-setup.sh does not persist custom variables; without this dcrwallet crash-loops on restart).

Testing

Deployed on a fresh VPS in Decred-only mode (BTCPAYGEN_CRYPTO1=none, plugin added via BTCPAYGEN_ADDITIONAL_FRAGMENTS):

  • dcrwallet syncs in SPV mode to the mainnet chain tip
  • BTCPay loads the plugin and connects to the wallet (DCR wallet availability changed to True)
  • Store settings page reports synced; invoices render a DCR address at the Kraken-derived rate

jzbz added 8 commits June 29, 2026 20:05
- Replace the Decred logo with the official mark
- Fix CryptoImagePath: the embedded SVG is served at /decred.svg, not
  /Resources/img/decred.svg, so the checkout icon was 404ing
- Switch the default rate source from poloniex DCR/BTC to kraken DCR/USD
- Use the store's configured account for getnewaddress instead of always
  using "default"; the AccountName setting was previously ignored
- Make the DecredRpcProvider summary cache thread-safe (ConcurrentDictionary)
- Return a clear error on non-JSON wallet responses instead of an opaque
  JObject.Parse failure
- Remove the unauthenticated, unused daemon callback controller
- Render the payment-data transaction link via the registered link provider
  rather than a hardcoded explorer URL
- Collapse the redundant poll-cycle event publish
- Delete unused DecredMoney, DecredPaymentViewModel, and the SyncStatus/
  EstimateSmartFee models
- decred.yml, README: pull ${BTCPAY_DCR_IMAGE:-ghcr.io/decred/decred:2.1.5};
  deployers override the image via BTCPAY_DCR_IMAGE, default is the upstream
  decred org image so the repo carries no fork-specific reference
- docker.yml: drive the build/tag from a DECRED_VERSION env (2.1.5) instead
  of the plugin's git tag, so cutting a vX.Y.Z plugin release no longer tries
  to fetch a non-existent Decred release
…EADME

- README: write BTCPAY_DCR_WALLET_PASSPHRASE to $BTCPAY_BASE_DIRECTORY/.env
  rather than exporting it, so dcrwallet can unlock on every (re)start;
  btcpay-setup.sh does not persist custom variables
- README: credit the Decred project (https://github.com/decred)
- LICENSE: copyright to The Decred developers
…scoping

- Validate antiforgery tokens on the settings and wallet-send POST actions;
  BTCPay has no global antiforgery filter, so the rendered tokens were never
  checked - the send endpoint was CSRF-reachable under cookie auth
- Sum transaction outputs paying the same address in all three payment
  detection paths; one entry per output previously deduped by txid, so a
  payment split across outputs of one tx was undercounted and the invoice
  never settled
- Use dcrwallet's syncstatus RPC for the synced flag instead of blocks > 0,
  which reported synced during initial SPV sync; keep the old check as a
  fallback for wallets without syncstatus
- Restrict wallet send to server admins: the dcrwallet instance is shared by
  the whole server, so store-settings permission on any store must not spend
  it; scope the balance shown and validated to the store's configured account
  and send via sendfrom, since sendtoaddress only spends the default account
  while receives can land in a custom one
- Use RedirectToAction and anchor tag helpers instead of hardcoded
  root-relative /stores/... URLs, which broke under BTCPAY_ROOTPATH
- Set a 15s HttpClient timeout so an unreachable wallet endpoint cannot
  stall the polling loop for the default 100s
- Run dcrwallet with --gaplimit=200 in the compose fragment and document
  the seed-restore implications; invoice addresses are generated past the
  gap limit, so restores with the default limit would miss funds
- Support optional TLS pinning of dcrwallet's rpc.cert via
  BTCPAY_DCR_RPC_CERT, failing closed if the file cannot be loaded;
  accept-any stays the default for the private compose network
- Fall back to the store speed policy for the settlement threshold
  (mirroring the core Bitcoin listener) instead of hardcoding 1
  confirmation; the settings UI already claimed this behavior
- Record payment Created from the transaction time, not detection time
- Drop the redundant Task.Run around address reservation, null-guard the
  JSON-RPC result field, and collapse the block/tx poll distinction the
  listener never differentiated
@jzbz jzbz changed the title DCR BTCPay fixes Fix build, harden payment processing, and make deployment fork-agnostic Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant