Add marketplace quote-send-confirm transaction flow controls
This commit is contained in:
parent
b1fb4706a8
commit
89fcd2be14
@ -37,7 +37,7 @@ Advanced integration controls (collapsible):
|
|||||||
1. API/chain connection settings
|
1. API/chain connection settings
|
||||||
2. Wallet intent + verify primitives
|
2. Wallet intent + verify primitives
|
||||||
3. Membership quote + confirm 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
|
5. Member channel register/poll primitives
|
||||||
6. Governance install + lease primitives
|
6. Governance install + lease primitives
|
||||||
7. Raw response log for deterministic troubleshooting
|
7. Raw response log for deterministic troubleshooting
|
||||||
|
|||||||
73
app/app.js
73
app/app.js
@ -421,11 +421,11 @@ async function waitForTxMined(txHash, timeoutMs = 120000, intervalMs = 3000) {
|
|||||||
if (statusHex === "0x1" || statusHex === "1") {
|
if (statusHex === "0x1" || statusHex === "1") {
|
||||||
return receipt;
|
return receipt;
|
||||||
}
|
}
|
||||||
throw new Error(`membership transaction reverted: status=${receipt.status}`);
|
throw new Error(`transaction reverted: status=${receipt.status}`);
|
||||||
}
|
}
|
||||||
await sleep(intervalMs);
|
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) {
|
async function confirmMembershipWithRetry(maxAttempts = 8, intervalMs = 2500) {
|
||||||
@ -598,7 +598,7 @@ async function onCheckoutQuote() {
|
|||||||
org_root_id: orgRootID(),
|
org_root_id: orgRootID(),
|
||||||
principal_id: principalID(),
|
principal_id: principalID(),
|
||||||
principal_role: principalRole(),
|
principal_role: principalRole(),
|
||||||
include_membership_if_missing: true,
|
include_membership_if_missing: false,
|
||||||
};
|
};
|
||||||
const payerWallet = $("payerWallet").value.trim();
|
const payerWallet = $("payerWallet").value.trim();
|
||||||
const payerProof = $("payerProof").value.trim();
|
const payerProof = $("payerProof").value.trim();
|
||||||
@ -616,6 +616,33 @@ async function onCheckoutQuote() {
|
|||||||
return out;
|
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() {
|
async function onCheckoutConfirm() {
|
||||||
const quoteID = $("checkoutQuoteId").value.trim();
|
const quoteID = $("checkoutQuoteId").value.trim();
|
||||||
if (!quoteID) {
|
if (!quoteID) {
|
||||||
@ -641,6 +668,44 @@ async function onCheckoutConfirm() {
|
|||||||
return out;
|
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() {
|
async function onListEntitlements() {
|
||||||
const out = await request("GET", `/marketplace/entitlements?wallet=${encodeURIComponent(requireWallet())}`);
|
const out = await request("GET", `/marketplace/entitlements?wallet=${encodeURIComponent(requireWallet())}`);
|
||||||
logLine("marketplace entitlements", out);
|
logLine("marketplace entitlements", out);
|
||||||
@ -755,7 +820,9 @@ bind("btnLeaseHeartbeat", onLeaseHeartbeat);
|
|||||||
bind("btnOfflineRenew", onOfflineRenew);
|
bind("btnOfflineRenew", onOfflineRenew);
|
||||||
bind("btnListOffers", onListOffers);
|
bind("btnListOffers", onListOffers);
|
||||||
bind("btnCheckoutQuote", onCheckoutQuote);
|
bind("btnCheckoutQuote", onCheckoutQuote);
|
||||||
|
bind("btnSendCheckoutTx", onSendCheckoutTx);
|
||||||
bind("btnCheckoutConfirm", onCheckoutConfirm);
|
bind("btnCheckoutConfirm", onCheckoutConfirm);
|
||||||
|
bind("btnRunCheckoutFlow", onRunCheckoutFlow);
|
||||||
bind("btnListEntitlements", onListEntitlements);
|
bind("btnListEntitlements", onListEntitlements);
|
||||||
|
|
||||||
logLine("launcher shell ready", {
|
logLine("launcher shell ready", {
|
||||||
|
|||||||
@ -171,7 +171,9 @@
|
|||||||
<h2>Marketplace Checkout</h2>
|
<h2>Marketplace Checkout</h2>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button id="btnListOffers">List offers</button>
|
<button id="btnListOffers">List offers</button>
|
||||||
|
<button id="btnRunCheckoutFlow">Run checkout flow</button>
|
||||||
<button id="btnCheckoutQuote">Checkout quote</button>
|
<button id="btnCheckoutQuote">Checkout quote</button>
|
||||||
|
<button id="btnSendCheckoutTx">Send checkout tx</button>
|
||||||
<button id="btnCheckoutConfirm">Checkout confirm</button>
|
<button id="btnCheckoutConfirm">Checkout confirm</button>
|
||||||
<button id="btnListEntitlements">List entitlements</button>
|
<button id="btnListEntitlements">List entitlements</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user