const { ethers } = require("ethers"); const API_BASE = (process.env.SECRET_API_BASE_URL || "https://api.edut.dev").trim(); const ORIGIN = (process.env.SECRET_API_ORIGIN || "https://edut.ai").trim(); const LOCALE = (process.env.SECRET_API_LOCALE || "en-US").trim(); const CHAIN_ID = Number((process.env.SECRET_API_CHAIN_ID || "84532").trim()); const PRIVATE_KEY = (process.env.DEPLOYER_PRIVATE_KEY || "").trim(); const RPC_URL = (process.env.BASE_SEPOLIA_RPC_URL || "").trim(); const GAS_PRICE_WEI = (process.env.E2E_GAS_PRICE_WEI || "").trim(); const GAS_LIMIT = (process.env.E2E_GAS_LIMIT || "300000").trim(); const OFFER_ID = (process.env.E2E_OFFER_ID || "edut.workspace.core").trim(); const ORG_ROOT_ID = (process.env.E2E_ORG_ROOT_ID || "org.e2e.workspace").trim(); const PRINCIPAL_ID = (process.env.E2E_PRINCIPAL_ID || "principal.e2e.owner").trim(); const PRINCIPAL_ROLE = (process.env.E2E_PRINCIPAL_ROLE || "org_root_owner").trim(); const WORKSPACE_ID = (process.env.E2E_WORKSPACE_ID || "workspace.e2e.alpha").trim(); const DEVICE_ID = (process.env.E2E_DEVICE_ID || "device-e2e-001").trim(); const PLATFORM = (process.env.E2E_PLATFORM || "macos").trim(); const LAUNCHER_VERSION = (process.env.E2E_LAUNCHER_VERSION || "0.0.0-e2e").trim(); const IDENTITY_ASSURANCE = (process.env.E2E_IDENTITY_ASSURANCE_LEVEL || "").trim(); const IDENTITY_ATTESTED_BY = (process.env.E2E_IDENTITY_ATTESTED_BY || "").trim(); const IDENTITY_ATTESTATION_ID = (process.env.E2E_IDENTITY_ATTESTATION_ID || "").trim(); function required(name, value) { if (!value) { throw new Error(`Missing required env: ${name}`); } return value; } function requestHeaders(sessionToken) { const headers = { "content-type": "application/json" }; if (sessionToken) { headers.authorization = `Bearer ${sessionToken}`; headers["x-edut-session"] = sessionToken; } return headers; } async function requestJSON(method, path, payload, sessionToken) { const response = await fetch(`${API_BASE}${path}`, { method, headers: requestHeaders(sessionToken), body: payload === undefined ? undefined : JSON.stringify(payload), }); const text = await response.text(); let data; try { data = text ? JSON.parse(text) : {}; } catch (err) { throw new Error(`${method} ${path} returned non-json response: ${text}`); } return { ok: response.ok, status: response.status, data, }; } async function expectOK(method, path, payload, sessionToken) { const result = await requestJSON(method, path, payload, sessionToken); if (!result.ok) { throw new Error(`${method} ${path} failed (${result.status}): ${JSON.stringify(result.data)}`); } return result.data; } async function submitTransaction(signer, txLike) { const txRequest = { to: txLike.to, data: txLike.data, value: ethers.BigNumber.from(txLike.value || "0x0"), }; if (GAS_LIMIT && GAS_LIMIT !== "0") { txRequest.gasLimit = ethers.BigNumber.from(GAS_LIMIT); } if (GAS_PRICE_WEI) { txRequest.gasPrice = ethers.BigNumber.from(GAS_PRICE_WEI); } const tx = await signer.sendTransaction(txRequest); await tx.wait(1); return tx.hash; } function summarizeSkip(reasonCode, httpStatus, detail) { return { status: "skipped", reason_code: reasonCode || "unknown", http_status: httpStatus, detail, }; } function isInsufficientFundsError(err) { const text = String(err?.message || err || "").toLowerCase(); return text.includes("insufficient funds"); } function isTxRevertError(err) { const code = String(err?.code || "").toUpperCase(); const text = String(err?.message || err || "").toLowerCase(); return code === "CALL_EXCEPTION" || text.includes("transaction failed"); } function isTxPendingResponse(result) { if (!result || result.ok) { return false; } const code = String(result.data?.code || "").toLowerCase(); const detail = String(result.data?.error || "").toLowerCase(); if (result.status === 409 && code.includes("verification") && detail.includes("pending")) { return true; } return false; } async function main() { required("DEPLOYER_PRIVATE_KEY", PRIVATE_KEY); required("BASE_SEPOLIA_RPC_URL", RPC_URL); const provider = new ethers.providers.JsonRpcProvider(RPC_URL); const signer = new ethers.Wallet(PRIVATE_KEY, provider); const wallet = await signer.getAddress(); const result = { api_base: API_BASE, wallet, chain_id: CHAIN_ID, stages: {}, }; const statusBefore = await expectOK( "GET", `/secret/membership/status?wallet=${encodeURIComponent(wallet)}`, undefined, null ); result.stages.membership_status_before = statusBefore; const intent = await expectOK("POST", "/secret/wallet/intent", { address: wallet, origin: ORIGIN, locale: LOCALE, chain_id: CHAIN_ID, }); const domain = { name: intent.domain_name, version: "1", chainId: intent.chain_id, verifyingContract: intent.verifying_contract, }; const types = { DesignationIntent: [ { name: "designationCode", type: "string" }, { name: "designationToken", type: "string" }, { name: "nonce", type: "string" }, { name: "issuedAt", type: "string" }, { name: "origin", type: "string" }, ], }; const message = { designationCode: intent.designation_code, designationToken: intent.display_token, nonce: intent.nonce, issuedAt: intent.issued_at, origin: ORIGIN, }; const signature = await signer._signTypedData(domain, types, message); const verify = await expectOK("POST", "/secret/wallet/verify", { intent_id: intent.intent_id, address: wallet, chain_id: CHAIN_ID, signature, }); const sessionToken = verify.session_token; result.stages.wallet_session = { status: verify.status, expires_at: verify.session_expires_at, }; if ((statusBefore.status || "").toLowerCase() !== "active") { const quote = await expectOK("POST", "/secret/membership/quote", { designation_code: intent.designation_code, address: wallet, chain_id: CHAIN_ID, }); let membershipTxHash; try { membershipTxHash = await submitTransaction(signer, quote.tx || {}); } catch (err) { if (isInsufficientFundsError(err)) { result.stages.membership_activation = summarizeSkip( "insufficient_funds", 0, String(err?.message || err) ); } else if (isTxRevertError(err)) { result.stages.membership_activation = summarizeSkip( "tx_reverted", 0, String(err?.message || err) ); } else { throw err; } } if (!membershipTxHash) { console.log(JSON.stringify(result, null, 2)); return; } const confirmPayload = { designation_code: intent.designation_code, quote_id: quote.quote_id, tx_hash: membershipTxHash, address: wallet, chain_id: CHAIN_ID, }; if (IDENTITY_ASSURANCE) { confirmPayload.identity_assurance_level = IDENTITY_ASSURANCE; } if (IDENTITY_ATTESTED_BY) { confirmPayload.identity_attested_by = IDENTITY_ATTESTED_BY; } if (IDENTITY_ATTESTATION_ID) { confirmPayload.identity_attestation_id = IDENTITY_ATTESTATION_ID; } const confirmRes = await requestJSON("POST", "/secret/membership/confirm", confirmPayload); if (!confirmRes.ok) { if (isTxPendingResponse(confirmRes)) { result.stages.membership_activation = summarizeSkip( "tx_pending", confirmRes.status, confirmRes.data?.error || JSON.stringify(confirmRes.data || {}) ); } else { throw new Error( `POST /secret/membership/confirm failed (${confirmRes.status}): ${JSON.stringify(confirmRes.data)}` ); } } else { result.stages.membership_activation = { status: confirmRes.data.status, tx_hash: membershipTxHash, identity_assurance_level: confirmRes.data.identity_assurance_level || null, }; } } else { result.stages.membership_activation = { status: "skipped_already_active", }; } const statusAfter = await expectOK( "GET", `/secret/membership/status?wallet=${encodeURIComponent(wallet)}`, undefined, null ); result.stages.membership_status_after = statusAfter; const offers = await expectOK("GET", "/marketplace/offers", undefined, sessionToken); const targetOffer = (offers.offers || []).find((offer) => offer.offer_id === OFFER_ID); if (!targetOffer) { throw new Error(`marketplace offer not found: ${OFFER_ID}`); } const checkoutQuoteRes = await requestJSON( "POST", "/marketplace/checkout/quote", { wallet, offer_id: OFFER_ID, org_root_id: ORG_ROOT_ID, principal_id: PRINCIPAL_ID, principal_role: PRINCIPAL_ROLE, workspace_id: WORKSPACE_ID, include_membership_if_missing: false, }, sessionToken ); if (!checkoutQuoteRes.ok) { result.stages.marketplace_checkout = summarizeSkip( checkoutQuoteRes.data.code, checkoutQuoteRes.status, checkoutQuoteRes.data.error ); } else { const checkoutQuote = checkoutQuoteRes.data; const txData = String((checkoutQuote.tx || {}).data || "").toLowerCase(); if (!txData || txData === "0x") { result.stages.marketplace_checkout = summarizeSkip( "entitlement_transaction_unconfigured", 200, "checkout quote returned empty calldata" ); } else { let checkoutTxHash; try { checkoutTxHash = await submitTransaction(signer, checkoutQuote.tx || {}); } catch (err) { if (isInsufficientFundsError(err)) { result.stages.marketplace_checkout = summarizeSkip( "insufficient_funds", 0, String(err?.message || err) ); } else if (isTxRevertError(err)) { result.stages.marketplace_checkout = summarizeSkip( "tx_reverted", 0, String(err?.message || err) ); } else { throw err; } } if (!checkoutTxHash) { console.log(JSON.stringify(result, null, 2)); return; } const checkoutConfirmRes = await requestJSON( "POST", "/marketplace/checkout/confirm", { quote_id: checkoutQuote.quote_id, wallet, offer_id: OFFER_ID, org_root_id: ORG_ROOT_ID, principal_id: PRINCIPAL_ID, principal_role: PRINCIPAL_ROLE, workspace_id: WORKSPACE_ID, tx_hash: checkoutTxHash, chain_id: CHAIN_ID, }, sessionToken ); if (!checkoutConfirmRes.ok) { if (isTxPendingResponse(checkoutConfirmRes)) { result.stages.marketplace_checkout = summarizeSkip( "tx_pending", checkoutConfirmRes.status, checkoutConfirmRes.data?.error || JSON.stringify(checkoutConfirmRes.data || {}) ); } else { throw new Error( `POST /marketplace/checkout/confirm failed (${checkoutConfirmRes.status}): ${JSON.stringify(checkoutConfirmRes.data)}` ); } } else { const entitlements = await expectOK( "GET", `/marketplace/entitlements?wallet=${encodeURIComponent(wallet)}`, undefined, sessionToken ); result.stages.marketplace_checkout = { status: checkoutConfirmRes.data.status, entitlement_id: checkoutConfirmRes.data.entitlement_id, tx_hash: checkoutTxHash, entitlement_count: Array.isArray(entitlements.entitlements) ? entitlements.entitlements.length : 0, }; } } } const installTokenRes = await requestJSON( "POST", "/governance/install/token", { wallet, org_root_id: ORG_ROOT_ID, principal_id: PRINCIPAL_ID, principal_role: PRINCIPAL_ROLE, device_id: DEVICE_ID, launcher_version: LAUNCHER_VERSION, platform: PLATFORM, }, sessionToken ); if (!installTokenRes.ok) { result.stages.governance_install = summarizeSkip( installTokenRes.data.code, installTokenRes.status, installTokenRes.data.error ); } else { const installToken = installTokenRes.data; const installConfirm = await expectOK( "POST", "/governance/install/confirm", { install_token: installToken.install_token, wallet, device_id: DEVICE_ID, entitlement_id: installToken.entitlement_id, package_hash: installToken.package.package_hash, runtime_version: installToken.package.runtime_version, installed_at: new Date().toISOString(), }, sessionToken ); const installStatus = await expectOK( "GET", `/governance/install/status?wallet=${encodeURIComponent(wallet)}&device_id=${encodeURIComponent( DEVICE_ID )}`, undefined, sessionToken ); result.stages.governance_install = { status: installConfirm.status, runtime_version: installConfirm.runtime_version, activation_status: installStatus.activation_status, availability_state: installStatus.availability_state, }; } console.log(JSON.stringify(result, null, 2)); } main().catch((err) => { console.error(err.message || err); process.exit(1); });