feat: add AccountCenter component for user authentication, subscription, and payment management.
This commit is contained in:
@@ -81,6 +81,8 @@ type CreatedIntent = {
|
|||||||
to: string;
|
to: string;
|
||||||
data: string;
|
data: string;
|
||||||
value: 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)}`;
|
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) {
|
function InfoItem({ icon: Icon, label, value, status = "default" }: InfoItemProps) {
|
||||||
return (
|
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">
|
<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 {}
|
} 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 planList = paymentConfig?.plans || [];
|
||||||
const selectedPlan = planList.find((p) => p.plan_code === selectedPlanCode) || planList[0];
|
const selectedPlan = planList.find((p) => p.plan_code === selectedPlanCode) || planList[0];
|
||||||
const paymentFeatureReady = Boolean(paymentConfig?.enabled && paymentConfig?.configured);
|
const paymentFeatureReady = Boolean(paymentConfig?.enabled && paymentConfig?.configured);
|
||||||
@@ -448,6 +495,43 @@ export function AccountCenter() {
|
|||||||
}
|
}
|
||||||
setLastIntentId(intentId);
|
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({
|
const txHash = (await eth.request({
|
||||||
method: "eth_sendTransaction",
|
method: "eth_sendTransaction",
|
||||||
params: [
|
params: [
|
||||||
|
|||||||
Reference in New Issue
Block a user