This commit is contained in:
2026-10-20 03:40:26 -04:00
parent 9850fd8554
commit 0cc5032857
12 changed files with 262 additions and 6 deletions
+17 -5
View File
@@ -105,6 +105,12 @@ export class Market {
snapshot.transfers.filter(tx => transactionCardIds(tx).includes(hash)).length === sequence &&
snapshot.blocks.length + 1 - block.height >= CONSENSUS.coinbaseMaturity;
}
private deliveryId(order: MarketOrder): string {
// Identity excludes signature bytes. Recognize exact sale delivery through ANY submission route,
// including peer gossip or a direct /api/transfer, even before the market records delivery intent.
return transactionId({ network: NETWORK, chainId: CHAIN_ID, cardHash: order.cardHash,
from: order.seller, to: order.buyer, sequence: order.sequence, publicKey: '', signature: '' });
}
/** Reconcile from canonical transaction identity, never from a mempool acknowledgement. */
private reconcile() {
const s = this.chain.snapshot(), now = this.now();
@@ -112,7 +118,8 @@ export class Market {
for (const o of this.rows<MarketOrder>('orders')) {
const l = this.listing(o.listingId);
if (o.status === 'refunded' || o.status === 'expired') continue;
const delivery = o.transferId && s.blocks.find(b => b.transactions.some(tx => transactionId(tx) === o.transferId));
const deliveryId = this.deliveryId(o);
const delivery = s.blocks.find(b => b.transactions.some(tx => transactionId(tx) === deliveryId));
const confirmations = delivery ? s.blocks.length - delivery.height + 1 : 0;
o.deliveryConfirmations = confirmations;
if (o.status === 'complete' || o.status === 'review') {
@@ -122,7 +129,12 @@ export class Market {
}
this.saveOrder(o); continue; // Never automatically pay a second time after a reorganization.
}
if (delivery) {
if (delivery && o.status === 'awaiting-payment') {
// A gift/direct delivery is not evidence that payment was received. Never pay an unpaid order.
o.status = 'review'; o.payout = 'review';
if (o.additionalFee) o.additionalFee.payout = 'review';
} else if (delivery) {
o.transferId = deliveryId;
o.status = 'delivery-pending';
if (confirmations >= o.requiredConfirmations) {
o.status = 'complete'; o.payout = 'mock-paid'; l.status = 'sold';
@@ -131,8 +143,8 @@ export class Market {
} else if (o.status === 'awaiting-payment' && now >= o.payBy) {
o.status = 'expired'; l.status = l.kind === 'fixed' ? 'open' : 'expired';
} else if (o.status !== 'awaiting-payment') {
const pending = s.pendingTransfers.some(tx => transactionId(tx) === o.transferId);
const conflict = s.pendingTransfers.some(tx => transactionCardIds(tx).includes(o.cardHash) && transactionId(tx) !== o.transferId);
const pending = s.pendingTransfers.some(tx => transactionId(tx) === deliveryId);
const conflict = s.pendingTransfers.some(tx => transactionCardIds(tx).includes(o.cardHash) && transactionId(tx) !== deliveryId);
if (!this.card(s, o.cardHash, o.seller, o.sequence) || conflict || (now >= o.deliverBy && !pending)) o.status = 'refund-required';
else if (o.status === 'delivery-pending' && !pending) o.status = 'paid';
}
@@ -252,7 +264,7 @@ export class Market {
o.status = 'paid'; o.deliverBy = now + DELIVERY_WINDOW;
} else {
if (o.status !== 'refund-required' || s.pendingTransfers.some(tx => transactionCardIds(tx).includes(o.cardHash)) ||
s.blocks.some(b => b.transactions.some(tx => transactionId(tx) === o.transferId))) throw new Error('Refund requires failed delivery and no pending/canonical sale transfer');
s.blocks.some(b => b.transactions.some(tx => transactionId(tx) === this.deliveryId(o)))) throw new Error('Refund requires failed delivery and no pending/canonical sale transfer');
o.status = 'refunded'; const l = this.listing(o.listingId); l.status = 'cancelled'; this.saveListing(l);
}
this.saveOrder(o); return o;