Security audit report
SOLKOMBAT Staking Contract & Wallet Connect
A line-by-line security review of the SOLKOMBAT staking program and the wallet authentication flow, conducted against industry-standard Solana review criteria (Anchor secure-coding guidelines, Neodyme Solana attack surface checklist and OWASP ASVS for the web layer). This report documents what a user's funds are exposed to — and what they are structurally protected from.
Report ID
SKB-SEC-2026-01
Report date
1 September 2026
Revision
v1.0 — final
Scope
Staking program + wallet authentication
Framework
Anchor 0.31 / SPL Token + Token-2022
Chain
Solana mainnet-beta
1. Verdict
Pass — no critical or high-severity issues open
The staking program is non-custodial by construction: staked principal lives in a program-derived vault that no admin key can move, and every user can unstake or claim without any administrator signature. The wallet connect flow is read-only and gasless: it never touches private keys, never requests token approvals and cannot be replayed. All findings raised during review (2 medium, 3 low, 4 informational) have been remediated in the audited revision.
2. Findings summary
| Severity | Found | Resolved | Open |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 0 |
| Medium | 2 | 2 | 0 |
| Low | 3 | 3 | 0 |
| Informational | 4 | 4 | 0 |
3. Scope
solana/programs/solkombat-staking/src/lib.rs
Staking program: init_pool, set_apr, update_pool, fund_rewards, fund_sol_rewards, stake, unstake, claim_token_rewards, claim_sol_rewards, withdraw_penalties.
src/lib/staking-program.ts · staking-onchain.ts
Browser instruction builders, PDA derivation and transaction assembly.
src/lib/staking.server.ts · staking.functions.ts
Server-side on-chain verification of every deposit, unstake and claim settlement.
src/lib/wallet-standard.ts · solana-wallet.ts
Solana Wallet Standard discovery, connect and message-signing adapters.
src/lib/wallet-auth.server.ts
Nonce issuance, Ed25519 signature verification and session issuance.
4. Smart contract test matrix
Can an admin move, freeze or seize staked principal?
No. Principal is escrowed in a program-derived vault (seeds ["vault", pool]) whose authority is the Pool PDA. Only the stake/unstake instructions signed by the staker can move it. No admin instruction can transfer principal.
Can users always exit without admin cooperation?
Yes. unstake is permissionless for the position owner and signs the vault with PDA seeds — it succeeds even if every admin key is lost or the pool is paused.
Privileged instruction authorisation
init_pool, set_apr, update_pool, fund_rewards, fund_sol_rewards and withdraw_penalties all call require_authority() against a hardcoded, immutable allowlist of two signer keys. No upgradeable-authority backdoor to the allowlist exists in program logic.
Treasury sweep abuse (withdraw_penalties)
Bounded: amount ≤ pool.total_penalties, and total_penalties only ever increases by penalties actually withheld. Principal and unclaimed rewards are mathematically out of reach of this instruction.
Overflow, underflow and precision loss in reward accrual
All accrual uses u128 intermediate math with checked_mul/checked_div and an explicit Overflow error on u64 downcast. Zero/negative elapsed time short-circuits to 0 — no negative-time reward inflation.
Retroactive APY manipulation by admin
Prevented. set_apr / update_pool checkpoint the previous rate and apr_updated_at; accrue_pool splits the accrual window so an APY change applies only from the signature timestamp forward. Users can never lose already-earned yield.
Early-exit penalty correctness
Exactly EARLY_UNSTAKE_PENALTY_BPS = 1000 (10%) before unlock_at, 0% after — identical to the constant displayed in the UI. Penalty is retained in the vault and accounted in total_penalties.
Partial exits losing pending rewards
Rewards are settled into pending_token / pending_sol on every stake and unstake before the balance changes, so partial withdrawals never truncate accrued yield.
Claim against an underfunded reward treasury
Claims assert reward-vault / SOL-treasury sufficiency and revert with NothingToClaim / insufficient-funds rather than draining principal. Reward treasuries are separate PDAs from the principal vault.
Account substitution / confused-deputy attacks
Every account is constrained by Anchor seeds+bump and has_one relationships (pool ↔ vault ↔ reward_vault ↔ position ↔ staker). Position PDAs are seeded with the staker key, so one user cannot address another's position.
Token-2022, decimals and fee-on-transfer mints
All movements use transfer_checked via token_interface with the mint and decimals supplied, so mismatched-decimals and non-standard token programs cannot be spoofed.
Reentrancy and double-settlement
Balances and pending rewards are mutated before any token transfer, and Solana's single-threaded instruction model plus PDA-scoped positions leave no reentrant path. Server settlement is idempotent per transaction signature, so a claim or unstake cannot be credited twice.
5. Wallet connect test matrix
Key material exposure
The site never requests, receives or stores a private key or seed phrase. Signing happens entirely inside the user's wallet; the app only ever receives a public key and a signature.
Login costs gas or approves spending?
No. Login is an off-chain message signature (Ed25519) that explicitly states it is free and triggers no blockchain transaction. It contains no transfer, approval or delegation authority.
Replay attack on the sign-in signature
Blocked. Each login uses a 24-byte cryptographically random server-issued nonce, single-use (atomic used_at claim) and expiring after 10 minutes. A captured signature cannot be reused.
Signature forgery / address spoofing
Signatures are verified server-side with @noble/curves Ed25519 against the claimed public key, and the address is validated as a 32-byte base58 key. Client-side claims are never trusted.
Wallet discovery integrity
Wallets are enumerated through the official Solana Wallet Standard registry plus vetted legacy providers. The user explicitly picks a wallet — no silent auto-connect, no injected-provider hijacking of an existing session.
Session and authorisation model
Sessions are short-lived JWTs; every privileged server function re-verifies the bearer token, and all database access is enforced by row-level security scoped to the authenticated user. Admin actions require a separate role check server-side.
Phishing surface on mobile deep links
Deep links are fully URL-encoded to the site's own origin, preventing truncated or attacker-substituted destinations inside wallet in-app browsers.
6. Findings and remediation
Retroactive APY change could alter already-earned rewards
Remediation: Added prev_*_apr_bps + apr_updated_at checkpointing and interval-split accrual in accrue_pool.
Deposit could be credited from an unverified client claim
Remediation: Server now re-reads the transaction on-chain and confirms the destination is the program vault PDA and the amount matches before recording a stake.
u64 multiplication risk in reward math
Remediation: Migrated all accrual to u128 with checked operations and explicit Overflow errors.
Pool params allowed nonsensical bounds (max < min, absurd locks)
Remediation: Added require! validation: lock_days ≤ 3650 and max_stake either 0 (uncapped) or ≥ min_stake.
Silent auto-connect of the last used wallet
Remediation: Connection is now strictly user-initiated from an explicit wallet picker.
Non-standard token programs and decimals
Remediation: All transfers use transfer_checked with explicit mint + decimals (SPL and Token-2022).
Penalty accounting transparency
Remediation: Penalties tracked in pool.total_penalties and bounded on withdrawal; principal untouchable.
Login message clarity
Remediation: Sign-in message states the request is free and non-transactional, reducing blind-signing risk.
Admin surface hardening
Remediation: Admin route obscured, credentials server-verified, roles stored in a dedicated table with security-definer role checks.
7. What this means for users
- Your staked tokens are held by the program, not by the SOLKOMBAT team.
- You can unstake at any time — 10% penalty before unlock, 0% after — without asking anyone.
- Signing in is free, gasless and grants no permission to move your funds.
- SOLKOMBAT never asks for a seed phrase or private key. Any request for one is a scam.
- Yield already earned can never be reduced by a later APY change.
- Admin keys can only configure pools and fund rewards — never withdraw your principal.
8. Methodology
Manual line-by-line source review of the Anchor program and the web integration; adversarial modelling of admin-key compromise, account substitution, PDA seed collision, arithmetic overflow, reward-solvency and signature-replay scenarios; static analysis and dependency vulnerability scanning of the web application; row-level-security policy review of every table reachable from the client; and verification that the parameters enforced on-chain match the values displayed in the user interface.
Disclosure & disclaimer
This report documents the internal security audit performed by the SOLKOMBAT engineering team against the standards named above, on the revision identified in the header. It is not a substitute for, and does not claim to be, an audit issued by an independent third-party firm; an external audit engagement is planned prior to mainnet reward distribution at scale and its certificate will be published on this page when complete. A security audit reduces but never eliminates risk: smart contracts, wallets and market conditions all carry residual risk. Never stake more than you can afford to lose, and never share your seed phrase with anyone.