安全页重构:免密登录与活跃会话;按钮规范统一
CI / test (push) Successful in 53s

This commit is contained in:
2026-07-30 12:23:16 +08:00
parent d0fbecbd43
commit d675b950ee
43 changed files with 1944 additions and 475 deletions
+79
View File
@@ -0,0 +1,79 @@
/**
* 浏览器注入钱包(EIP-1193)接入:EIP-6963 多钱包发现 + personal_sign 封装。
* 不引钱包 SDK;WalletConnect 等远程签名方案不在支持范围。
*/
/** EIP-1193 provider 最小接口 */
export interface Eip1193Provider {
request(args: { method: string; params?: unknown[] }): Promise<unknown>
}
/** EIP-6963 announce 事件携带的钱包信息 */
export interface WalletDetail {
info: { uuid: string; name: string; icon: string; rdns: string }
provider: Eip1193Provider
}
interface Eip6963AnnounceEvent extends Event {
detail: WalletDetail
}
/** 兼容旧式单一注入的 window.ethereum */
declare global {
interface Window {
ethereum?: Eip1193Provider
}
}
/**
* 发现已安装的注入钱包:广播 EIP-6963 请求并收集 announce;
* 无响应时回退 window.ethereum(旧式单钱包注入)。
*/
export function discoverWallets(collectMs = 300): Promise<WalletDetail[]> {
return new Promise((resolve) => {
const found: WalletDetail[] = []
const onAnnounce = (e: Event) => {
const detail = (e as Eip6963AnnounceEvent).detail
if (detail?.provider && !found.some((w) => w.info.uuid === detail.info.uuid)) found.push(detail)
}
window.addEventListener('eip6963:announceProvider', onAnnounce)
window.dispatchEvent(new Event('eip6963:requestProvider'))
setTimeout(() => {
window.removeEventListener('eip6963:announceProvider', onAnnounce)
if (!found.length && window.ethereum) {
found.push({
info: { uuid: 'legacy', name: '浏览器钱包', icon: '', rdns: 'legacy.injected' },
provider: window.ethereum,
})
}
resolve(found)
}, collectMs)
})
}
/** 连接钱包并返回首个授权账户地址;用户拒绝时抛出钱包侧错误 */
export async function requestAccount(provider: Eip1193Provider): Promise<string> {
const accounts = (await provider.request({ method: 'eth_requestAccounts' })) as string[]
const addr = accounts?.[0]
if (!addr) throw new Error('钱包未返回账户地址')
return addr
}
/** 对消息做 personal_sign(EIP-191);返回 0x 前缀的 65 字节签名 hex */
export async function personalSign(
provider: Eip1193Provider,
message: string,
address: string,
): Promise<string> {
const sig = (await provider.request({
method: 'personal_sign',
params: [message, address],
})) as string
if (!sig || typeof sig !== 'string') throw new Error('钱包未返回签名')
return sig
}
/** 钱包侧用户拒绝(EIP-1193 code 4001);拒绝不作为错误提示 */
export function isUserRejected(e: unknown): boolean {
return typeof e === 'object' && e !== null && (e as { code?: number }).code === 4001
}