-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathweb3-verify.ts
More file actions
97 lines (86 loc) · 2.61 KB
/
web3-verify.ts
File metadata and controls
97 lines (86 loc) · 2.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import bs58 from 'bs58'
import nacl from 'tweetnacl'
import type { Hex } from 'viem'
import { getAddress, verifyMessage } from 'viem'
const eip155Chain = 'eip155'
const solanaChain = 'solana'
export type Web3Chain = typeof eip155Chain | typeof solanaChain
export async function verifyWalletSignature({
chain,
message,
signature,
address,
}: {
chain: Web3Chain
message: string
signature: string
address: string
}): Promise<{ valid: boolean; normalizedAddress: string }> {
const normalized = normalizeAddress({ chain, address })
if (!normalized) return { valid: false, normalizedAddress: '' }
if (chain === eip155Chain)
try {
const valid = await verifyMessage({
address: normalized as `0x${string}`,
message,
signature: signature as Hex,
})
return { valid, normalizedAddress: normalized }
} catch {
return { valid: false, normalizedAddress: '' }
}
if (chain === solanaChain)
try {
const msgBytes = new TextEncoder().encode(message)
const sigBytes = bs58.decode(signature)
const pubkeyBytes = bs58.decode(normalized)
const valid = nacl.sign.detached.verify(msgBytes, sigBytes, pubkeyBytes)
return { valid, normalizedAddress: normalized }
} catch {
return { valid: false, normalizedAddress: '' }
}
return { valid: false, normalizedAddress: '' }
}
// Parse SIWE/SIWS message to extract address and nonce
export function parseSignInMessage(message: string): { address: string; nonce: string } | null {
// SIWE format: "domain wants you to sign in with your Ethereum account:\n{address}\n..."
// SIWS format: "domain wants you to sign in with your Solana account:\n{address}\n..."
const addrMatch = message.match(
/wants you to sign in with your (?:Ethereum|Solana) account:\s*\n([^\s\n]+)/i,
)
const nonceMatch = message.match(/Nonce:\s*(\S+)/i)
if (!addrMatch?.[1] || !nonceMatch?.[1]) return null
return { address: addrMatch[1].trim(), nonce: nonceMatch[1].trim() }
}
export function getCanonicalAddress({
chain,
address,
}: {
chain: Web3Chain
address: string
}): string | null {
return normalizeAddress({ chain, address })
}
function normalizeAddress({
chain,
address,
}: {
chain: Web3Chain
address: string
}): string | null {
if (chain === eip155Chain)
try {
return getAddress(address).toLowerCase()
} catch {
return null
}
if (chain === solanaChain)
try {
const decoded = bs58.decode(address)
if (decoded.length !== 32) return null
return bs58.encode(decoded) // Re-encode to normalize
} catch {
return null
}
return null
}