· 大約1個月 ago
import { Controller } from "@hotwired/stimulus"
export default class extends Controller { static values = { url: String }
async pay() { if (!window.ethereum) { alert("⚠️ 请先安装钱包") return }
try {
// 1️⃣ 请求资源
const res = await fetch(this.urlValue)
const payInfo = await res.json()
if (res.status !== 402) {
alert("✅ 已解锁或无需支付")
return
}
// 2️⃣ 获取支付要求
const accept = payInfo.accepts[0]
const { payTo, asset, maxAmountRequired, network, scheme, extra } = accept
console.log("🧾 支付要求:", accept)
console.log("📦 Extra 信息:", extra)
// 3️⃣ 连接钱包
const accounts = await window.ethereum.request({
method: "eth_requestAccounts"
})
const from = accounts[0]
// 4️⃣ 生成 nonce 和时间戳
const nonce = this.generateNonce()
const timestamp = Math.floor(Date.now() / 1000)
const validAfter = timestamp - 300
const validBefore = timestamp + 600
// 5️⃣ 获取 Chain ID
const chainId = await this.getChainId()
console.log("🔗 Chain ID:", chainId)
// 6️⃣ 读取合约信息(调试用)
await this.debugContractInfo(asset)
// 7️⃣ 构造 EIP-712 签名数据(尝试多个配置)
const domainConfigs = [
// 配置 1: 使用 extra 信息
{
name: extra?.name || "USD Coin",
version: extra?.version?.toString() || "2",
chainId: chainId,
verifyingContract: asset
},
// 配置 2: 标准 USDC
{
name: "USD Coin",
version: "2",
chainId: chainId,
verifyingContract: asset
},
// 配置 3: FiatToken
{
name: "FiatToken",
version: "2",
chainId: chainId,
verifyingContract: asset
}
]
// 尝试第一个配置
const domain = domainConfigs[0]
console.log("🏛️ Domain 配置:")
console.log(JSON.stringify(domain, null, 2))
const types = {
TransferWithAuthorization: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" },
{ name: "nonce", type: "bytes32" }
]
}
const message = {
from: from,
to: payTo,
value: maxAmountRequired,
validAfter: validAfter,
validBefore: validBefore,
nonce: nonce
}
console.log("📝 签名消息:")
console.log(JSON.stringify(message, null, 2))
// 8️⃣ 请求用户签名
const signature = await window.ethereum.request({
method: "eth_signTypedData_v4",
params: [
from,
JSON.stringify({
domain,
types,
primaryType: "TransferWithAuthorization",
message
})
]
})
console.log("✍️ 签名完成:", signature)
// 9️⃣ 构造 Payment Payload
const paymentPayload = {
x402Version: 1,
scheme: scheme || "exact",
network: network,
payload: {
authorization: {
from: from,
to: payTo,
value: maxAmountRequired.toString(),
validAfter: validAfter.toString(),
validBefore: validBefore.toString(),
nonce: nonce
},
signature: signature
}
}
console.log("=" .repeat(60))
console.log("🔐 Payment Payload:")
console.log(JSON.stringify(paymentPayload, null, 2))
console.log("=" .repeat(60))
// 🔟 编码并发送
const encoded = btoa(JSON.stringify(paymentPayload))
const verifyRes = await fetch(this.urlValue, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-PAYMENT": encoded
},
body: JSON.stringify({})
})
console.log("📬 响应状态:", verifyRes.status)
if (verifyRes.ok) {
const data = await verifyRes.json()
alert("✅ 支付成功!内容已解锁")
console.log("📄 内容:", data)
window.location.reload()
} else {
const err = await verifyRes.text()
console.error("❌ 验证失败:", err)
alert(`❌ 验证失败: ${err}`)
}
} catch (e) {
console.error("💥 出错:", e)
if (e.code === 4001) {
alert("❌ 您拒绝了签名")
} else {
alert("💥 支付失败: " + e.message)
}
}
}
// 调试:读取合约信息 async debugContractInfo(asset) { try { console.log("🔍 读取合约信息...")
// name()
const nameData = "0x06fdde03"
const nameResult = await window.ethereum.request({
method: "eth_call",
params: [{ to: asset, data: nameData }, "latest"]
})
console.log(" name() 原始结果:", nameResult)
// version()
const versionData = "0x54fd4d50"
const versionResult = await window.ethereum.request({
method: "eth_call",
params: [{ to: asset, data: versionData }, "latest"]
})
console.log(" version() 原始结果:", versionResult)
// DOMAIN_SEPARATOR()
const domainData = "0x3644e515"
const domainResult = await window.ethereum.request({
method: "eth_call",
params: [{ to: asset, data: domainData }, "latest"]
})
console.log(" DOMAIN_SEPARATOR():", domainResult)
} catch (e) {
console.warn("读取合约信息失败(可忽略):", e.message)
}
}
generateNonce() { const array = new Uint8Array(32) crypto.getRandomValues(array) return "0x" + Array.from(array) .map(b => b.toString(16).padStart(2, "0")) .join("") }
async getChainId() { const chainIdHex = await window.ethereum.request({ method: "eth_chainId" }) return parseInt(chainIdHex, 16) } }
與您的關注者分享。
回覆