From cfb4770e7ef823851fb0cb6947e52728393e005e Mon Sep 17 00:00:00 2001 From: Joshua Date: Wed, 18 Feb 2026 09:06:29 -0800 Subject: [PATCH] feat(scripts): add end-to-end membership flow smoke runner --- package.json | 3 +- scripts/e2e-membership-flow.cjs | 165 ++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 scripts/e2e-membership-flow.cjs diff --git a/package.json b/package.json index 5ea9fdb..8cde4af 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test": "hardhat test", "check": "npm run build && npm run test", "deploy:sepolia": "hardhat run scripts/deploy-membership.cjs --network baseSepolia", - "deploy:mainnet": "hardhat run scripts/deploy-membership.cjs --network base" + "deploy:mainnet": "hardhat run scripts/deploy-membership.cjs --network base", + "smoke:e2e:sepolia": "node scripts/e2e-membership-flow.cjs" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.2.3", diff --git a/scripts/e2e-membership-flow.cjs b/scripts/e2e-membership-flow.cjs new file mode 100644 index 0000000..ad8f18a --- /dev/null +++ b/scripts/e2e-membership-flow.cjs @@ -0,0 +1,165 @@ +const { ethers } = require("ethers"); + +const API_BASE = (process.env.SECRET_API_BASE_URL || "https://api.edut.ai").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(); + +function required(name, value) { + if (!value) { + throw new Error(`Missing required env: ${name}`); + } + return value; +} + +async function postJSON(path, payload) { + const res = await fetch(`${API_BASE}${path}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), + }); + const text = await res.text(); + let data; + try { + data = text ? JSON.parse(text) : {}; + } catch (err) { + throw new Error(`${path} returned non-json response: ${text}`); + } + if (!res.ok) { + throw new Error(`${path} failed (${res.status}): ${JSON.stringify(data)}`); + } + return data; +} + +async function getJSON(path) { + const res = await fetch(`${API_BASE}${path}`); + const text = await res.text(); + let data; + try { + data = text ? JSON.parse(text) : {}; + } catch (err) { + throw new Error(`${path} returned non-json response: ${text}`); + } + if (!res.ok) { + throw new Error(`${path} failed (${res.status}): ${JSON.stringify(data)}`); + } + return data; +} + +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 address = await signer.getAddress(); + console.log("wallet:", address); + console.log("chain_id:", CHAIN_ID); + console.log("api_base:", API_BASE); + + const intent = await postJSON("/secret/wallet/intent", { + address, + origin: ORIGIN, + locale: LOCALE, + chain_id: CHAIN_ID, + }); + console.log("intent_id:", intent.intent_id); + console.log("designation_code:", intent.designation_code); + console.log("display_token:", intent.display_token); + + 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 postJSON("/secret/wallet/verify", { + intent_id: intent.intent_id, + address, + chain_id: CHAIN_ID, + signature, + }); + console.log("verify_status:", verify.status); + + const quote = await postJSON("/secret/membership/quote", { + designation_code: intent.designation_code, + address, + chain_id: CHAIN_ID, + }); + console.log("quote_id:", quote.quote_id); + console.log("contract:", quote.contract_address); + console.log("value:", quote.value); + + const txReq = quote.tx || { + to: quote.contract_address, + data: quote.calldata, + value: quote.value, + }; + const txRequest = { + to: txReq.to, + data: txReq.data, + value: txReq.value, + }; + if (GAS_PRICE_WEI) { + txRequest.gasPrice = ethers.BigNumber.from(GAS_PRICE_WEI); + } + const tx = await signer.sendTransaction(txRequest); + console.log("submitted_tx:", tx.hash); + await tx.wait(1); + console.log("mined_tx:", tx.hash); + + const confirm = await postJSON("/secret/membership/confirm", { + designation_code: intent.designation_code, + quote_id: quote.quote_id, + tx_hash: tx.hash, + address, + chain_id: CHAIN_ID, + }); + console.log("confirm_status:", confirm.status); + + const status = await getJSON( + `/secret/membership/status?wallet=${encodeURIComponent(address)}` + ); + console.log("membership_status:", status.status); + + console.log( + JSON.stringify( + { + wallet: address, + designation_code: intent.designation_code, + quote_id: quote.quote_id, + tx_hash: tx.hash, + confirm_status: confirm.status, + membership_status: status.status, + }, + null, + 2 + ) + ); +} + +main().catch((err) => { + console.error(err.message || err); + process.exit(1); +});