80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
/**
|
|
* 浏览器注入钱包(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
|
|
}
|