Paid-sale workflow

This commit is contained in:
2026-10-20 17:38:34 -04:00
parent 621611ecd9
commit ca8ff48628
11 changed files with 414 additions and 13 deletions
+14 -1
View File
@@ -11,6 +11,7 @@ import { applySaleBlock, initialSaleState, saleContext, saleDecision, saleId, sa
type SaleState, type CandidateTransaction } from '../server/testnet/sale-candidate.ts';
import { address, transactionSchema, CHAIN_ID } from '../server/testnet/consensus.ts';
import { CustodyMarket } from '../server/market/custody.ts';
import { ADDITIONAL_MARKET_FEE } from '../shared/market.ts';
import { CUSTODY_POLICY, custodyMessage, type CustodyCommand, type CustodyAction, type CustodyOrder, type CustodyListing } from '../shared/custody.ts';
function identity() {
@@ -99,6 +100,18 @@ test('host cannot redirect delivery or alter signed reservation evidence, and co
assert.equal(saleDecision(at(done, 14), f.terms, context, 'settled').action, 'none');
});
test('candidate host recipient extension is signed and omission preserves historical terms bytes', () => {
const legacy = fixture(), t = legacy.terms;
assert.equal(saleTermsMessage(t), JSON.stringify([SALE_CANDIDATE, 'terms', t.network, t.chainId, t.policyDigest,
t.custodianId, t.orderId, t.cardHash, t.sequence, t.seller, t.buyer, t.host, 'XMR', t.priceAtomicXmr,
t.feeBps, ADDITIONAL_MARKET_FEE.bps, ADDITIONAL_MARKET_FEE.address, t.lockUntilHeight, t.expiresHeight]));
const custom = fixture({ hostFeeAddress: '8'.repeat(95) }); // Syntax-only candidate, no wallet/RPC claim.
assert.notEqual(saleId(custom.terms), saleId({ ...custom.terms, hostFeeAddress: undefined }));
assert.equal(apply(custom.state, [custom.lock]).sales[saleId(custom.terms)].terms.hostFeeAddress, '8'.repeat(95));
assert.throws(() => apply(custom.state, [{ ...custom.lock, terms: { ...custom.terms, hostFeeAddress: ADDITIONAL_MARKET_FEE.address } }]), /both trader/);
assert.throws(() => apply(custom.state, [{ ...custom.lock, terms: { ...custom.terms, hostFeeAddress: undefined } }]), /both trader/);
});
test('locked cards cannot escape through free transfers or any leg of an atomic trade', () => {
const f = fixture(), locked = apply(f.state, [f.lock]);
assert.throws(() => apply(locked, [gift(1)]), /locked/);
@@ -206,7 +219,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/);
assert.throws(() => termsForCustodyOrder({ ...order, hostFeeAddress: 'different-recipient' }, context, options));
});
test('enumerated adversarial histories preserve exclusivity and deterministic replay across snapshot restores', () => {