diff --git a/api/handler_ai_model.go b/api/handler_ai_model.go
index 91178dde..15ffce3d 100644
--- a/api/handler_ai_model.go
+++ b/api/handler_ai_model.go
@@ -220,10 +220,8 @@ func (s *Server) handleGetSupportedModels(c *gin.Context) {
{"id": "gemini", "name": "Google Gemini", "provider": "gemini", "defaultModel": "gemini-3-pro-preview"},
{"id": "grok", "name": "Grok (xAI)", "provider": "grok", "defaultModel": "grok-3-latest"},
{"id": "kimi", "name": "Kimi (Moonshot)", "provider": "kimi", "defaultModel": "moonshot-v1-auto"},
- {"id": "minimax", "name": "MiniMax", "provider": "minimax", "defaultModel": "MiniMax-M2.5"},
- {"id": "blockrun-base", "name": "BlockRun (Base Wallet)", "provider": "blockrun-base", "defaultModel": "auto"},
- {"id": "blockrun-sol", "name": "BlockRun (Solana Wallet)", "provider": "blockrun-sol", "defaultModel": "auto"},
- {"id": "claw402", "name": "Claw402 (Base USDC)", "provider": "claw402", "defaultModel": "deepseek"},
+ {"id": "minimax", "name": "MiniMax", "provider": "minimax", "defaultModel": "MiniMax-M2.7"},
+ {"id": "claw402", "name": "Claw402 (Base USDC)", "provider": "claw402", "defaultModel": "deepseek-v4-flash"},
}
c.JSON(http.StatusOK, supportedModels)
diff --git a/api/handler_onboarding.go b/api/handler_onboarding.go
index 7989ed56..27151bb4 100644
--- a/api/handler_onboarding.go
+++ b/api/handler_onboarding.go
@@ -10,6 +10,7 @@ import (
"strings"
"nofx/logger"
+ "nofx/mcp/payment"
"nofx/wallet"
gethcrypto "github.com/ethereum/go-ethereum/crypto"
@@ -54,7 +55,7 @@ func (s *Server) handleBeginnerOnboarding(c *gin.Context) {
}
if !reusedExisting {
- if err := s.store.AIModel().Update(userID, "claw402", true, privateKey, "", "glm-5"); err != nil {
+ if err := s.store.AIModel().Update(userID, "claw402", true, privateKey, "", payment.DefaultClaw402Model); err != nil {
logger.Errorf("Failed to save beginner claw402 config for user %s: %v", userID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save beginner model configuration"})
return
@@ -68,7 +69,7 @@ func (s *Server) handleBeginnerOnboarding(c *gin.Context) {
os.Setenv("CLAW402_WALLET_KEY", privateKey)
os.Setenv("CLAW402_WALLET_ADDRESS", address)
- os.Setenv("CLAW402_DEFAULT_MODEL", "glm-5")
+ os.Setenv("CLAW402_DEFAULT_MODEL", payment.DefaultClaw402Model)
envSaved, envPath, envErr := persistBeginnerWalletEnv(privateKey, address)
resp := beginnerOnboardingResponse{
@@ -77,7 +78,7 @@ func (s *Server) handleBeginnerOnboarding(c *gin.Context) {
Chain: "base",
Asset: "USDC",
Provider: "claw402",
- DefaultModel: "glm-5",
+ DefaultModel: payment.DefaultClaw402Model,
ConfiguredModelID: configuredModelID,
BalanceUSDC: wallet.QueryUSDCBalanceStr(address),
EnvSaved: envSaved,
@@ -253,7 +254,7 @@ func persistBeginnerWalletEnv(privateKey string, address string) (bool, string,
if err := upsertEnvFile(path, map[string]string{
"CLAW402_WALLET_KEY": privateKey,
"CLAW402_WALLET_ADDRESS": address,
- "CLAW402_DEFAULT_MODEL": "glm-5",
+ "CLAW402_DEFAULT_MODEL": payment.DefaultClaw402Model,
}); err != nil {
lastErr = err
continue
diff --git a/mcp/payment/claw402.go b/mcp/payment/claw402.go
index 9d628f54..871cc765 100644
--- a/mcp/payment/claw402.go
+++ b/mcp/payment/claw402.go
@@ -50,7 +50,7 @@ func shortAddr(addr string) string {
const (
DefaultClaw402URL = "https://claw402.ai"
- DefaultClaw402Model = "glm-5"
+ DefaultClaw402Model = "deepseek-v4-flash"
)
// claw402ModelEndpoints maps user-friendly model names to claw402 API paths.
diff --git a/web/src/components/trader/ModelConfigModal.tsx b/web/src/components/trader/ModelConfigModal.tsx
index d13bdcb6..6e8ea9f9 100644
--- a/web/src/components/trader/ModelConfigModal.tsx
+++ b/web/src/components/trader/ModelConfigModal.tsx
@@ -11,6 +11,7 @@ import {
BLOCKRUN_MODELS,
CLAW402_MODELS,
AI_PROVIDER_CONFIG,
+ DEFAULT_CLAW402_MODEL,
getShortName,
} from './model-constants'
@@ -74,32 +75,74 @@ export function ModelConfigModal({
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
- if (!selectedModelId || !apiKey.trim()) return
- onSave(selectedModelId, apiKey.trim(), baseUrl.trim() || undefined, modelName.trim() || undefined)
+ if (!selectedModelId) return
+ const key = apiKey.trim()
+ // Allow empty key when editing an existing model (backend preserves existing key)
+ if (!key && !editingModelId) return
+ const trimmedModelName = modelName.trim()
+ const resolvedModelName =
+ selectedModel?.provider === 'claw402'
+ ? trimmedModelName || DEFAULT_CLAW402_MODEL
+ : trimmedModelName || undefined
+ onSave(selectedModelId, key, baseUrl.trim() || undefined, resolvedModelName)
}
const availableModels = allModels || []
- const configuredIds = new Set(configuredModels?.map(m => m.id) || [])
- const stepLabels = [t('modelConfig.selectModel', language), t('modelConfig.configureApi', language)]
+ const configuredIds = new Set(configuredModels?.map((m) => m.id) || [])
+ const isClaw402Selected =
+ selectedModel?.provider === 'claw402' || selectedModel?.id === 'claw402'
+ const isBeginnerDefaultModel =
+ isClaw402Selected && getUserMode() === 'beginner'
+ const stepLabels = [
+ t('modelConfig.selectModel', language),
+ t(
+ !selectedModel
+ ? 'modelConfig.configure'
+ : isClaw402Selected
+ ? 'modelConfig.configureWallet'
+ : 'modelConfig.configure',
+ language
+ ),
+ ]
return (
{/* Header */}
{currentStep > 0 && !editingModelId && (
-
@@ -113,7 +156,12 @@ export function ModelConfigModal({
)}
-
@@ -127,7 +175,10 @@ export function ModelConfigModal({
)}
{/* Content */}
-
+
{/* Step 0: Select Model */}
{currentStep === 0 && !editingModelId && (
- )}
+ {(currentStep === 1 || editingModelId) &&
+ selectedModel &&
+ (selectedModel.provider === 'claw402' ||
+ selectedModel.id === 'claw402') && (
+
+ )}
{/* Step 1: Configure — Standard Providers (non-claw402) */}
- {(currentStep === 1 || editingModelId) && selectedModel && selectedModel.provider !== 'claw402' && selectedModel.id !== 'claw402' && (
-
- )}
+ {(currentStep === 1 || editingModelId) &&
+ selectedModel &&
+ selectedModel.provider !== 'claw402' &&
+ selectedModel.id !== 'claw402' && (
+
+ )}
@@ -205,17 +263,41 @@ function ModelSelectionStep({
if (claw) onSelectModel(claw.id)
}}
className="w-full p-5 rounded-xl text-left transition-all hover:scale-[1.01]"
- style={{ background: 'linear-gradient(135deg, rgba(37, 99, 235, 0.15) 0%, rgba(139, 92, 246, 0.15) 100%)', border: '1.5px solid rgba(37, 99, 235, 0.4)' }}
+ style={{
+ background:
+ 'linear-gradient(135deg, rgba(37, 99, 235, 0.15) 0%, rgba(139, 92, 246, 0.15) 100%)',
+ border: '1.5px solid rgba(37, 99, 235, 0.4)',
+ }}
>
-

+
-
+
{t('modelConfig.payPerCall', language)}
@@ -223,54 +305,97 @@ function ModelSelectionStep({
- {configuredIds.has(availableModels.find(m => m.provider === 'claw402')?.id || '') && (
-
+ {configuredIds.has(claw402Model.id) && (
+
)}
-
+
{'🔥 ' + t('modelConfig.recommended', language)}
-
+
GPT · Claude · DeepSeek · Gemini · Grok · Qwen · Kimi
+
+ {t('modelConfig.claw402EntryDesc', language)}
+
)}
-
- {availableModels.filter(m => !m.provider?.startsWith('blockrun') && m.provider !== 'claw402').map((model) => (
- onSelectModel(model.id)}
- configured={configuredIds.has(model.id)}
- />
- ))}
-
- {availableModels.some(m => m.provider?.startsWith('blockrun')) && (
- <>
-
-
-
- {t('modelConfig.viaBlockrunWallet', language)}
-
-
-
-
- {availableModels.filter(m => m.provider?.startsWith('blockrun')).map((model) => (
- onSelectModel(model.id)}
- configured={configuredIds.has(model.id)}
- />
- ))}
-
- >
+ {otherProviders.length > 0 && (
+
+
setShowOtherProviders((prev) => !prev)}
+ className="w-full flex items-center justify-between px-4 py-4 text-left transition-all hover:bg-white/5"
+ >
+
+
+ {t('modelConfig.otherApiEntry', language)}
+
+
+ {t('modelConfig.otherApiEntryDesc', language)}
+
+
+
+
+ {otherProviders.length} API
+
+
+ {showOtherProviders ? '−' : '+'}
+
+
+
+
+ {showOtherProviders && (
+
+
+ {otherProviders.map((model) => (
+ onSelectModel(model.id)}
+ configured={configuredIds.has(model.id)}
+ />
+ ))}
+
+
+ {t('modelConfig.modelsConfigured', language)}
+
+
+ )}
+
)}
{t('modelConfig.modelsConfigured', language)}
@@ -307,21 +432,71 @@ function Claw402ConfigForm({
const [keyError, setKeyError] = useState('')
const [validating, setValidating] = useState(false)
const [claw402Status, setClaw402Status] = useState
(null)
- const [testResult, setTestResult] = useState<{ status: string; message: string } | null>(null)
+ const [testResult, setTestResult] = useState<{
+ status: string
+ message: string
+ } | null>(null)
const [testing, setTesting] = useState(false)
+ const [serverWalletAddress, setServerWalletAddress] = useState('')
+ const [serverWalletBalance, setServerWalletBalance] = useState(
+ null
+ )
+ const localWalletAddress = getBeginnerWalletAddress()?.trim() || ''
+ const configuredWalletAddress =
+ configuredModel?.walletAddress?.trim() ||
+ localWalletAddress ||
+ serverWalletAddress
+ const resolvedWalletAddress = walletAddress || configuredWalletAddress
+ const resolvedUsdcBalance =
+ usdcBalance ?? configuredModel?.balanceUsdc ?? serverWalletBalance ?? null
+ const hasExistingWallet = Boolean(configuredWalletAddress)
// Client-side validation helper
const getClientError = (key: string): string => {
if (!key) return ''
- if (!key.startsWith('0x')) return t('modelConfig.invalidKeyPrefix', language)
- if (key.length !== 66) return `${t('modelConfig.invalidKeyLength', language)} ${key.length}`
- if (!/^0x[0-9a-fA-F]{64}$/.test(key)) return t('modelConfig.invalidKeyChars', language)
+ if (!key.startsWith('0x'))
+ return t('modelConfig.invalidKeyPrefix', language)
+ if (key.length !== 66)
+ return `${t('modelConfig.invalidKeyLength', language)} ${key.length}`
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key))
+ return t('modelConfig.invalidKeyChars', language)
return ''
}
- const isKeyValid = apiKey.length === 66 && apiKey.startsWith('0x') && /^0x[0-9a-fA-F]{64}$/.test(apiKey)
+ const isKeyValid =
+ apiKey.length === 66 &&
+ apiKey.startsWith('0x') &&
+ /^0x[0-9a-fA-F]{64}$/.test(apiKey)
- // Truncate address for display
+ useEffect(() => {
+ if (hasExistingWallet) {
+ setShowDeposit(true)
+ }
+ }, [hasExistingWallet])
+
+ useEffect(() => {
+ if (
+ configuredModel?.walletAddress ||
+ localWalletAddress ||
+ serverWalletAddress
+ ) {
+ return
+ }
+
+ let cancelled = false
+ void api
+ .getCurrentBeginnerWallet()
+ .then((result) => {
+ setClaw402Status(result.claw402_status || 'unknown')
+ if (cancelled || !result.found || !result.address) {
+ return
+ }
+ setServerWalletAddress(result.address)
+ setServerWalletBalance(result.balance_usdc || null)
+ })
+ .catch(() => {
+ // Ignore silently: this is a best-effort fallback for showing the current wallet.
+ })
// Debounced validation when apiKey changes
@@ -370,6 +545,24 @@ function Claw402ConfigForm({
setTesting(true)
setTestResult(null)
try {
+ if (!apiKey && hasExistingWallet) {
+ const result = await api.getCurrentBeginnerWallet()
+ setClaw402Status(result.claw402_status || 'unknown')
+ if (result.found && result.address) {
+ setWalletAddress(result.address)
+ setUsdcBalance(result.balance_usdc || '0.00')
+ setShowDeposit(true)
+ }
+ setTestResult({
+ status: result.claw402_status === 'ok' ? 'ok' : 'error',
+ message:
+ result.claw402_status === 'ok'
+ ? t('modelConfig.claw402Connected', language)
+ : t('modelConfig.claw402Unreachable', language),
+ })
+ return
+ }
+
const res = await fetch('/api/wallet/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -383,15 +576,19 @@ function Claw402ConfigForm({
if (parseFloat(data.balance_usdc || '0') === 0) setShowDeposit(true)
setTestResult({
status: data.claw402_status === 'ok' ? 'ok' : 'error',
- message: data.claw402_status === 'ok'
- ? t('modelConfig.claw402Connected', language)
- : t('modelConfig.claw402Unreachable', language),
+ message:
+ data.claw402_status === 'ok'
+ ? t('modelConfig.claw402Connected', language)
+ : t('modelConfig.claw402Unreachable', language),
})
} else {
setTestResult({ status: 'error', message: data.error || 'Invalid key' })
}
} catch {
- setTestResult({ status: 'error', message: t('modelConfig.claw402Unreachable', language) })
+ setTestResult({
+ status: 'error',
+ message: t('modelConfig.claw402Unreachable', language),
+ })
} finally {
setTesting(false)
}
@@ -402,28 +599,84 @@ function Claw402ConfigForm({
return (