Environment variables

This commit is contained in:
2026-10-20 16:24:01 -04:00
parent 4cac26d84b
commit 7734d8ae90
29 changed files with 267 additions and 38 deletions
+57 -1
View File
@@ -11,6 +11,9 @@ import { CUSTODY_POLICY, MOCK_CREDIT_ATOMIC, custodyCommandSchema, custodyMessag
type CustodyWithdrawal, type CustodyCommand } from '../shared/custody.ts';
import { NETWORK } from '../shared/testnet.ts';
import { address, CHAIN_ID } from '../server/testnet/consensus.ts';
import { operatorMarketPolicy, validateMarketPolicy, type MarketPolicy } from '../server/market/policy.ts';
import { ADDITIONAL_MARKET_FEE } from '../shared/market.ts';
import { keccak } from 'hash-wasm';
function identity() {
const keys = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
@@ -42,9 +45,62 @@ async function fixture(t: { after(fn: () => Promise<void>): void }) {
priceAtomicXmr: l.priceAtomicXmr, feeBps: l.feeBps });
return { dir, envelope, execute, activity, credit, listing, reserve, get market() { return market; },
changeCard: () => { present = false; }, advance: (ms: number) => { now += ms; },
restart: async () => { market.close(); market = await CustodyMarket.open(dir, options); } };
restart: async (policy: MarketPolicy = {}) => { market.close(); market = await CustodyMarket.open(dir, { ...options, ...policy }); } };
}
async function feeAddress() {
const prefix = Uint8Array.from([18, ...Array(64).fill(2)]);
const data = Buffer.concat([prefix, Buffer.from((await keccak(prefix, 256)).slice(0, 8), 'hex')]);
const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const sizes: Record<number, number> = { 1: 2, 2: 3, 3: 5, 4: 6, 5: 7, 6: 9, 7: 10, 8: 11 };
let encoded = '';
for (let i = 0; i < data.length; i += 8) {
const chunk = data.subarray(i, i + 8); let n = BigInt(`0x${chunk.toString('hex')}`), text = '';
while (n) { text = alphabet[Number(n % 58n)] + text; n /= 58n; }
encoded += text.padStart(sizes[chunk.length], '1');
}
return encoded;
}
test('operator market switches are strict and host fee defaults to fixed author with checksum validation', async () => {
assert.deepEqual(operatorMarketPolicy({}), { marketEnabled: true, tradingEnabled: true, hostFeeAddress: ADDITIONAL_MARKET_FEE.address });
for (const v of ['', 'true', 'false', 'yes', '2']) {
assert.throws(() => operatorMarketPolicy({ WIKIDECK_MARKET_ENABLED: v }));
assert.throws(() => operatorMarketPolicy({ WIKIDECK_MARKET_TRADING_ENABLED: v }));
}
await validateMarketPolicy(operatorMarketPolicy({}));
await assert.rejects(validateMarketPolicy({ hostFeeAddress: '4'.repeat(95) }), /checksum|network/);
});
test('market disable hides public listings and rejects new business but retains private balances and safe release', async t => {
const f = await fixture(t); f.credit(); const l = f.listing(), o = f.reserve(l);
const w = f.execute<CustodyWithdrawal>(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '100', maxFeeAtomicXmr: '1' });
await f.restart({ marketEnabled: false });
assert.deepEqual(f.market.browse().listings, []); assert.equal(f.market.info().marketEnabled, false);
assert.throws(() => f.listing('2'), /disabled/);
assert.throws(() => f.credit(stranger), /disabled/);
f.execute(buyer, { type: 'cancel-reservation', orderId: o.id });
f.execute(buyer, { type: 'cancel-withdrawal', withdrawalId: w.id });
assert.equal(f.activity().availableAtomicXmr, MOCK_CREDIT_ATOMIC);
const withdrawal = f.execute<CustodyWithdrawal>(buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '1', maxFeeAtomicXmr: '0' });
assert.equal(withdrawal.status, 'reserved');
});
test('paused trading keeps browse and cancellations available, and fee recipients are signed and snapshotted', async t => {
const f = await fixture(t), recipient = await feeAddress(); f.credit();
await f.restart({ hostFeeAddress: recipient });
assert.throws(() => f.listing(), /recipient/);
const action = { type: 'list' as const, cardHash, sequence: 0, priceAtomicXmr: '1000', feeBps: 250, hostFeeAddress: recipient };
const c = f.envelope(seller, action);
assert.throws(() => f.market.execute({ ...c, action: { ...action, hostFeeAddress: ADDITIONAL_MARKET_FEE.address } }), /signature/);
const l = f.market.execute(c) as CustodyListing;
await f.restart({ hostFeeAddress: ADDITIONAL_MARKET_FEE.address, tradingEnabled: false });
assert.equal(f.market.browse().listings[0].hostFeeAddress, recipient);
assert.throws(() => f.reserve(l), /disabled/);
await f.restart();
assert.throws(() => f.reserve(l), /recipient/);
const o = f.execute<CustodyOrder>(buyer, { type: 'reserve', listingId: l.id, priceAtomicXmr: l.priceAtomicXmr, feeBps: 250, hostFeeAddress: recipient });
assert.equal(o.hostFeeAddress, recipient); assert.equal(o.authorAddress, ADDITIONAL_MARKET_FEE.address);
f.market.settleFixture(o.id, evidence); assert.equal(f.market.audit().assetsAtomicXmr, MOCK_CREDIT_ATOMIC);
});
test('custody mode is off by default and rejects all live configurations', () => {
assert.equal(custodyMode({}), 'off'); assert.equal(custodyMode({ WIKIDECK_CUSTODY_MODE: 'sandbox' }), 'sandbox');
for (const mode of ['live', 'mainnet', '', 'shkeeper']) assert.throws(() => custodyMode({ WIKIDECK_CUSTODY_MODE: mode }));
+13 -2
View File
@@ -12,6 +12,7 @@ import { marketActionV3Schema, marketMessageV3, type MarketActionV3, type Market
type MarketListingV3, type MarketOrderV3 } from '../shared/market-v3.ts';
import { NETWORK, nftTransferMessage, testnetOwnerOf, transactionCardIds, type NftTransfer, type TestnetSnapshot, type TestnetBlock } from '../shared/testnet.ts';
import type { CanonicalEvent } from '../server/testnet/canonical-index.ts';
import type { MarketPolicy } from '../server/market/policy.ts';
import { address, CHAIN_ID, GENESIS_HASH, transferId, verifySignature } from '../server/testnet/consensus.ts';
function identity() {
@@ -105,9 +106,19 @@ async function fixture(t: { after(fn: () => Promise<void>): void }, indexed = fa
};
return { dir, chain, events, envelope, envelopeV3, run, list, buy, orders, delivery, confirm,
get market() { return market; }, get now() { return time; }, advance: (ms: number) => { time += ms; },
restart: async () => { market.close(); market = await Market.open(dir, adapter,
{ feeBps: 500, confirmations: 5 }, () => time); } };
restart: async (policy: MarketPolicy = {}) => { market.close(); market = await Market.open(dir, adapter,
{ feeBps: 500, confirmations: 5, ...policy }, () => time); } };
}
test('mock market switches block new listings/bids/checkout while allowing paid-order delivery and private activity', async t => {
const f = await fixture(t), l = await f.list(), o = await f.buy(l);
await f.run(bob, { type: 'mock-pay', orderId: o.id });
await f.restart({ marketEnabled: false });
assert.equal(f.market.info().marketEnabled, false); assert.deepEqual(f.market.browse().listings, []);
await assert.rejects(f.list(), /disabled/);
assert.equal((await f.orders())[0].id, o.id);
const tx = f.delivery(o); await f.run(alice, { type: 'deliver', orderId: o.id, transfer: tx });
assert.equal(f.chain.state.pendingTransfers.length, 1);
});
test('indexed market uses bounded durable event catch-up and never requests full snapshots', async t => {
const f = await fixture(t, true);
+1 -1
View File
@@ -27,4 +27,4 @@ test('database HBA requires TLS+SCRAM over TCP and provisioning refuses partial
assert.match(setup, /Partial secret initialization/);
assert.match(setup, /subjectAltName=DNS:database/);
assert.match(setup, /-checkend/);
});
});
+15
View File
@@ -211,6 +211,21 @@ test('configuration requires secret files, verified TLS, literal backend, and lo
await assert.rejects(postgresConfig({ WIKIDECK_CUSTODY_PG_URL_FILE: file }), /configuration/);
});
test('PostgreSQL market disable preserves existing claims and signed withdrawal cancellation', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const listing = await f.execute(seller, { type: 'list', cardHash: hash, sequence: 0, priceAtomicXmr: '100', feeBps: 250 }) as CustodyListing;
const order = await f.execute(buyer, { type: 'reserve', listingId: listing.id, priceAtomicXmr: '100', feeBps: 250 }) as CustodyOrder;
const disabled = await PostgresCustody.open(f.runtime, { ...options, marketEnabled: false });
try {
assert.equal(disabled.info().marketEnabled, false); assert.deepEqual((await disabled.browse()).listings, []);
await assert.rejects(disabled.execute(envelope(disabled.id, other, { type: 'mock-credit' })), /disabled/);
await disabled.execute(envelope(disabled.id, buyer, { type: 'cancel-reservation', orderId: order.id }));
const w = await disabled.execute(envelope(disabled.id, buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '1', maxFeeAtomicXmr: '0' })) as CustodyWithdrawal;
await disabled.execute(envelope(disabled.id, buyer, { type: 'cancel-withdrawal', withdrawalId: w.id }));
assert.equal((await disabled.audit()).assetsAtomicXmr, '1000000000000');
} finally { await disabled.close(); }
});
test('full PostgreSQL dump/isolated restore preserves custody identity, receipts and journal backing', async t => {
const f = await fixture(t); await f.execute(buyer, { type: 'mock-credit' });
const cmd = envelope(f.market.id, buyer, { type: 'withdraw', destination: 'mock-external', amountAtomicXmr: '500', maxFeeAtomicXmr: '20' });
+1
View File
@@ -206,6 +206,7 @@ test('candidate drafts from real sandbox reservations without signing, changing
assert.equal(market.info().nftSettlement, false);
assert.throws(() => termsForCustodyOrder({ ...order, authorAtomicXmr: '1' }, context, options), /allocations/);
assert.throws(() => termsForCustodyOrder({ ...order, status: 'cancelled' }, context, options), /awaiting-lock/);
assert.throws(() => termsForCustodyOrder({ ...order, hostFeeAddress: 'different-recipient' }, context, options), /terms extension/);
});
test('enumerated adversarial histories preserve exclusivity and deterministic replay across snapshot restores', () => {