Common errors when interacting with NebGov on Stellar, along with their causes and fixes.
- RPC Errors
- Wallet Errors
- Transaction Errors
- Contract Errors
- Voting and Proposals
- Liquidity Contract Errors
- FAQ
Cause: The contract invocation failed during simulation. Common reasons: wrong function name, incorrect argument types, or the contract is not deployed at the configured address.
Fix:
- Verify the function name matches the contract ABI exactly (case-sensitive).
- Check that all argument types are correct (e.g.,
u32vsi128,AddressvsString). - Confirm
NEXT_PUBLIC_GOVERNOR_ADDRESSin your.envpoints to a deployed contract on the correct network. - Run the simulation manually with
stellar contract invoke --simulate-onlyto inspect the full error.
Cause: The app cannot reach the Stellar RPC endpoint. The NEXT_PUBLIC_RPC_URL environment variable is missing or points to the wrong host.
Fix:
- Open your
.env(or.env.local) and verifyNEXT_PUBLIC_RPC_URL.- Testnet:
https://soroban-testnet.stellar.org - Futurenet:
https://rpc-futurenet.stellar.org - Mainnet:
https://soroban-mainnet.stellar.org
- Testnet:
- Restart the dev server after changing
.env. - Check that your firewall or VPN is not blocking outbound HTTPS to port 443.
Cause: The wallet is connected to a different Stellar network than the one the app targets.
Fix:
- In your wallet (Freighter, Lobstr, etc.), switch to the network specified in
NEXT_PUBLIC_NETWORK_PASSPHRASE. - The passphrase values are:
- Testnet:
Test SDF Network ; September 2015 - Mainnet:
Public Global Stellar Network ; September 2015
- Testnet:
Cause: WalletKitProvider is not wrapping the component tree, or the provider was initialized after the component mounted.
Fix:
- Ensure
<WalletKitProvider>wraps your root layout inapp/layout.tsx(or equivalent). - Do not conditionally render the provider — it must wrap the tree unconditionally.
- If using
useWalletKit(), confirm you are inside a component that is a descendant of the provider.
Cause: The user cancelled the signing prompt in their wallet extension.
Fix: This is expected behavior. Show a non-blocking notification and allow the user to retry. Do not treat this as an application error.
Cause: The wallet returned an empty or malformed public key. This can happen when the wallet is locked or has not yet granted permission to the dApp.
Fix:
- Unlock your wallet and approve the site connection request.
- Disconnect and reconnect via the Connect Wallet button.
- If the issue persists, clear wallet permissions for the site and reconnect.
Cause: The submitted fee is too low for current network conditions. The Stellar network uses a fee market during congestion.
Fix:
- Use fee bumping: submit the transaction with a higher
base_fee(e.g., 1 000 000 stroops during high congestion). - The SDK's
submitTransactionhelper retries with fee bumping automatically. Ensure you are on SDK v0.3.0+. - Monitor current base fees with
stellar ledger fetch --network testnet | jq .base_fee.
Cause: The transaction's timeBounds window elapsed before it was included in a ledger. This often happens when the user delays signing.
Fix:
- Increase
timeboundsSecondsin the transaction builder (default is 30 s; try 120 s for complex multi-step flows). - Prompt the user to sign promptly after building the transaction.
Cause: A persistent storage entry (e.g., a proposal or LP position) does not exist at the queried key. The contract may be on a different network than expected.
Fix:
- Confirm
NEXT_PUBLIC_GOVERNOR_ADDRESSandNEXT_PUBLIC_TIMELOCK_ADDRESSare set for the correct network. - Verify the data was written in a transaction that was successfully included in the ledger (not just simulated).
Cause: The VoteEscrow contract has not been initialized. This occurs when calling contract methods before initialize() is invoked.
Fix:
- Ensure the deployment script has completed and called
initialize()on the VoteEscrow contract. - Verify
VOTE_ESCROW_ADDRESSin your.envpoints to a deployed and initialized contract. - Run
scripts/verify-deployment.shto check VoteEscrow initialization status.
Cause: The lock duration is outside the allowed range. Lock duration must be between min_lock_duration and max_lock_duration.
Fix:
- Query
min_lock_durationandmax_lock_durationfrom the contract. - Ensure
duration_ledgersforcreate_lock()is within this range. - Typical ranges: minimum 1 ledger, maximum 52 weeks in ledgers (~2,700,000 on Stellar).
Cause: The lock amount is zero or negative. VoteEscrow requires a positive token amount to lock.
Fix:
- Ensure the amount being locked is greater than zero.
- Account for token decimals when calculating amounts (e.g., 7 decimals = multiply by 10^7).
Cause: No lock exists for the given address. Either no lock was created, or the address is incorrect.
Fix:
- Call
create_lock()to create a new lock before attempting to query or modify it. - Verify the wallet address matches the owner of the lock.
- Use the same account that created the lock when querying or modifying it.
Cause: The lock's end_ledger has not yet passed. Locks must mature before withdrawal.
Fix:
- Query
get_lock()to see the lock'send_ledger. - Wait until the current ledger sequence exceeds the
end_ledgerto withdraw. - Monitor the network:
stellar ledger fetch --network testnet | jq .sequence.
Cause: The lock has already been withdrawn. Each lock can only be withdrawn once.
Fix:
- Create a new lock if you wish to lock tokens again.
- Check the lock's
withdrawnfield before attempting to withdraw.
Cause: The computed end_ledger (start + duration) is invalid or has already passed.
Fix:
- Ensure the lock duration is valid and not zero.
- Check that the current ledger is not already past the intended
end_ledger.
Cause: The caller is not authorized to perform this action. Some operations require admin privileges.
Fix:
- Ensure the wallet address is authorized (admin or lock owner).
- For admin-only operations, use the admin account configured during deployment.
Cause: An internal calculation overflowed. This typically occurs with very large amounts or edge cases in voting power calculations.
Fix:
- Reduce the lock amount or duration if possible.
- Report this as a bug if using normal amounts; it indicates a contract issue.
Cause: The VotingRewards contract has already been initialized. initialize() can only be called once.
Fix:
- Verify the contract is already initialized by checking
total_pool(). - If deploying fresh, use a new contract address.
Cause: The VotingRewards contract has not been initialized yet.
Fix:
- Ensure
initialize()has been called after contract deployment. - Run
scripts/verify-deployment.shto confirm VotingRewards initialization.
Cause: The caller lacks the required permissions. This typically applies to admin-only operations.
Fix:
- Use the admin account configured during deployment.
- For claim operations, ensure the caller's address matches the one in the Merkle proof.
Cause: The epoch duration is zero or invalid. Epochs must have positive duration.
Fix:
- Pass a positive epoch duration (in ledgers) to
initialize(). - Typical epoch durations: 1 week to 1 month of ledgers (~252,000 to 2,628,000 ledgers on Stellar).
Cause: The specified epoch does not exist. The epoch number or state is incorrect.
Fix:
- Query recent epochs with
get_epochs(). - Verify the epoch number is within the range of finalized or active epochs.
Cause: The epoch is still active. Operations requiring a closed epoch cannot proceed.
Fix:
- Wait until the current ledger surpasses the epoch's end ledger.
- Call
finalize_epoch()only after the epoch has ended.
Cause: The epoch has already been finalized. Finalization is idempotent but unnecessary to repeat.
Fix:
- Verify the epoch state before attempting to finalize again.
- Proceed to the next operation (e.g., computing or submitting Merkle proofs).
Cause: The epoch must be finalized before claims can be submitted. This ensures reward calculations are complete.
Fix:
- Call
finalize_epoch()first to compute the final reward distribution. - Wait for finalization to complete before submitting claims.
Cause: The reward pool has insufficient balance to pay the claim. Rewards may have already been partially claimed.
Fix:
- Verify the total reward pool was funded correctly during epoch setup.
- Check how much has already been claimed for this epoch.
- If pool is exhausted, it indicates an issue with reward calculations or multiple claims.
Cause: The claim amount is zero, negative, or exceeds the claimable amount for this address.
Fix:
- Query the claimable amount for your address using
get_claimable(). - Ensure the proof corresponds to a valid, non-zero reward for this epoch.
Cause: The address has already claimed rewards for this epoch. Each address can claim only once per epoch.
Fix:
- Use a different address if you wish to claim again (e.g., for a different vote weight).
- Check the claim status with
get_claim()before submitting a claim.
Cause: The Merkle proof is invalid or does not match the epoch's Merkle root. This occurs when the proof was corrupted or computed incorrectly.
Fix:
- Re-fetch the Merkle proof from the backend API (
GET /voting-rewards/proof?epoch=<EPOCH>&address=<ADDRESS>). - Verify the proof is for the correct epoch and address.
- Ensure the backend's Merkle root matches the contract's stored root for this epoch.
Cause: The total claimed amount exceeds the finalized rewards for this epoch. This indicates a calculation error or corrupted Merkle root.
Fix:
- Report this as a critical bug; it suggests either backend miscalculation or a malicious proof.
- Stop processing claims and investigate the Merkle tree computation.
Cause: The proposal ID does not exist in the governor contract. Either the wrong contract address is configured, or the proposal was never created on this network.
Fix:
- Verify
NEXT_PUBLIC_GOVERNOR_ADDRESSmatches the deployed governor on the active network. - Confirm the proposal was created by checking the transaction that called
propose().
Cause: The caller's signature was not present in the transaction authorization envelope, or the wrong address was passed as the caller argument.
Fix:
- Ensure the wallet's public key matches the address passed to privileged functions (e.g.,
governor,admin,provider). - When constructing the transaction, call
addSignatureBase64or equivalent for each required signer before submitting.
Cause: No liquidity pool exists for the given outcome pair, or the pair was registered under a different token ordering.
Fix:
- Confirm
create_poolandinitialize_poolwere called by the governor for this outcome pair. - Outcome pair ordering is canonical (smaller id first) — querying
(1, 2)and(2, 1)resolve to the same pool. - Check that the
NEXT_PUBLIC_LIQUIDITY_ADDRESSenv var points to the correct contract.
Cause: cast_vote was called before the proposal's start_ledger. The proposal is in Pending state.
Fix:
- Call
governor.state(proposal_id)to get the current state. - Read
proposal.start_ledgerand wait until the current ledger sequence surpasses it. - The UI should disable the Vote button until the proposal is
Active.
Cause: The wallet address has already cast a vote for this proposal. The contract rejects duplicate votes.
Fix: Each address may vote only once per proposal. Show the user their existing vote choice instead of the voting form.
Cause: The user has no delegated voting power at the proposal's snapshot ledger. Tokens must be delegated (self-delegated or to another account) before the proposal was created.
Fix:
- Call
votes.delegate(account, account)to self-delegate before the next proposal is created. - Wrapping tokens via
token_votes.wrap(amount)and then delegating grants voting power. - Voting power is snapshotted at
proposal.start_ledger - 1; delegation after that point does not count for this proposal.
Cause: cast_vote was called when the proposal is in Succeeded, Defeated, Queued, Executed, or Expired state.
Fix: Check governor.state(proposal_id) before casting a vote. Only Active proposals accept new votes.
Cause: A subsequent deposit to a pool provided a amount_b that is less than the proportional amount required by the current reserve ratio. This prevents price manipulation.
Fix:
- Fetch the current pool reserves with
liquidity.get_pool(outcome_a, outcome_b). - Compute
required_b = amount_a * reserve_b / reserve_abefore callingadd_liquidity. - Pass
required_b(or a value greater than it) asamount_b.
Cause: The deposit amount is below MIN_LIQUIDITY (1 000 units). Tiny deposits are rejected to prevent dust griefing.
Fix: Ensure both amount_a and the computed required_b are at least 1 000 units.
Cause: The swap output amount_out after fees is less than the caller's min_amount_out. The pool moved between simulation and execution.
Fix:
- Re-simulate the swap immediately before submitting to get the latest output.
- Add a slippage tolerance (e.g., 0.5%) to
min_amount_out:min_amount_out = simulated_out * 995 / 1000.
Q: How do I find the correct contract addresses for my network?
A: Contract addresses are published in scripts/ after each deployment. You can also query the factory: liquidity.governor() returns the governor address linked to the liquidity contract.
Q: My transaction simulates successfully but fails on-chain. Why?
A: State can change between simulation and submission (another transaction was included first). Retry the simulation immediately before resubmitting, or add a short polling loop.
Q: How do I run contract calls without the frontend?
A: Use the Stellar CLI:
stellar contract invoke \
--network testnet \
--id <CONTRACT_ADDRESS> \
--source <SECRET_KEY> \
-- <function_name> --arg1 value1Q: Can I test on Futurenet before Testnet?
A: Yes. Set NEXT_PUBLIC_RPC_URL to https://rpc-futurenet.stellar.org and NEXT_PUBLIC_NETWORK_PASSPHRASE to the Futurenet passphrase. Deploy contracts to Futurenet first using stellar contract deploy --network futurenet.
Q: How do I reset local state during development?
A: Re-initialize the contracts with fresh deployments:
stellar contract deploy --wasm target/wasm32v1-none/release/sorogov_governor.wasm --network testnet --source <KEY>Update the address in .env.local and restart the dev server.
Q: Why does get_lp_position return 0 even after adding liquidity?
A: The provider address queried must exactly match the one used in add_liquidity. Also verify that outcome_a and outcome_b are in the correct order (use canonical order: smaller id first, e.g., (0, 1) not (1, 0)).