From 89fcd2be14a4ae971615769f8ae914e49bdb5728 Mon Sep 17 00:00:00 2001 From: Joshua Date: Wed, 18 Feb 2026 13:29:31 -0800 Subject: [PATCH] Add marketplace quote-send-confirm transaction flow controls --- README.md | 2 +- app/app.js | 73 +++++++++++++++++++++++++++++++++++++++++++++++--- app/index.html | 2 ++ 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5d3980d..ec81126 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Advanced integration controls (collapsible): 1. API/chain connection settings 2. Wallet intent + verify primitives 3. Membership quote + confirm primitives -4. Marketplace offer list + checkout quote/confirm primitives +4. Marketplace offer list + checkout quote/send/confirm primitives 5. Member channel register/poll primitives 6. Governance install + lease primitives 7. Raw response log for deterministic troubleshooting diff --git a/app/app.js b/app/app.js index b17a296..a42c1fb 100644 --- a/app/app.js +++ b/app/app.js @@ -421,11 +421,11 @@ async function waitForTxMined(txHash, timeoutMs = 120000, intervalMs = 3000) { if (statusHex === "0x1" || statusHex === "1") { return receipt; } - throw new Error(`membership transaction reverted: status=${receipt.status}`); + throw new Error(`transaction reverted: status=${receipt.status}`); } await sleep(intervalMs); } - throw new Error(`membership transaction not mined within ${timeoutMs}ms`); + throw new Error(`transaction not mined within ${timeoutMs}ms`); } async function confirmMembershipWithRetry(maxAttempts = 8, intervalMs = 2500) { @@ -598,7 +598,7 @@ async function onCheckoutQuote() { org_root_id: orgRootID(), principal_id: principalID(), principal_role: principalRole(), - include_membership_if_missing: true, + include_membership_if_missing: false, }; const payerWallet = $("payerWallet").value.trim(); const payerProof = $("payerProof").value.trim(); @@ -616,6 +616,33 @@ async function onCheckoutQuote() { return out; } +async function onSendCheckoutTx() { + if (!state.lastCheckoutQuote || !state.lastCheckoutQuote.tx) { + throw new Error("request checkout quote before sending transaction"); + } + const provider = await requireProvider(); + const from = normalizedAddress(state.lastCheckoutQuote.tx.from || requireWallet()); + if (from !== normalizedAddress(requireWallet())) { + throw new Error(`active wallet ${requireWallet()} does not match checkout payer ${from}`); + } + const txRequest = { + from, + to: state.lastCheckoutQuote.tx.to, + data: state.lastCheckoutQuote.tx.data, + value: state.lastCheckoutQuote.tx.value || "0x0", + }; + const txHash = await provider.request({ + method: "eth_sendTransaction", + params: [txRequest], + }); + $("checkoutTxHash").value = txHash; + logLine("marketplace tx sent", { + quote_id: state.lastCheckoutQuote.quote_id, + tx_hash: txHash, + payer_wallet: from, + }); +} + async function onCheckoutConfirm() { const quoteID = $("checkoutQuoteId").value.trim(); if (!quoteID) { @@ -641,6 +668,44 @@ async function onCheckoutConfirm() { return out; } +async function confirmCheckoutWithRetry(maxAttempts = 8, intervalMs = 2500) { + let lastErr = null; + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + try { + const out = await onCheckoutConfirm(); + return out; + } catch (err) { + lastErr = err; + const message = String(err || ""); + if (!message.includes("tx verification pending/failed") && !message.includes("membership verification pending/failed")) { + throw err; + } + setFlowStatus(`checkout confirm pending (${attempt}/${maxAttempts})`); + await sleep(intervalMs); + } + } + throw lastErr || new Error("checkout confirm failed"); +} + +async function onRunCheckoutFlow() { + setFlowStatus("quoting checkout"); + await onCheckoutQuote(); + setFlowStatus("sending checkout transaction"); + await onSendCheckoutTx(); + + const txHash = $("checkoutTxHash").value.trim(); + if (!txHash) { + throw new Error("missing checkout tx hash after send"); + } + setFlowStatus("waiting for checkout chain confirmation"); + await waitForTxMined(txHash); + setFlowStatus("confirming checkout with API"); + await confirmCheckoutWithRetry(); + setFlowStatus("refreshing entitlements"); + await onListEntitlements(); + setFlowStatus("checkout flow complete"); +} + async function onListEntitlements() { const out = await request("GET", `/marketplace/entitlements?wallet=${encodeURIComponent(requireWallet())}`); logLine("marketplace entitlements", out); @@ -755,7 +820,9 @@ bind("btnLeaseHeartbeat", onLeaseHeartbeat); bind("btnOfflineRenew", onOfflineRenew); bind("btnListOffers", onListOffers); bind("btnCheckoutQuote", onCheckoutQuote); +bind("btnSendCheckoutTx", onSendCheckoutTx); bind("btnCheckoutConfirm", onCheckoutConfirm); +bind("btnRunCheckoutFlow", onRunCheckoutFlow); bind("btnListEntitlements", onListEntitlements); logLine("launcher shell ready", { diff --git a/app/index.html b/app/index.html index 6c6a88d..4fda08e 100644 --- a/app/index.html +++ b/app/index.html @@ -171,7 +171,9 @@

Marketplace Checkout

+ +