Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.22.6] - 2026-10-06

### Changed
- Pinned xchain-hub at v0.22.6 in the release manifest for the single-flight, read-bounded peer catch-up and price round catch-up fixes.
- Moved the generated DOGE signer's environment reads into the node config with no change in behavior.

## [0.22.5] - 2026-10-06

### Changed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "xchain-node",
"version": "0.22.5",
"version": "0.22.6",
"description": "xchain-node allows users to install, configure and run XChain platform nodes.",
"license": "AGPL-3.0-or-later",
"repository": {
Expand Down
18 changes: 17 additions & 1 deletion src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ const containersFilesDir = path.join(tmpDir, "containers_files")
// or the install --no-bootstrap flag (both read live from the env, not at load).
const BOOTSTRAP_BASE_URL = process.env.XCHAIN_NODE_BOOTSTRAP_BASE_URL || "https://sync.xchain.io/bootstraps"

// The environment view the generated DOGE signer module carries. That module
// runs inside the hub container, where this config does not exist, so the config
// owns its source text: the generated file is written from this string and never
// names the environment itself. The WIF is a non-enumerable getter so it is read
// only when a signature is made and never appears in a dump of the view.
const DOGE_SIGNER_ENV_SOURCE = [
"const signerEnv = {",
" get network() { return process.env.DOGE_NETWORK || ''; },",
" get address() { return process.env.DOGE_ADDRESS || ''; },",
" get encoderUrl() { return process.env.DOGE_ENCODER_URL || ''; },",
" get feePerKb() { return process.env.DOGE_FEE_PER_KB ? Number(process.env.DOGE_FEE_PER_KB) : undefined; }",
"};",
"Object.defineProperty(signerEnv, 'wif', { enumerable: false, get() { return process.env.DOGE_WIF || ''; } });"
].join('\n')

module.exports = {
// codemod:env-entries
// HUB_PORT is the ONE environment name that collides with a constant of
Expand Down Expand Up @@ -297,5 +312,6 @@ module.exports = {
bootstrapDir,
configDir,
containersFilesDir,
BOOTSTRAP_BASE_URL
BOOTSTRAP_BASE_URL,
DOGE_SIGNER_ENV_SOURCE
}
8 changes: 4 additions & 4 deletions src/release-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_comment": [
"Pinned component set for XChain Platform v0.22.5.",
"Pinned component set for XChain Platform v0.22.6.",
"Generated by bin/write-release-manifest.js from the ACTUAL tagged master merge commits.",
"xchain-node is the carrier and is not listed: checking out its tag IS this manifest.",
"A train tags only the repos it touches. This one moves xchain-hub.",
Expand All @@ -12,7 +12,7 @@
"repo's origin/master, and is a GPG-signed annotated tag verifying against the",
"platform release key (fingerprint 1DA7C4896F56EA22CF491EDF4361611A82F90B70)."
],
"platform_version": "0.22.5",
"platform_version": "0.22.6",
"released": "2026-10-06",
"components": {
"xchain-vm": {
Expand All @@ -28,8 +28,8 @@
"commit": "4ac2c37375d4b1e55d13ed081d723e1ef17537f2"
},
"xchain-hub": {
"tag": "v0.22.5",
"commit": "e8ea5011f781fd24ef3a868cc665d7dabe7dfe63"
"tag": "v0.22.6",
"commit": "fdd50cc292cba54990e6ae5673c11b31fd31c72a"
},
"xchain-sync": {
"tag": "v0.22.4",
Expand Down
26 changes: 11 additions & 15 deletions src/services/validator_service/signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,15 @@ require('dotenv').config({ path: path.join(__dirname, '.env') });

const { XChainSDK } = require('@dankest-llc/xchain-sdk');

const NETWORK = process.env.DOGE_NETWORK || '';
const WIF = process.env.DOGE_WIF || '';
const ADDRESS = process.env.DOGE_ADDRESS || '';
const ENCODER = process.env.DOGE_ENCODER_URL || '';
const FEE_PER_KB = process.env.DOGE_FEE_PER_KB ? Number(process.env.DOGE_FEE_PER_KB) : undefined;
${config.DOGE_SIGNER_ENV_SOURCE}

// Fail at load time: the hub treats a configured-but-broken signer as fatal,
// and a signer that cannot sign must not boot quietly.
for (const [name, value] of [['DOGE_NETWORK', NETWORK], ['DOGE_WIF', WIF], ['DOGE_ADDRESS', ADDRESS], ['DOGE_ENCODER_URL', ENCODER]]) {
for (const [name, value] of [['DOGE_NETWORK', signerEnv.network], ['DOGE_WIF', signerEnv.wif], ['DOGE_ADDRESS', signerEnv.address], ['DOGE_ENCODER_URL', signerEnv.encoderUrl]]) {
if (!value) throw new Error('doge-signer: ' + name + ' is not set in ' + path.join(__dirname, '.env'));
}

const sdk = new XChainSDK({ network: NETWORK, encoderUrl: ENCODER });
const sdk = new XChainSDK({ network: signerEnv.network, encoderUrl: signerEnv.encoderUrl });

function requireEncoder() {
return typeof sdk.requireEncoder === 'function' ? sdk.requireEncoder() : sdk['_requireEncoder']();
Expand All @@ -132,11 +128,11 @@ module.exports = {
async broadcast(payload) {
const encoder = requireEncoder();

const txParams = { data: payload, pubkey: ADDRESS, change: ADDRESS, encoding: 'P2SH' };
if (FEE_PER_KB !== undefined) txParams.feePerKb = FEE_PER_KB;
const txParams = { data: payload, pubkey: signerEnv.address, change: signerEnv.address, encoding: 'P2SH' };
if (signerEnv.feePerKb !== undefined) txParams.feePerKb = signerEnv.feePerKb;

const encoded = await encoder.createTx(txParams);
const signed = sdk.wallet.signPsbt(encoded.psbt, WIF);
const signed = sdk.wallet.signPsbt(encoded.psbt, signerEnv.wif);
await encoder.broadcastTx(signed.txHex);
if (encoded.encoding !== 'P2SH' && encoded.encoding !== 'P2WSH')
return { txid: signed.txid };
Expand All @@ -149,16 +145,16 @@ module.exports = {
// operator reconciles the stranded funding transaction against.
try {
const spendParams = {
pubkey: ADDRESS,
pubkey: signerEnv.address,
p2shHash: signed.txid,
p2shHex: signed.txHex,
data: payload,
encoding: encoded.encoding,
change: ADDRESS
change: signerEnv.address
};
if (FEE_PER_KB !== undefined) spendParams.feePerKb = FEE_PER_KB;
if (signerEnv.feePerKb !== undefined) spendParams.feePerKb = signerEnv.feePerKb;
const spendResult = await encoder.spendP2sh(spendParams);
const spendSigned = sdk.wallet.signRevealPsbt(spendResult.psbt, WIF);
const spendSigned = sdk.wallet.signRevealPsbt(spendResult.psbt, signerEnv.wif);
await encoder.broadcastTx(spendSigned.txHex);

return { txid: spendSigned.txid, phase1_txid: signed.txid };
Expand All @@ -179,7 +175,7 @@ module.exports = {
// phase-1 pipeline and balance plumbing use this; broadcast() above takes
// precedence when both are exported.
async walletSign(psbtHex) {
return sdk.wallet.signPsbt(psbtHex, WIF).txHex;
return sdk.wallet.signPsbt(psbtHex, signerEnv.wif).txHex;
}
};
`
Expand Down
Loading