shared: live-CBOR for deferred builders (Reclaim, RetireAdvert)
Closes 2/4 builders from the M1-F-D-defer list (postAdvert already live in
7ea7328; updateAdvert and release remain).
Reclaim:
- buildLiveTxForReclaim in liveCbor.ts (mirrors buildLiveTxForAccept):
spends the escrow UTxO with REDEEMER_RECLAIM (Constr3), pays
payment + buyer_bond + supplier_bond to buyer, delegates UPLC eval
to Ogmios via localUPLCEval:false. Validity range = tipMs ± 60s.
- reclaim.ts: replace the unconditional mockSlotToWallclockMs tipMs
computation with `isLive ? Date.now() : mockSlotToWallclockMs(tipSlot)`,
matching accept.ts:77. Adds the live-CBOR dispatch branch.
Funds-at-risk before this fix: with the broken tipMs path, every live
Reclaim was rejected client-side ("tip < deliver_by" using a Nov-1970
tip). If a supplier never Claimed, buyer's 6-7 AP3X stayed locked
indefinitely. Verified recovery of 4 stuck escrows on Vector L2 testnet
(txs f6660e52, 769f16fd, 79970d86, 9f4db14b — 14 AP3X total recovered).
RetireAdvert:
- buildLiveTxForRetireAdvert in liveCbor.ts: spends advert UTxO with
REDEEMER_RETIRE_ADVERT (Constr2), advert script address.
- retireAdvert.ts: live-CBOR dispatch.
- supplier CLI: retire-advert.ts + retireAdvertFlow.ts.
- supplier/package.json: tx:retire-advert script.
Indexer:
- follower/blockProcessor + worker: handle RetireAdvert events,
transition advert row state to Retired.
- db/cache.ts: markAdvertTerminal helper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,6 +168,16 @@ export class SqliteCache {
|
||||
return row ?? null;
|
||||
}
|
||||
|
||||
/** Mark an advertisement row as Retired. Called when the indexer observes
|
||||
* a RetireAdvert spend (advert UTxO spent with no continuing output at
|
||||
* the advert script address). Drops the row out of listActiveAdvertisements
|
||||
* which filters on `status = 'Active'`. */
|
||||
markAdvertisementRetired(utxoRef: string): void {
|
||||
this.db.prepare(
|
||||
`UPDATE advertisements SET status = 'Retired' WHERE utxo_ref = ?`
|
||||
).run(utxoRef);
|
||||
}
|
||||
|
||||
listActiveAdvertisements(filter?: { capability_id?: string; supplier_pkh?: string }): AdvertRow[] {
|
||||
let sql = `SELECT * FROM advertisements WHERE status = 'Active' AND rolled_back = 0`;
|
||||
const params: unknown[] = [];
|
||||
|
||||
@@ -192,6 +192,10 @@ export class ChainSyncWorker extends EventEmitter {
|
||||
} catch {
|
||||
// already warned by processBlock
|
||||
}
|
||||
} else if (event.type === "RetireAdvert") {
|
||||
// The advert UTxO was spent with no continuing output → flip the row's
|
||||
// status to Retired so listActiveAdvertisements drops it.
|
||||
this.cache.markAdvertisementRetired(event.utxoRef);
|
||||
} else if (event.type === "PostEscrow" || event.type === "ClaimEscrow" || event.type === "SubmitEscrow") {
|
||||
try {
|
||||
const d = decodeEscrowDatum(event.datumHex);
|
||||
|
||||
Reference in New Issue
Block a user