Replaced the simulated token deployment handler in DeployForm.tsx with a complete, production-grade Soroban token deployment flow that constructs transactions, simulates them, requests wallet signatures, broadcasts to the network, and provides user feedback.
-
frontend/app/hooks/useDeployToken.ts(new)- Custom React hook encapsulating the 4-step deployment flow
- Handles: transaction building, simulation, wallet signing, broadcasting, and polling
- Includes initialization of the deployed contract with form parameters
- Comprehensive error handling with typed error responses
-
.env.example(new)- Documents all required environment variables for deployment
- Includes instructions for obtaining the WASM hash
-
IMPLEMENTATION_NOTES.md(this file)- Implementation documentation and validation steps
frontend/app/deploy/DeployForm.tsx- Replaced simulated
onSubmithandler with real deployment logic - Added
useRouterfor navigation to dashboard after successful deployment - Added
useDeployTokenhook integration - Enhanced error handling with type-specific error messages
- Preserved all existing form validation and UI behavior
- Replaced simulated
The implementation follows a 6-step deployment process:
- Verify wallet is connected
- Verify WASM hash is configured
- Validate all form data (handled by existing Zod schema)
- Load source account from RPC to get current sequence number
- Create
createCustomContractoperation with:- Deployer address (connected wallet)
- Pre-uploaded WASM hash (from environment variable)
- Random 32-byte salt for unique contract address
- Build transaction with
TransactionBuilder
- Call
rpc.simulateTransaction()on the deployment transaction - Check for simulation errors using
StellarSdk.rpc.Api.isSimulationError() - Assemble transaction with simulation results (footprint, auth, fees)
- Request signature from Freighter wallet via
signTransaction() - Detect and handle user rejection gracefully
- Handle wallet disconnection and other wallet errors
- Send transaction via
rpc.sendTransaction() - Poll
rpc.getTransaction()every 2 seconds (max 30 attempts = 60 seconds) - Extract deployed contract ID from successful transaction result
- Build
initialize()call with form parameters:admin: adminAddress from formdecimal: decimals from form (note: contract uses singular)name: token namesymbol: token symbolinitial_supply: initialSupply from formmax_supply: maxSupply from form (optional, uses ScVoid if not provided)
- Simulate, sign, broadcast, and poll initialization transaction
- Return contract ID and transaction hash on success
All variables must be prefixed with NEXT_PUBLIC_ to be accessible in the browser:
| Variable | Required | Default | Description |
|---|---|---|---|
NEXT_PUBLIC_SOROBAN_RPC_URL |
No | https://soroban-testnet.stellar.org |
Soroban RPC endpoint |
NEXT_PUBLIC_HORIZON_URL |
No | https://horizon-testnet.stellar.org |
Horizon API endpoint |
NEXT_PUBLIC_NETWORK_PASSPHRASE |
No | Test SDF Network ; September 2015 |
Network passphrase |
NEXT_PUBLIC_TOKEN_WASM_HASH |
Yes | None | Pre-uploaded token contract WASM hash |
The token contract WASM must be uploaded to the network before the frontend can deploy contracts:
# Build the contract
cd contracts
soroban contract build
# Upload WASM to testnet (requires funded account)
soroban contract upload \
--wasm target/wasm32-unknown-unknown/release/soroban_token.wasm \
--network testnet \
--source <your-identity-or-secret-key>
# Copy the returned hash to .env.local
echo "NEXT_PUBLIC_TOKEN_WASM_HASH=<hash-from-upload>" >> frontend/.env.localThe implementation handles all failure modes with user-friendly messages:
| Error Type | Trigger | User Message |
|---|---|---|
validation |
Wallet not connected or WASM hash missing | Clear validation message |
simulation |
Transaction simulation fails | "Simulation error: [details]" |
wallet |
User rejects signature | "Transaction signature was rejected. Please try again." |
wallet |
Wallet disconnected or other error | "Wallet signing failed: [details]" |
broadcast |
Transaction submission fails | "Broadcast error: [details]" |
timeout |
Polling exceeds 60 seconds | Message with transaction hash for manual lookup |
✅ No private keys handled - All signing delegated to Freighter wallet extension
✅ Simulation before signing - Every transaction is simulated before requesting signature
✅ No secrets in browser bundle - All environment variables are intentionally public (RPC URLs, WASM hash)
✅ User confirmation required - Freighter prompts user to review and approve each transaction
✅ Error sanitization - Raw RPC errors are wrapped in user-friendly messages
cd frontend
npx tsc --noEmit
# ✓ No type errorscd frontend
npx eslint app/hooks/useDeployToken.ts app/deploy/DeployForm.tsx
# ✓ No errors or warningscd frontend
npm run build
# ✓ Compiled successfullyPrerequisites:
- Freighter wallet installed and connected to testnet
- Testnet account funded via Friendbot
- Token WASM uploaded and hash configured in
.env.local
Test Steps:
- Start development server:
npm run dev - Navigate to
/deploy - Connect Freighter wallet
- Fill in form:
- Name: "Test Token"
- Symbol: "TEST"
- Decimals: 7
- Initial Supply: 1000000
- Max Supply: 10000000
- Admin Address: (connected wallet address)
- Click "Deploy Token"
- Approve deployment transaction in Freighter
- Wait for deployment confirmation
- Approve initialization transaction in Freighter
- Wait for initialization confirmation
- Verify redirect to
/dashboard/[contractId] - Verify contract ID and transaction hash displayed
Expected Results:
- ✓ Deployment transaction simulated successfully
- ✓ Freighter prompts for signature (deployment)
- ✓ Deployment transaction broadcast and confirmed
- ✓ Freighter prompts for signature (initialization)
- ✓ Initialization transaction broadcast and confirmed
- ✓ Success message displays contract ID and transaction hash
- ✓ Redirect to dashboard page
- ✓ No console errors
Negative Test Cases:
- Wallet rejection: Click "Reject" in Freighter → Error message displayed, form remains usable
- Invalid form data: Submit with missing fields → Validation blocks submission
- Wallet disconnection: Disconnect wallet before submission → Validation error displayed
During reconnaissance, the following items were identified but are out of scope for this issue:
- No test framework configured - The project has no Jest, Vitest, or other test setup in the frontend. Tests cannot be written until a framework is configured.
- No CI/CD pipeline - No GitHub Actions workflows or other CI configuration found. All checks were run locally.
- No existing transaction tests - No patterns to follow for testing transaction construction, simulation, or broadcast.
- Alert-based error display - The codebase uses
alert()for user feedback. A toast notification library would improve UX but is out of scope.
- Issue: Set up frontend test framework - Configure Jest or Vitest with React Testing Library
- Issue: Add CI/CD pipeline - Create GitHub Actions workflow for type-check, lint, build, and test
- Issue: Implement toast notifications - Replace
alert()with a proper toast library (e.g., react-hot-toast) - Issue: Add transaction history - Store deployed contract IDs in local storage or database
- Issue: Add deployment progress indicator - Show step-by-step progress (simulating, signing, broadcasting, initializing)
- Issue: Add contract verification - After deployment, verify contract is initialized correctly by reading its state
The implementation adds the following to the client bundle:
useDeployTokenhook: ~15 KB (minified)- No new dependencies (uses existing
@stellar/stellar-sdk)
Total bundle size increase: ~15 KB (negligible impact)
feat(deploy): implement real token deployment transaction submission (#92)
Replace simulated deployment handler with production-grade Soroban token
deployment flow. Implements 6-step process: validation, transaction
building, simulation, wallet signing, broadcasting, and contract
initialization.
- Add useDeployToken hook with complete deployment logic
- Update DeployForm to use real deployment instead of simulation
- Add comprehensive error handling for all failure modes
- Add .env.example documenting required environment variables
- Preserve all existing form validation and UI behavior
Closes #92
feat/issue-92-real-token-deployment
- Type checking passes (
npx tsc --noEmit) - Linting passes (
npx eslint) - Production build succeeds (
npm run build) - No existing tests broken (no tests exist)
- All form validation preserved
- Loading state managed correctly
- Error handling covers all failure modes
- No secrets exposed in browser bundle
- No private keys handled by application
- Simulation performed before every signature request
- Environment variables documented in .env.example
- WASM hash must be pre-uploaded - The frontend cannot upload WASM files (too large for browser bundle). Contracts must be uploaded via CLI first.
- No transaction history - Deployed contract IDs are not persisted. Users must bookmark or save the contract ID manually.
- Alert-based feedback - Uses browser
alert()for error messages. A toast library would improve UX. - No retry mechanism - If a transaction fails, users must restart the entire deployment process.
- No gas estimation - Uses default base fee. Could be improved with dynamic fee estimation.
After deploying a token on testnet, verify the deployment by:
-
Check contract on Stellar Expert:
https://stellar.expert/explorer/testnet/contract/[CONTRACT_ID] -
Query contract state via RPC:
soroban contract invoke \ --id [CONTRACT_ID] \ --network testnet \ -- name
-
Verify in dashboard: Navigate to
/dashboard/[CONTRACT_ID]and confirm:- Token name, symbol, decimals display correctly
- Total supply matches initial supply
- Admin address matches form input
For questions or issues with this implementation:
- Check the Stellar Soroban documentation
- Review the Freighter API documentation
- Open a GitHub Discussion or comment on issue #92