feat: add AccountCenter component for user authentication, subscription, and payment management.
This commit is contained in:
@@ -81,6 +81,8 @@ type CreatedIntent = {
|
||||
to: string;
|
||||
data: string;
|
||||
value: string;
|
||||
amount_units: string;
|
||||
token_address: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -132,6 +134,27 @@ function shortAddress(address: string) {
|
||||
return `${text.slice(0, 8)}...${text.slice(-6)}`;
|
||||
}
|
||||
|
||||
function toPaddedHex(value: bigint) {
|
||||
return value.toString(16).padStart(64, "0");
|
||||
}
|
||||
|
||||
function toPaddedAddress(address: string) {
|
||||
return String(address || "")
|
||||
.toLowerCase()
|
||||
.replace(/^0x/, "")
|
||||
.padStart(64, "0");
|
||||
}
|
||||
|
||||
function buildAllowanceCalldata(owner: string, spender: string) {
|
||||
// allowance(address owner, address spender)
|
||||
return `0xdd62ed3e${toPaddedAddress(owner)}${toPaddedAddress(spender)}`;
|
||||
}
|
||||
|
||||
function buildApproveCalldata(spender: string, amount: bigint) {
|
||||
// approve(address spender, uint256 amount)
|
||||
return `0x095ea7b3${toPaddedAddress(spender)}${toPaddedHex(amount)}`;
|
||||
}
|
||||
|
||||
function InfoItem({ icon: Icon, label, value, status = "default" }: InfoItemProps) {
|
||||
return (
|
||||
<div className="group flex items-center justify-between rounded-2xl border border-white/5 bg-white/5 p-4 transition-all hover:bg-white/10">
|
||||
@@ -324,6 +347,30 @@ export function AccountCenter() {
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const waitForReceipt = async (
|
||||
txHash: string,
|
||||
timeoutMs = 120000,
|
||||
pollMs = 3000,
|
||||
) => {
|
||||
const eth = window.ethereum;
|
||||
if (!eth) {
|
||||
throw new Error("MetaMask not found");
|
||||
}
|
||||
const started = Date.now();
|
||||
while (Date.now() - started < timeoutMs) {
|
||||
const receipt = (await eth.request({
|
||||
method: "eth_getTransactionReceipt",
|
||||
params: [txHash],
|
||||
})) as { status?: string } | null;
|
||||
if (receipt && receipt.status) {
|
||||
if (receipt.status === "0x1") return receipt;
|
||||
throw new Error(`transaction reverted: ${txHash}`);
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
||||
}
|
||||
throw new Error(`transaction confirmation timeout: ${txHash}`);
|
||||
};
|
||||
|
||||
const planList = paymentConfig?.plans || [];
|
||||
const selectedPlan = planList.find((p) => p.plan_code === selectedPlanCode) || planList[0];
|
||||
const paymentFeatureReady = Boolean(paymentConfig?.enabled && paymentConfig?.configured);
|
||||
@@ -448,6 +495,43 @@ export function AccountCenter() {
|
||||
}
|
||||
setLastIntentId(intentId);
|
||||
|
||||
const tokenAddress = String(txPayload.token_address || "").toLowerCase();
|
||||
const amountUnits = BigInt(String(txPayload.amount_units || "0"));
|
||||
if (!tokenAddress.startsWith("0x") || amountUnits <= 0n) {
|
||||
throw new Error("intent token/amount invalid");
|
||||
}
|
||||
|
||||
const allowanceHex = (await eth.request({
|
||||
method: "eth_call",
|
||||
params: [
|
||||
{
|
||||
to: tokenAddress,
|
||||
data: buildAllowanceCalldata(payingWallet, txPayload.to),
|
||||
},
|
||||
"latest",
|
||||
],
|
||||
})) as string;
|
||||
const allowance = BigInt(String(allowanceHex || "0x0"));
|
||||
|
||||
if (allowance < amountUnits) {
|
||||
setPaymentInfo("检测到授权不足,正在发起 USDC 授权...");
|
||||
const approveHash = (await eth.request({
|
||||
method: "eth_sendTransaction",
|
||||
params: [
|
||||
{
|
||||
from: payingWallet,
|
||||
to: tokenAddress,
|
||||
data: buildApproveCalldata(txPayload.to, amountUnits),
|
||||
value: "0x0",
|
||||
},
|
||||
],
|
||||
})) as string;
|
||||
await waitForReceipt(String(approveHash || ""));
|
||||
setPaymentInfo("USDC 授权成功,正在发起支付...");
|
||||
} else {
|
||||
setPaymentInfo("授权额度充足,正在发起支付...");
|
||||
}
|
||||
|
||||
const txHash = (await eth.request({
|
||||
method: "eth_sendTransaction",
|
||||
params: [
|
||||
|
||||
Reference in New Issue
Block a user