contracts/scripts/e2e-membership-flow.cjs

190 lines
5.6 KiB
JavaScript

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 value = ethers.BigNumber.from(txReq.value || "0x0");
const feeData = await provider.getFeeData();
const configuredGasPrice = GAS_PRICE_WEI ? ethers.BigNumber.from(GAS_PRICE_WEI) : null;
const effectiveGasPrice =
configuredGasPrice || feeData.gasPrice || feeData.maxFeePerGas || ethers.BigNumber.from("0");
const gasUnitsReserve = ethers.BigNumber.from("250000");
const estimatedGasCost = effectiveGasPrice.mul(gasUnitsReserve);
const totalNeeded = value.add(estimatedGasCost);
const balance = await provider.getBalance(address);
if (balance.lt(totalNeeded)) {
const deficit = totalNeeded.sub(balance);
throw new Error(
[
"insufficient funded balance for membership mint preflight",
`balance_wei=${balance.toString()}`,
`required_min_wei=${totalNeeded.toString()}`,
`deficit_wei=${deficit.toString()}`,
`mint_value_wei=${value.toString()}`,
`gas_reserve_wei=${estimatedGasCost.toString()}`,
].join(" ")
);
}
const txRequest = {
to: txReq.to,
data: txReq.data,
value: 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);
});