Skip to content

Commit 6399d53

Browse files
balogh.adam@icloud.comclaude
andcommitted
Add EVM wallet signature verification endpoint
Add POST /api/verify/ethereum for EIP-191 personal-sign verification, mirroring the existing Solana flow. Works with any EVM chain. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent aafeed6 commit 6399d53

3 files changed

Lines changed: 56 additions & 11 deletions

File tree

api/api_types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ class SolanaVerifyRequest(BaseModel):
140140
signature: str
141141

142142

143+
class EvmVerifyRequest(BaseModel):
144+
address: str
145+
message: str
146+
signature: str
147+
148+
143149
class ProcessSwapRequest(BaseModel):
144150
txid: str
145151
chain: str = "solana"

server/fastapi_server.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
Message,
3030
TokenMetadata,
3131
SolanaVerifyRequest,
32+
EvmVerifyRequest,
3233
Context,
3334
UserMessage,
3435
ProcessSwapRequest,
@@ -262,6 +263,25 @@ async def verify_solana_signature(request: Request):
262263
logging.error(f"Error verifying SIWX signature: {e}")
263264
raise HTTPException(status_code=500, detail="Internal server error")
264265

266+
@app.post("/api/verify/ethereum")
267+
async def verify_evm_signature(request: Request):
268+
try:
269+
request_data = await request.json()
270+
verify_request = EvmVerifyRequest(**request_data)
271+
272+
token = await asyncio.to_thread(
273+
service.verify_evm_signature, verify_request
274+
)
275+
return {"token": token}
276+
277+
except ValidationError as e:
278+
raise HTTPException(status_code=400, detail=str(e))
279+
except ValueError as e:
280+
raise HTTPException(status_code=401, detail=str(e))
281+
except Exception as e:
282+
logging.error(f"Error verifying EVM signature: {e}")
283+
raise HTTPException(status_code=500, detail="Internal server error")
284+
265285
@app.get("/api/healthcheck")
266286
async def healthcheck():
267287
return {"status": "ok"}

server/service.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
from nacl.signing import VerifyKey
2-
from api.api_types import SolanaVerifyRequest
2+
from eth_account.messages import encode_defunct
3+
from eth_account import Account
4+
from api.api_types import SolanaVerifyRequest, EvmVerifyRequest
35
from base58 import b58decode
46
from server.firebase import auth
57

68

9+
def _firebase_custom_token(uid: str) -> str:
10+
custom_token = auth.create_custom_token(uid)
11+
12+
if isinstance(custom_token, bytes):
13+
token_bytes = custom_token
14+
elif isinstance(custom_token, str):
15+
token_bytes = custom_token.encode("utf-8")
16+
else:
17+
token_bytes = str(custom_token).encode("utf-8")
18+
19+
return token_bytes.decode("utf-8")
20+
21+
722
def verify_solana_signature(verify_request: SolanaVerifyRequest) -> str:
823
try:
924
public_key = b58decode(verify_request.address)
@@ -14,15 +29,19 @@ def verify_solana_signature(verify_request: SolanaVerifyRequest) -> str:
1429
verify_key.verify(message, signature)
1530

1631
uid = f"wallet_{verify_request.address}"
17-
custom_token = auth.create_custom_token(uid)
18-
19-
if isinstance(custom_token, bytes):
20-
token_bytes = custom_token
21-
elif isinstance(custom_token, str):
22-
token_bytes = custom_token.encode("utf-8")
23-
else:
24-
token_bytes = str(custom_token).encode("utf-8")
25-
26-
return token_bytes.decode("utf-8")
32+
return _firebase_custom_token(uid)
2733
except Exception:
2834
raise
35+
36+
37+
def verify_evm_signature(verify_request: EvmVerifyRequest) -> str:
38+
claimed_address = verify_request.address.lower()
39+
40+
signable = encode_defunct(text=verify_request.message)
41+
recovered = Account.recover_message(signable, signature=verify_request.signature)
42+
43+
if recovered.lower() != claimed_address:
44+
raise ValueError("Recovered address does not match claimed address")
45+
46+
uid = f"wallet_{claimed_address}"
47+
return _firebase_custom_token(uid)

0 commit comments

Comments
 (0)