2026-02-10 11:32:49 -08:00
package wallet
import (
"context"
"fmt"
2026-05-22 21:55:42 -07:00
"os"
"strings"
"time"
2026-02-10 11:32:49 -08:00
)
2026-05-22 21:55:42 -07:00
const (
WalletConnectStatusStub = "stub"
2026-05-23 02:21:37 -07:00
WalletConnectStatusClient = "client"
2026-05-22 21:55:42 -07:00
WalletConnectStatusDisabled = "disabled"
)
// Config describes the public WalletConnect v2 posture exposed to clients.
type Config struct {
Status string ` json:"status" `
Enabled bool ` json:"enabled" `
ProjectID string ` json:"projectId" `
RelayURL string ` json:"relayUrl" `
MetadataURL string ` json:"metadataUrl" `
RequiredNamespaces [ ] string ` json:"requiredNamespaces" `
SupportedChains [ ] int ` json:"supportedChains" `
FallbackAuth string ` json:"fallbackAuth" `
Message string ` json:"message" `
UpdatedAt string ` json:"updatedAt" `
}
// ConnectResponse is returned while WalletConnect session bridging remains a stub.
type ConnectResponse struct {
Status string ` json:"status" `
Enabled bool ` json:"enabled" `
URI string ` json:"uri,omitempty" `
SessionID string ` json:"sessionId,omitempty" `
ExpiresAt string ` json:"expiresAt,omitempty" `
FallbackAuth string ` json:"fallbackAuth" `
Message string ` json:"message" `
}
// Session represents a wallet session snapshot for future WalletConnect integration.
type Session struct {
SessionID string ` json:"sessionId" `
Address string ` json:"address,omitempty" `
ChainID int ` json:"chainId,omitempty" `
Connected bool ` json:"connected" `
Status string ` json:"status" `
Message string ` json:"message" `
}
// WalletConnect handles WalletConnect v2 integration posture for the explorer API.
2026-02-10 11:32:49 -08:00
type WalletConnect struct {
projectID string
2026-05-22 21:55:42 -07:00
relayURL string
chainID int
2026-02-10 11:32:49 -08:00
}
2026-05-22 21:55:42 -07:00
// NewWalletConnect creates a WalletConnect handler using deployment env vars.
func NewWalletConnect ( chainID int ) * WalletConnect {
projectID := strings . TrimSpace ( os . Getenv ( "WALLETCONNECT_PROJECT_ID" ) )
relayURL := strings . TrimSpace ( os . Getenv ( "WALLETCONNECT_RELAY_URL" ) )
if relayURL == "" {
relayURL = "wss://relay.walletconnect.org"
}
return & WalletConnect {
projectID : projectID ,
relayURL : relayURL ,
chainID : chainID ,
}
2026-02-10 11:32:49 -08:00
}
2026-05-22 21:55:42 -07:00
func ( wc * WalletConnect ) enabled ( ) bool {
return wc . projectID != ""
2026-02-10 11:32:49 -08:00
}
2026-05-22 21:55:42 -07:00
// PublicConfig returns the read-only WalletConnect config surface for clients.
func ( wc * WalletConnect ) PublicConfig ( ) Config {
2026-05-23 02:21:37 -07:00
status := WalletConnectStatusDisabled
if wc . enabled ( ) {
status = WalletConnectStatusClient
2026-05-22 21:55:42 -07:00
}
return Config {
Status : status ,
Enabled : wc . enabled ( ) ,
ProjectID : wc . projectID ,
RelayURL : wc . relayURL ,
MetadataURL : "/api/v1/walletconnect/metadata" ,
RequiredNamespaces : [ ] string { "eip155" } ,
SupportedChains : [ ] int { wc . chainID , 1 } ,
FallbackAuth : "/api/v1/auth/wallet" ,
Message : wc . publicMessage ( ) ,
UpdatedAt : time . Now ( ) . UTC ( ) . Format ( time . RFC3339 ) ,
}
2026-02-10 11:32:49 -08:00
}
2026-05-22 21:55:42 -07:00
func ( wc * WalletConnect ) publicMessage ( ) string {
if wc . enabled ( ) {
2026-05-23 02:21:37 -07:00
return "WalletConnect v2 is enabled. Use the WalletConnect button on /wallet for mobile QR pairing; browser extension wallets can continue using /api/v1/auth/wallet."
2026-05-22 21:55:42 -07:00
}
return "WalletConnect v2 is not configured. Set WALLETCONNECT_PROJECT_ID to publish relay config; browser wallet auth remains available at /api/v1/auth/wallet."
2026-02-10 11:32:49 -08:00
}
2026-05-23 02:21:37 -07:00
// Connect reports client-side WalletConnect posture. Pairing runs in the browser when projectId is published.
2026-05-22 21:55:42 -07:00
func ( wc * WalletConnect ) Connect ( _ context . Context ) ( * ConnectResponse , error ) {
2026-05-23 02:21:37 -07:00
if ! wc . enabled ( ) {
return & ConnectResponse {
Status : WalletConnectStatusDisabled ,
Enabled : false ,
FallbackAuth : "/api/v1/auth/wallet" ,
Message : wc . publicMessage ( ) ,
} , fmt . Errorf ( "walletconnect is disabled" )
}
2026-05-22 21:55:42 -07:00
return & ConnectResponse {
2026-05-23 02:21:37 -07:00
Status : "client" ,
Enabled : true ,
2026-05-22 21:55:42 -07:00
FallbackAuth : "/api/v1/auth/wallet" ,
2026-05-23 02:21:37 -07:00
Message : "Initialize WalletConnect in the browser via /wallet using the published projectId; authenticate with /api/v1/auth/wallet after pairing." ,
} , nil
2026-05-22 21:55:42 -07:00
}
2026-05-23 02:21:37 -07:00
// GetSession returns a registered browser-paired WalletConnect session snapshot.
2026-05-22 21:55:42 -07:00
func ( wc * WalletConnect ) GetSession ( _ context . Context , sessionID string ) ( * Session , error ) {
if strings . TrimSpace ( sessionID ) == "" {
return nil , fmt . Errorf ( "session id is required" )
}
2026-05-23 02:21:37 -07:00
if session , ok := lookupWalletConnectSession ( sessionID ) ; ok {
return session , nil
}
2026-05-22 21:55:42 -07:00
return & Session {
SessionID : sessionID ,
Connected : false ,
2026-05-23 02:21:37 -07:00
Status : WalletConnectStatusClient ,
Message : "Session not registered yet. Pair on /wallet, then POST /api/v1/walletconnect/session with sessionId and address." ,
} , fmt . Errorf ( "walletconnect session not found" )
}
// RegisterSession stores a client-paired WalletConnect session for operator lookup.
func ( wc * WalletConnect ) RegisterSession ( _ context . Context , sessionID , address string , chainID int ) ( * Session , error ) {
if strings . TrimSpace ( sessionID ) == "" {
return nil , fmt . Errorf ( "session id is required" )
}
if ! strings . HasPrefix ( strings . ToLower ( address ) , "0x" ) || len ( address ) != 42 {
return nil , fmt . Errorf ( "valid address is required" )
}
if chainID <= 0 {
chainID = wc . chainID
}
return RegisterClientSession ( sessionID , address , chainID ) , nil
2026-05-22 21:55:42 -07:00
}