diff --git a/agents/error-catalog.json b/agents/error-catalog.json index 9249bfc..dd5dc2b 100644 --- a/agents/error-catalog.json +++ b/agents/error-catalog.json @@ -16,6 +16,10 @@ "category": "validation", "recovery": "Add --yes flag to confirm, or --dry-run to simulate without executing" }, + "ACCESS_CODE_REMOVED": { + "category": "validation", + "recovery": "Access and invite codes are no longer supported. Pass referral_code, or omit it to register with the default referral code" + }, "NOT_IMPLEMENTED": { "category": "validation", "recovery": "This feature is not yet available. Check for updates." @@ -124,10 +128,6 @@ "category": "config", "recovery": "MCP server failed to initialize. Check config and try again" }, - "REGISTER_API_FAILED": { - "category": "api", - "recovery": "Check your registration code and API URL. Run 'vulcan status' to verify connectivity" - }, "TRADERS_FETCH_FAILED": { "category": "api", "recovery": "Phoenix API unreachable. Run 'vulcan status' to check connectivity" diff --git a/agents/tool-catalog.json b/agents/tool-catalog.json index 57cb792..a9a573e 100644 --- a/agents/tool-catalog.json +++ b/agents/tool-catalog.json @@ -1121,27 +1121,15 @@ "name": "vulcan_account_register", "command": "vulcan account register", "group": "account", - "description": "Register and onboard a trader account. Omit a code for builder onboarding, pass referral_code for /v1/referral/activate-tx, or pass access_code/invite_code for allowlist activation.", + "description": "Register and onboard a trader account via /v1/referral/activate-tx. Omit referral_code to register with the default code.", "auth_required": true, "dangerous": true, "parameters": [ - { - "name": "access_code", - "type": "string", - "required": false, - "description": "Access code for registration" - }, { "name": "referral_code", "type": "string", "required": false, - "description": "Referral code for registration" - }, - { - "name": "invite_code", - "type": "string", - "required": false, - "description": "Backwards-compatible alias for access_code" + "description": "Referral code (optional; the default code is used when omitted)" }, { "name": "acknowledged", diff --git a/skills/vulcan-onboarding/SKILL.md b/skills/vulcan-onboarding/SKILL.md index 0ac33ae..7e1c6fb 100644 --- a/skills/vulcan-onboarding/SKILL.md +++ b/skills/vulcan-onboarding/SKILL.md @@ -62,7 +62,7 @@ Steps 2–6 below are for **live** setup only and should not run if the user has ## Prerequisites - Solana wallet with SOL (for transaction fees) and USDC (for collateral). -- An access code or referral code for Phoenix DEX registration. +- Optionally, a referral code for Phoenix DEX registration (registration also works without one). ## Step 1: Install and Configure @@ -73,7 +73,7 @@ vulcan agent health -o json # readiness and next steps vulcan agent mcp doctor --target cursor --scope user -o json ``` -Setup creates `~/.vulcan/config.toml`, checks trader registration status, can complete registration with no code, an access code, or a referral code, and can install read-only/paper MCP config. The Phoenix API session is signed in automatically using the configured wallet — no separate login step is required. +Setup creates `~/.vulcan/config.toml`, checks trader registration status, can complete registration with or without a referral code, and can install read-only/paper MCP config. The Phoenix API session is signed in automatically using the configured wallet — no separate login step is required. MCP is optional for paper and dry-run usage. For agent-driven live trading, prefer dangerous MCP with an unlocked session wallet: `vulcan agent mcp install --target cursor --dangerous`. Use this only after the user accepts that `VULCAN_WALLET_PASSWORD` may live in plaintext agent config. @@ -111,12 +111,11 @@ vulcan_wallet_balance → {} ## Step 4: Register Trader Account -Registration can be completed without a code through builder onboarding. If the user has a referral code, pass it so Vulcan uses the referral activation transaction flow; access codes remain supported for allowlist activation. The setup wizard can choose the path interactively: +A referral code is optional: when the user has one, pass it; when omitted, Vulcan registers with its default referral code. The setup wizard prompts for an optional referral code interactively: ```bash vulcan account register vulcan account register --referral-code -vulcan account register --access-code ``` For MCP: @@ -124,11 +123,9 @@ For MCP: ``` vulcan_account_register → { acknowledged: true } vulcan_account_register → { referral_code: "YOUR_CODE", acknowledged: true } -vulcan_account_register → { access_code: "YOUR_CODE", acknowledged: true } -vulcan_account_register → { invite_code: "YOUR_CODE", acknowledged: true } # backwards-compatible alias ``` -Registration submits a signed onboarding transaction for the default cross-margin subaccount. Referral codes use `/v1/referral/activate-tx`; no-code onboarding uses the exchange builder endpoints. If the trader is already registered, verify with `vulcan_account_info`. +Registration submits a signed onboarding transaction for the default cross-margin subaccount via `/v1/referral/activate-tx`; the wallet pays the transaction fee and trader-account rent, and the API adds the onboarder co-signature. If the trader is already registered, verify with `vulcan_account_info`. ## Step 5: Deposit Collateral @@ -170,6 +167,5 @@ Then place a small test trade. | `DECRYPT_FAILED` | Wrong password. Set `VULCAN_WALLET_PASSWORD` | | `NO_TRADER_ACCOUNT` | Register with `vulcan account register` | | `CONFIG_ERROR` | Run `vulcan setup` | -| `REGISTER_API_FAILED` | Check code validity and `api_url` in `~/.vulcan/config.toml` | | Insufficient SOL | Fund wallet with SOL for tx fees | | Insufficient USDC | Transfer USDC to wallet address | diff --git a/vulcan-lib/src/agent_log.rs b/vulcan-lib/src/agent_log.rs index aa34e80..4131678 100644 --- a/vulcan-lib/src/agent_log.rs +++ b/vulcan-lib/src/agent_log.rs @@ -367,8 +367,7 @@ pub fn summarize_args(args: &Value) -> Value { ); } "size" | "tokens" | "notional_usdc" | "price" | "tp" | "sl" | "collateral" - | "amount" | "access_code" | "referral_code" | "invite_code" | "api_key" - | "password" => { + | "amount" | "referral_code" | "api_key" | "password" => { out.insert(key.clone(), serde_json::json!({ "provided": true })); } "acknowledged" => {} @@ -1360,7 +1359,7 @@ mod tests { "symbol": "SOL", "side": "buy", "notional_usdc": 100, - "access_code": "SECRET", + "referral_code": "SECRET", "password": "WALLET_SECRET", "VULCAN_WALLET_PASSWORD": "ENV_SECRET", "private_key": "PRIVATE_SECRET", @@ -1371,7 +1370,7 @@ mod tests { assert_eq!(redacted["symbol"], "SOL"); assert_eq!(redacted["notional_usdc"]["provided"], true); assert!(redacted.get("acknowledged").is_none()); - assert_ne!(redacted["access_code"], "SECRET"); + assert_ne!(redacted["referral_code"], "SECRET"); let encoded = serde_json::to_string(&redacted).unwrap(); assert!(!encoded.contains("WALLET_SECRET")); assert!(!encoded.contains("ENV_SECRET")); diff --git a/vulcan-lib/src/cli/account.rs b/vulcan-lib/src/cli/account.rs index a81a32a..d69fb6c 100644 --- a/vulcan-lib/src/cli/account.rs +++ b/vulcan-lib/src/cli/account.rs @@ -1,26 +1,14 @@ //! Account subcommand definitions. -use clap::{ArgGroup, Subcommand}; +use clap::Subcommand; #[derive(Debug, Subcommand)] pub enum AccountCommand { /// Register a trader account on Phoenix - #[command(group( - ArgGroup::new("registration_code") - .args(["access_code", "referral_code", "invite_code"]) - ))] Register { - /// Access code for registration - #[arg(long)] - access_code: Option, - - /// Referral code for registration + /// Referral code (optional; the default code is used when omitted) #[arg(long)] referral_code: Option, - - /// Backwards-compatible alias for --access-code - #[arg(long)] - invite_code: Option, }, /// Show trader account details (PDA, subaccounts, margin mode) diff --git a/vulcan-lib/src/commands/account.rs b/vulcan-lib/src/commands/account.rs index 1a71112..cc67b87 100644 --- a/vulcan-lib/src/commands/account.rs +++ b/vulcan-lib/src/commands/account.rs @@ -8,8 +8,8 @@ use crate::wallet::ResolvedSigner; use phoenix_rise::accounts::owned::Permission; use phoenix_rise::accounts::permission::TRADER_ONBOARDING_PERMISSION; use phoenix_rise::api::{ - fetch_referral_activation_trader_status, ActivateReferralTxRequest, ApiInstructionResponse, - BuildRegisterIxsRequest, ReferralActivationTraderStatus, SendRegisterIxsRequest, TraderKey, + fetch_referral_activation_trader_status, ActivateReferralTxRequest, + ReferralActivationTraderStatus, TraderKey, }; use phoenix_rise::ix::onboard_trader_delegated::{ create_onboard_trader_delegated_ix, OnboardTraderDelegatedParams, @@ -22,6 +22,9 @@ use solana_rpc_client_api::config::RpcSimulateTransactionConfig; use std::str::FromStr; const CROSS_MARGIN_MAX_POSITIONS: u32 = 128; +/// Used when the user registers without providing a referral code; the +/// activate-tx endpoint requires one. +const DEFAULT_REFERRAL_CODE: &str = "VULCAN"; const LAMPORTS_PER_SOL: u64 = 1_000_000_000; const TRADER_HEADER_LEN: usize = 224; const TRADER_POSITION_MAP_PREFIX_LEN: usize = 16; @@ -37,11 +40,6 @@ pub struct RegisterResult { pub tx_signature: Option, } -pub enum RegistrationCode { - Access(String), - Referral(String), -} - pub(crate) async fn trader_onboarding_status( ctx: &AppContext, authority: &Pubkey, @@ -53,24 +51,6 @@ pub(crate) async fn trader_onboarding_status( .map_err(|e| VulcanError::network("TRADER_STATUS_FAILED", e.to_string())) } -fn parse_registration_code( - access_code: Option, - referral_code: Option, - invite_code: Option, -) -> Result, VulcanError> { - match (access_code, referral_code, invite_code) { - (None, None, None) => Ok(None), - (Some(code), None, None) | (None, None, Some(code)) => { - Ok(Some(RegistrationCode::Access(code))) - } - (None, Some(code), None) => Ok(Some(RegistrationCode::Referral(code))), - _ => Err(VulcanError::validation( - "REGISTRATION_CODE_CONFLICT", - "Provide at most one of --access-code, --referral-code, or --invite-code", - )), - } -} - fn trader_account_size(max_positions: u32) -> Result { let position_entries_len = TRADER_POSITION_ENTRY_LEN .checked_mul(max_positions as usize) @@ -284,14 +264,9 @@ fn resolve_authority(ctx: &AppContext) -> Result<(String, Pubkey), VulcanError> pub async fn execute(ctx: &AppContext, cmd: AccountCommand) -> Result<(), VulcanError> { match cmd { - AccountCommand::Register { - access_code, - referral_code, - invite_code, - } => { + AccountCommand::Register { referral_code } => { let (wallet_name, authority) = resolve_authority(ctx)?; - let code = parse_registration_code(access_code, referral_code, invite_code)?; - let result = register_authority(ctx, &wallet_name, authority, code).await?; + let result = register_authority(ctx, &wallet_name, authority, referral_code).await?; render_success(ctx.output_format, &result, serde_json::Value::Null); Ok(()) @@ -469,30 +444,16 @@ pub async fn execute_info_inner(ctx: &AppContext) -> Result Result { - let (wallet_name, authority) = resolve_authority(ctx)?; - register_authority( - ctx, - &wallet_name, - authority, - Some(RegistrationCode::Access(invite_code.to_string())), - ) - .await -} - -pub async fn execute_register_with_code_inner( - ctx: &AppContext, - code: Option, + referral_code: Option, ) -> Result { let (wallet_name, authority) = resolve_authority(ctx)?; - register_authority(ctx, &wallet_name, authority, code).await + register_authority(ctx, &wallet_name, authority, referral_code).await } -pub async fn execute_register_wallet_with_code_inner( +pub async fn execute_register_wallet_inner( ctx: &AppContext, wallet_name: &str, - code: Option, + referral_code: Option, ) -> Result { let wallet_file = ctx .wallet_store @@ -500,113 +461,14 @@ pub async fn execute_register_wallet_with_code_inner( .map_err(|e| VulcanError::auth("WALLET_NOT_FOUND", e.to_string()))?; let authority = Pubkey::from_str(&wallet_file.public_key) .map_err(|e| VulcanError::validation("INVALID_PUBKEY", e.to_string()))?; - register_authority(ctx, wallet_name, authority, code).await -} - -async fn is_cross_margin_registered( - ctx: &AppContext, - authority: &Pubkey, -) -> Result { - Ok( - crate::commands::trader_state::try_fetch_trader_state_snapshot(ctx, authority, 0) - .await? - .is_some_and(|state| state.has_cross_margin_subaccount()), - ) -} - -async fn activate_access_code( - ctx: &AppContext, - authority: &Pubkey, - code: &str, -) -> Result<(), VulcanError> { - ctx.http_client - .invite() - .activate_invite(authority, code) - .await - .map_err(|e| VulcanError::api("REGISTER_API_FAILED", e.to_string()))?; - Ok(()) -} - -async fn submit_local_register_tx( - ctx: &AppContext, - wallet_name: &str, - authority: Pubkey, -) -> Result, VulcanError> { - ensure_sol_for_cross_trader_rent(ctx, authority, CROSS_MARGIN_MAX_POSITIONS).await?; - - let builder = ctx.tx_builder().await?; - let ixs = builder - .build_register_trader(authority, 0, 0) - .map_err(|e| VulcanError::api("BUILD_REGISTER_FAILED", e.to_string()))?; - - let (wallet, _, _) = - crate::commands::trade::resolve_wallet_and_pda(ctx, Some(wallet_name)).await?; - let fee_payer = wallet.signer()?.pubkey(); - if fee_payer != wallet.authority { - return Err(VulcanError::auth( - "SIGNER_PUBKEY_MISMATCH", - format!( - "Signer pubkey {} does not match active wallet authority {}", - fee_payer, wallet.authority - ), - )); - } - let mut simulation_ixs = Vec::with_capacity(ixs.len() + 1); - simulation_ixs.push( - solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit( - crate::commands::trade::DEFAULT_CU_LIMIT, - ), - ); - simulation_ixs.extend(ixs.iter().cloned()); - simulate_onboarding_transaction(ctx, &simulation_ixs, fee_payer).await?; - - match crate::commands::trade::send_or_dry_run(ctx, ixs, &wallet).await { - Ok(sig) => Ok(sig), - Err(err) - if err.category == crate::error::ErrorCategory::TxFailed - && is_cross_margin_registered(ctx, &authority).await? => - { - eprintln!("Registration transaction failed, but trader account is now registered."); - Ok(None) - } - Err(err) => Err(err), - } -} - -fn api_instruction_to_solana( - api_instruction: &ApiInstructionResponse, -) -> Result { - Ok(solana_sdk::instruction::Instruction { - program_id: Pubkey::from_str(&api_instruction.program_id).map_err(|e| { - VulcanError::api( - "REGISTER_API_INVALID_IX", - format!("Invalid program id: {e}"), - ) - })?, - accounts: api_instruction - .keys - .iter() - .map(|account| { - Ok(solana_sdk::instruction::AccountMeta { - pubkey: Pubkey::from_str(&account.pubkey).map_err(|e| { - VulcanError::api( - "REGISTER_API_INVALID_IX", - format!("Invalid account pubkey: {e}"), - ) - })?, - is_signer: account.is_signer, - is_writable: account.is_writable, - }) - }) - .collect::, VulcanError>>()?, - data: api_instruction.data.clone(), - }) + register_authority(ctx, wallet_name, authority, referral_code).await } async fn sign_onboarding_transaction_for_api( ctx: &AppContext, wallet: &ResolvedSigner, ixs: Vec, + trader_onboarder: Pubkey, ) -> Result<(String, String, Pubkey), VulcanError> { let signer = wallet.signer()?; let fee_payer = signer.pubkey(); @@ -634,72 +496,43 @@ async fn sign_onboarding_transaction_for_api( .await .map_err(|e| VulcanError::auth("TX_SIGN_FAILED", e.to_string()))?; if matches!(signed, SignTransactionResult::Partial(_)) { - return Err(VulcanError::auth( - "PARTIAL_SIGNATURE", - "Transaction was only partially signed; onboarding requires a complete authority signature.", - )); + validate_partial_onboarding_signatures(&tx, &trader_onboarder)?; } let (transaction, _) = signed.into_signed_transaction(); Ok((transaction, recent_blockhash.to_string(), fee_payer)) } -async fn submit_builder_onboarding_tx( - ctx: &AppContext, - wallet_name: &str, - authority: Pubkey, - status: ReferralActivationTraderStatus, -) -> Result, VulcanError> { - let (wallet, _, _) = - crate::commands::trade::resolve_wallet_and_pda(ctx, Some(wallet_name)).await?; - let fee_payer = wallet.signer()?.pubkey(); - if fee_payer != wallet.authority { - return Err(VulcanError::auth( - "SIGNER_PUBKEY_MISMATCH", - format!( - "Signer pubkey {} does not match active wallet authority {}", - fee_payer, wallet.authority - ), - )); - } - if status.should_include_register_trader() { - ensure_sol_for_cross_trader_rent(ctx, fee_payer, CROSS_MARGIN_MAX_POSITIONS).await?; - } - let built = ctx - .http_client - .exchange() - .build_register_ixs(&BuildRegisterIxsRequest { - trader_authority: authority.to_string(), - tx_fee_payer: fee_payer.to_string(), - max_positions: Some(CROSS_MARGIN_MAX_POSITIONS), - }) - .await - .map_err(|e| VulcanError::api("BUILD_REGISTER_API_FAILED", e.to_string()))?; - if built.include_register_trader && built.max_positions != CROSS_MARGIN_MAX_POSITIONS { - ensure_sol_for_cross_trader_rent(ctx, fee_payer, built.max_positions).await?; - } - let ixs = built - .instructions +/// The onboard instruction lists the API's trader onboarder as a required +/// signer; the API fills that slot server-side after submission, so a locally +/// partial signature is the expected state. Only a missing signature for any +/// other key is fatal. +fn validate_partial_onboarding_signatures( + tx: &solana_sdk::transaction::Transaction, + trader_onboarder: &Pubkey, +) -> Result<(), VulcanError> { + let num_required = tx.message.header.num_required_signatures as usize; + for (position, key) in tx + .message + .account_keys .iter() - .map(api_instruction_to_solana) - .collect::, _>>()?; - let (transaction, _, fee_payer) = - sign_onboarding_transaction_for_api(ctx, &wallet, ixs).await?; - - let submitted = ctx - .http_client - .exchange() - .send_register_ixs(&SendRegisterIxsRequest { - transaction, - trader_authority: authority.to_string(), - tx_fee_payer: fee_payer.to_string(), - max_positions: Some(built.max_positions), - trader_pda_index: Some(0), - trader_subaccount_index: Some(0), - }) - .await - .map_err(|e| VulcanError::api("SEND_REGISTER_API_FAILED", e.to_string()))?; - Ok(Some(submitted.signature)) + .take(num_required) + .enumerate() + { + let missing = tx + .signatures + .get(position) + .is_none_or(|sig| *sig == solana_sdk::signature::Signature::default()); + if missing && key != trader_onboarder { + return Err(VulcanError::auth( + "PARTIAL_SIGNATURE", + format!( + "Onboarding transaction is missing a signature for {key}, which is not the API trader onboarder {trader_onboarder}." + ), + )); + } + } + Ok(()) } fn parse_api_pubkey(value: &str, field: &str) -> Result { @@ -843,7 +676,7 @@ async fn submit_referral_activation_tx( ); let (transaction, recent_blockhash, _) = - sign_onboarding_transaction_for_api(ctx, &wallet, ixs).await?; + sign_onboarding_transaction_for_api(ctx, &wallet, ixs, trader_onboarder).await?; let response = ctx .http_client .invite() @@ -865,7 +698,7 @@ async fn register_authority( ctx: &AppContext, wallet_name: &str, authority: Pubkey, - code: Option, + referral_code: Option, ) -> Result { let status = trader_onboarding_status(ctx, &authority).await?; @@ -875,25 +708,11 @@ async fn register_authority( } else if ctx.dry_run { None } else { - match code { - Some(RegistrationCode::Access(code)) => { - activate_access_code(ctx, &authority, code.as_str()).await?; - - if is_cross_margin_registered(ctx, &authority).await? { - eprintln!( - "Trader account registered after code activation; skipping on-chain transaction." - ); - None - } else { - submit_local_register_tx(ctx, wallet_name, authority).await? - } - } - Some(RegistrationCode::Referral(referral_code)) => { - submit_referral_activation_tx(ctx, wallet_name, authority, referral_code, status) - .await? - } - None => submit_builder_onboarding_tx(ctx, wallet_name, authority, status).await?, - } + let code = referral_code + .map(|code| code.trim().to_string()) + .filter(|code| !code.is_empty()) + .unwrap_or_else(|| DEFAULT_REFERRAL_CODE.to_string()); + submit_referral_activation_tx(ctx, wallet_name, authority, code, status).await? }; let trader_key = TraderKey::new(authority); @@ -908,6 +727,45 @@ async fn register_authority( #[cfg(test)] mod tests { use super::*; + use solana_sdk::signature::Signature; + + fn two_signer_onboarding_tx( + authority: Pubkey, + onboarder: Pubkey, + ) -> solana_sdk::transaction::Transaction { + let program_id = Pubkey::new_unique(); + let ix = solana_sdk::instruction::Instruction { + program_id, + accounts: vec![ + solana_sdk::instruction::AccountMeta::new(authority, true), + solana_sdk::instruction::AccountMeta::new_readonly(onboarder, true), + ], + data: vec![], + }; + solana_sdk::transaction::Transaction::new_with_payer(&[ix], Some(&authority)) + } + + #[test] + fn partial_signature_missing_only_onboarder_is_accepted() { + let authority = Pubkey::new_unique(); + let onboarder = Pubkey::new_unique(); + let mut tx = two_signer_onboarding_tx(authority, onboarder); + tx.signatures = vec![Signature::from([1u8; 64]), Signature::default()]; + + assert!(validate_partial_onboarding_signatures(&tx, &onboarder).is_ok()); + } + + #[test] + fn partial_signature_missing_authority_is_rejected() { + let authority = Pubkey::new_unique(); + let onboarder = Pubkey::new_unique(); + let mut tx = two_signer_onboarding_tx(authority, onboarder); + tx.signatures = vec![Signature::default(), Signature::default()]; + + let err = validate_partial_onboarding_signatures(&tx, &onboarder) + .expect_err("missing authority signature must be rejected"); + assert!(err.to_string().contains(&authority.to_string())); + } #[test] fn trader_account_size_matches_rise_layout() { diff --git a/vulcan-lib/src/commands/agent.rs b/vulcan-lib/src/commands/agent.rs index 1b10059..879a667 100644 --- a/vulcan-lib/src/commands/agent.rs +++ b/vulcan-lib/src/commands/agent.rs @@ -180,7 +180,6 @@ pub struct AgentLiveReadiness { pub wallet_has_sol: bool, pub wallet_has_usdc: bool, pub trader_registered: bool, - pub invite_code_required: bool, #[serde(skip_serializing_if = "Option::is_none")] pub deposited_collateral_usdc: Option, pub can_attempt_live_trading: bool, @@ -563,9 +562,6 @@ impl TableRenderable for AgentHealthResult { .map(|c| format!(", deposited collateral={c} USDC")) .unwrap_or_default() ); - if self.live_readiness.invite_code_required { - println!(" Invite: required for registration"); - } println!( " Skills: {} targets checked, {} missing files", self.agent_skills.len(), @@ -942,7 +938,6 @@ fn live_readiness_from_health( wallet_has_sol, wallet_has_usdc, trader_registered, - invite_code_required: false, deposited_collateral_usdc: status.trader.collateral.clone(), can_attempt_live_trading: status.api.ok && status.rpc.ok @@ -991,9 +986,7 @@ fn health_next_steps( } else if wallets.default_wallet.is_none() { steps.push("Set a default wallet: vulcan wallet set-default ".to_string()); } - if live_readiness.invite_code_required - || (live_readiness.has_default_wallet && !status.trader.registered) - { + if live_readiness.has_default_wallet && !status.trader.registered { steps.push("Register the trader account: vulcan account register --yes (or add --referral-code when applicable).".to_string()); } if status.trader.registered { diff --git a/vulcan-lib/src/commands/setup.rs b/vulcan-lib/src/commands/setup.rs index 800ea86..450a06c 100644 --- a/vulcan-lib/src/commands/setup.rs +++ b/vulcan-lib/src/commands/setup.rs @@ -1,7 +1,7 @@ //! Interactive setup wizard — wallet creation, config, registration, and connectivity check. use crate::cli::agent::{AgentScope, AgentTarget}; -use crate::commands::account::{self, RegistrationCode}; +use crate::commands::account; use crate::commands::agent; use crate::config::VulcanConfig; use crate::context::AppContext; @@ -569,56 +569,12 @@ async fn maybe_register_trader( } println!(); - println!(" Which registration path do you want?"); - println!(" 1) No code"); - println!(" 2) Referral code"); - println!(" 3) Access code"); - println!(" 4) Skip"); - println!(); - - let choice = prompt(" Choice [1]: ")?; - let choice = if choice.is_empty() { - "1" - } else { - choice.as_str() - }; - - let code_kind = match choice { - "1" => None, - "2" => { - let code = prompt(" Referral code: ")?; - if code.is_empty() { - return Err(VulcanError::validation( - "EMPTY_CODE", - "Referral code cannot be empty", - )); - } - Some(RegistrationCode::Referral(code)) - } - "3" => { - let code = prompt(" Access code: ")?; - if code.is_empty() { - return Err(VulcanError::validation( - "EMPTY_CODE", - "Access code cannot be empty", - )); - } - Some(RegistrationCode::Access(code)) - } - "4" => { - println!(" Skipped registration."); - return Ok(()); - } - _ => { - return Err(VulcanError::validation( - "INVALID_CHOICE", - "Please enter 1, 2, 3, or 4", - )); - } - }; + println!(" If you have a referral code, enter it now."); + println!(" Press Enter to skip and register without a code."); + let code = prompt(" Referral code (optional): ")?; + let referral_code = if code.is_empty() { None } else { Some(code) }; - let result = - account::execute_register_wallet_with_code_inner(ctx, &wallet_name, code_kind).await?; + let result = account::execute_register_wallet_inner(ctx, &wallet_name, referral_code).await?; println!(" ✓ Trader account registered"); println!(" Trader PDA: {}", result.trader_pda); if let Some(sig) = result.tx_signature { diff --git a/vulcan-lib/src/error.rs b/vulcan-lib/src/error.rs index 5fd6079..9f3ba96 100644 --- a/vulcan-lib/src/error.rs +++ b/vulcan-lib/src/error.rs @@ -146,7 +146,6 @@ impl VulcanError { "REGISTRATION_SIMULATION_FAILED" => { "Check the simulation logs, payer SOL balance, and account state before retrying registration" } - "REGISTER_API_FAILED" => "Check registration code and API URL. Run 'vulcan status' to verify", "TX_SEND_FAILED" => "Check wallet SOL balance and account state", "CONFIG_ERROR" | "CONFIG_LOAD_FAILED" | "INIT_FAILED" => { "Run 'vulcan setup' to configure" diff --git a/vulcan-lib/src/mcp/registry.rs b/vulcan-lib/src/mcp/registry.rs index 339dafe..7e43a86 100644 --- a/vulcan-lib/src/mcp/registry.rs +++ b/vulcan-lib/src/mcp/registry.rs @@ -893,15 +893,13 @@ pub static TOOLS: &[ToolDef] = &[ }, ToolDef { name: "vulcan_account_register", - description: "Register and onboard a trader account. Omit a code for builder onboarding, pass referral_code for /v1/referral/activate-tx, or pass access_code/invite_code for allowlist activation.", + description: "Register and onboard a trader account via /v1/referral/activate-tx. Omit referral_code to register with the default code.", group: "account", dangerous: true, schema: || json!({ "type": "object", "properties": { - "access_code": { "type": "string", "description": "Access code for registration" }, - "referral_code": { "type": "string", "description": "Referral code for registration" }, - "invite_code": { "type": "string", "description": "Backwards-compatible alias for access_code" }, + "referral_code": { "type": "string", "description": "Referral code (optional; the default code is used when omitted)" }, "acknowledged": { "type": "boolean", "description": "Must be true to execute" } }, "required": ["acknowledged"], diff --git a/vulcan-lib/src/mcp/server.rs b/vulcan-lib/src/mcp/server.rs index 94e83e7..ff0ae16 100644 --- a/vulcan-lib/src/mcp/server.rs +++ b/vulcan-lib/src/mcp/server.rs @@ -757,27 +757,17 @@ impl VulcanMcpServer { Ok(serde_json::to_value(result).unwrap()) } "vulcan_account_register" => { - let code = match ( - arg_str_opt(args, "access_code"), - arg_str_opt(args, "referral_code"), - arg_str_opt(args, "invite_code"), - ) { - (None, None, None) => None, - (Some(code), None, None) | (None, None, Some(code)) => { - Some(commands::account::RegistrationCode::Access(code)) - } - (None, Some(code), None) => { - Some(commands::account::RegistrationCode::Referral(code)) - } - _ => { - return Err(crate::error::VulcanError::validation( - "REGISTRATION_CODE_CONFLICT", - "Provide at most one of access_code, referral_code, or invite_code", - )); - } - }; + if arg_str_opt(args, "access_code").is_some() + || arg_str_opt(args, "invite_code").is_some() + { + return Err(crate::error::VulcanError::validation( + "ACCESS_CODE_REMOVED", + "Access and invite codes are no longer supported. Pass referral_code, or omit it to register via builder onboarding.", + )); + } + let referral_code = arg_str_opt(args, "referral_code"); let result = - commands::account::execute_register_with_code_inner(&self.ctx, code).await?; + commands::account::execute_register_inner(&self.ctx, referral_code).await?; Ok(serde_json::to_value(result).unwrap()) }