Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)

    Documentation

    Concepts, value, and typical clone scenarios — less code.

    Welcome to Ring
    Quick Reference
    Getting Started
    Prerequisites
    Installation
    First Success Validation
    Next Steps
    Features
    Multi-Vendor Store
    Inventory & Stock
    Vendor Management
    Commissions & Settlements
    SubscriptionConductor
    PaymentConductor
    Ring Oracle
    Payments Overview
    Public Pools & DAO Jars
    WayForPay Payment Integration
    Wallet & Credit System
    WalletConductor
    Affiliate & Referral Enablement
    Referral Codes (Refcodes)
    NFT Exhibition Marketplace
    Solana NFT Gates
    Token Staking System
    Owner Project Lab
    Entities
    Opportunities
    Real-Time Messaging
    Ring Tasks
    WebRTC Calls & STUNner TURN
    Peer Games
    News Module
    Member Blogs
    Public Profile Pages
    Profile Account Widgets
    Ring File Cabinet
    Username Reservation System
    Scientific Editor
    Notifications
    Push Notifications with FCM (Ring-Powered)
    Email AI-CRM
    Ring Mailer & RingdomX Mail
    Tunnel Protocol
    VideoConductor
    MediaConductor
    Generative Gallery
    Authentication
    Security & Compliance
    Admin console
    Admin Wiki
    Manage via Telegram
    Locale System
    Mobile Experience
    Performance Optimization Patterns
    Examples
    Quick Start
    Basic Setup
    White Label
    Custom Branding
    Web3 Integration
    Real World
    Advanced Features
    Customization
    Quick Start — Your First Ring Clone
    Customization Guide
    Vertical Presets (SSOT)
    Ringization playbook
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    Project configuration
    Public environment variables
    Order Lab secrets
    WalletConnect Project ID (Reown Cloud)
    Supported services
    NODUS wiki (project knowledge)
    Configuration playbook
    Web3
    Token launch jurisdictions
    Wallet
    Wallet Security Tips
    Integrations
    Ethereum wallets (Wagmi v3)
    RingFileBase (object storage API)
    Ring CDN (RingFileBase edge)
    Deployment
    Self-hosted deployment
    Vercel Deployment
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    Backend Services
    Firebase Integration
    Development
    Ring MCP Server

    Quick entry (CTOs · auditors · agents)

    Welcome — mission & audiences
    Quick Reference
    Getting started
    Architecture & Auth.js
    Backend modes & databases (DB_BACKEND_MODE)
    Self-hosted
    Ring MCP Tools
    Ring MCP Server
    Token economics
    Token launch jurisdictions
    Deploy (Docker · k8s)
    Security & compliance reads
    ringdom.org — LegioX homebase
    Source — MIT license (GitHub)
    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Ring Platform Logo

    Loading documentation...

    Preparing Ring Platform content

    Wallet & Credit System

    Filter with Founder / Developer in the docs sidebar. This page reflects the verified v1.6.4 codebase: PIN-wrapped encryption, atomic single-write provisioning, wallet_access_tokens, and oracle/platform settings persisted via db() (no private pg.Pool).

    Ring Platform's wallet system provides two complementary layers:

    1. Custodial native-token wallets — auto-provisioned for signed-in members on all enabled chains (Solana first, EVM/Base enabled per clone config). Private keys encrypted with WALLET_ENCRYPTION_KEY using AES-256-GCM + scrypt. PIN-wrapped v2 encryption for withdrawal operations.
    2. In-app credit ledger — fiat USD credits stored in credit_balance field on user documents. Used for store purchases, subscriptions, and conversions to native tokens via the Token Desk widget.

    Browser routes: /wallet (dashboard), /wallet/topup, /wallet/send, /wallet/staking.

    Money layers (SSOT)

    Four separate money paths — do not conflate them:

    #PathEntryResult
    1Card top-up (wallet_topup)/wallet/topup / CreditAddFsModal → PaymentConductorCredit points (fiat ledger) — never on-chain RING
    2Token Desk (buy)/wallet Desk widget → desk-service.ts (subscriber+)Credit points → native RING (nativeOut = points / nativePerMainCurrency)
    3Store native_token railPOST /api/store/payments/tokenSpend native RING at checkout (separate from top-up)
    4Chain-proof credit addPOST /api/wallet/credit/topup (tx-hash verify)Credit add after on-chain transfer proof
    5BuyNativeViaCard (native_token_onramp)Confidential tabs when CONFIDENTIAL_TOKEN_ONRAMP=trueCard/PayPal → treasury RING (not credit)

    Any signed-in user can buy credit points with a card (path 1). Converting points → RING is Token Desk (path 2), available to subscriber+. Optional CONFIDENTIAL_TOKEN_ONRAMP=true enables BuyNativeViaCard (native_token_onramp) for confidential / admin / superadmin — card or PayPal tabs that settle native RING from treasury (not credit points).

    What members experience

    Wallet dashboard — /wallet

    Balance hero showing credit balance + native-token balances per chain. Desk widget for converting credits ↔ native tokens.

    Top-up — /wallet/topup

    Add credits via native-token transfer (tx-hash verify) or live Card / PayPal (CreditAddFsModal → PaymentConductor wallet_topup). Card uses env processor (WayForPay or Stripe); PayPal tab sends processor=paypal.

    Send tokens — /wallet/send

    Gasless transfers to contacts — treasury sponsors transaction fees. Pick recipient from contacts or enter address.

    Store & credits

    Architecture

    The wallet system uses server actions (app/_actions/wallet.ts) as the primary integration layer. These actions call SSOT service files in features/wallet/ and lib/wallet/.

    Server actions inventory

    #ActionLayerPurpose
    1ensureUserWalletsWalletProvisions wallets for all enabled chains (atomic single-write)
    2listUserWalletsWalletLists wallets with on-chain balances
    3getWalletBalanceWalletNative-token balance via viem/Solana RPC
    4

    Related documentation

    WalletConductor

    SSOT facade for top-up, desk, custodial send, credit spend, NFT buy.

    Wallet API reference

    Complete server action signatures and response types.

    Authentication

    Social sign-in triggers wallet provisioning.

    Store feature

    Credit spend at checkout.

    Token economics

    Wallet & Credit System

    Filter with Founder / Developer in the docs sidebar. This page reflects the verified v1.6.4 codebase: PIN-wrapped encryption, atomic single-write provisioning, wallet_access_tokens, and oracle/platform settings persisted via db() (no private pg.Pool).

    Ring Platform's wallet system provides two complementary layers:

    1. Custodial native-token wallets — auto-provisioned for signed-in members on all enabled chains (Solana first, EVM/Base enabled per clone config). Private keys encrypted with WALLET_ENCRYPTION_KEY using AES-256-GCM + scrypt. PIN-wrapped v2 encryption for withdrawal operations.
    2. In-app credit ledger — fiat USD credits stored in credit_balance field on user documents. Used for store purchases, subscriptions, and conversions to native tokens via the Token Desk widget.

    Browser routes: /wallet (dashboard), /wallet/topup, /wallet/send, /wallet/staking.

    Money layers (SSOT)

    Four separate money paths — do not conflate them:

    #PathEntryResult
    1Card top-up (wallet_topup)/wallet/topup / CreditAddFsModal → PaymentConductorCredit points (fiat ledger) — never on-chain RING
    2Token Desk (buy)/wallet Desk widget → desk-service.ts (subscriber+)Credit points → native RING (nativeOut = points / nativePerMainCurrency)
    3Store native_token railPOST /api/store/payments/tokenSpend native RING at checkout (separate from top-up)
    4Chain-proof credit addPOST /api/wallet/credit/topup (tx-hash verify)Credit add after on-chain transfer proof
    5BuyNativeViaCard (native_token_onramp)Confidential tabs when CONFIDENTIAL_TOKEN_ONRAMP=trueCard/PayPal → treasury RING (not credit)

    Any signed-in user can buy credit points with a card (path 1). Converting points → RING is Token Desk (path 2), available to subscriber+. Optional CONFIDENTIAL_TOKEN_ONRAMP=true enables BuyNativeViaCard (native_token_onramp) for confidential / admin / superadmin — card or PayPal tabs that settle native RING from treasury (not credit points).

    What members experience

    Wallet dashboard — /wallet

    Balance hero showing credit balance + native-token balances per chain. Desk widget for converting credits ↔ native tokens.

    Top-up — /wallet/topup

    Add credits via native-token transfer (tx-hash verify) or live Card / PayPal (CreditAddFsModal → PaymentConductor wallet_topup). Card uses env processor (WayForPay or Stripe); PayPal tab sends processor=paypal.

    Send tokens — /wallet/send

    Gasless transfers to contacts — treasury sponsors transaction fees. Pick recipient from contacts or enter address.

    Store & credits

    Architecture

    The wallet system uses server actions (app/_actions/wallet.ts) as the primary integration layer. These actions call SSOT service files in features/wallet/ and lib/wallet/.

    Server actions inventory

    #ActionLayerPurpose
    1ensureUserWalletsWalletProvisions wallets for all enabled chains (atomic single-write)
    2listUserWalletsWalletLists wallets with on-chain balances
    3getWalletBalanceWalletNative-token balance via viem/Solana RPC
    4

    Related documentation

    WalletConductor

    SSOT facade for top-up, desk, custodial send, credit spend, NFT buy.

    Wallet API reference

    Complete server action signatures and response types.

    Authentication

    Social sign-in triggers wallet provisioning.

    Store feature

    Credit spend at checkout.

    Token economics

    Wallet & Credit System

    Filter with Founder / Developer in the docs sidebar. This page reflects the verified v1.6.4 codebase: PIN-wrapped encryption, atomic single-write provisioning, wallet_access_tokens, and oracle/platform settings persisted via db() (no private pg.Pool).

    Ring Platform's wallet system provides two complementary layers:

    1. Custodial native-token wallets — auto-provisioned for signed-in members on all enabled chains (Solana first, EVM/Base enabled per clone config). Private keys encrypted with WALLET_ENCRYPTION_KEY using AES-256-GCM + scrypt. PIN-wrapped v2 encryption for withdrawal operations.
    2. In-app credit ledger — fiat USD credits stored in credit_balance field on user documents. Used for store purchases, subscriptions, and conversions to native tokens via the Token Desk widget.

    Browser routes: /wallet (dashboard), /wallet/topup, /wallet/send, /wallet/staking.

    Money layers (SSOT)

    Four separate money paths — do not conflate them:

    #PathEntryResult
    1Card top-up (wallet_topup)/wallet/topup / CreditAddFsModal → PaymentConductorCredit points (fiat ledger) — never on-chain RING
    2Token Desk (buy)/wallet Desk widget → desk-service.ts (subscriber+)Credit points → native RING (nativeOut = points / nativePerMainCurrency)
    3Store native_token railPOST /api/store/payments/tokenSpend native RING at checkout (separate from top-up)
    4Chain-proof credit addPOST /api/wallet/credit/topup (tx-hash verify)Credit add after on-chain transfer proof
    5BuyNativeViaCard (native_token_onramp)Confidential tabs when CONFIDENTIAL_TOKEN_ONRAMP=trueCard/PayPal → treasury RING (not credit)

    Any signed-in user can buy credit points with a card (path 1). Converting points → RING is Token Desk (path 2), available to subscriber+. Optional CONFIDENTIAL_TOKEN_ONRAMP=true enables BuyNativeViaCard (native_token_onramp) for confidential / admin / superadmin — card or PayPal tabs that settle native RING from treasury (not credit points).

    What members experience

    Wallet dashboard — /wallet

    Balance hero showing credit balance + native-token balances per chain. Desk widget for converting credits ↔ native tokens.

    Top-up — /wallet/topup

    Add credits via native-token transfer (tx-hash verify) or live Card / PayPal (CreditAddFsModal → PaymentConductor wallet_topup). Card uses env processor (WayForPay or Stripe); PayPal tab sends processor=paypal.

    Send tokens — /wallet/send

    Gasless transfers to contacts — treasury sponsors transaction fees. Pick recipient from contacts or enter address.

    Store & credits

    Architecture

    The wallet system uses server actions (app/_actions/wallet.ts) as the primary integration layer. These actions call SSOT service files in features/wallet/ and lib/wallet/.

    Server actions inventory

    #ActionLayerPurpose
    1ensureUserWalletsWalletProvisions wallets for all enabled chains (atomic single-write)
    2listUserWalletsWalletLists wallets with on-chain balances
    3getWalletBalanceWalletNative-token balance via viem/Solana RPC
    4

    Related documentation

    WalletConductor

    SSOT facade for top-up, desk, custodial send, credit spend, NFT buy.

    Wallet API reference

    Complete server action signatures and response types.

    Authentication

    Social sign-in triggers wallet provisioning.

    Store feature

    Credit spend at checkout.

    Token economics

    Credit spend at checkout — ledger debits before on-chain settlement in some flows.

    Core concepts for clone operators

    Credit balance is fiat points denominated in store mainCurrency (USD on ring-platform.org). Accounting multiplier is credit.creditBalanceUnitToMainCurrency in ring-config.json (usually 1 = 1:1). Members earn or top up credits; convert to native token only through the Token Desk (separate oracle rate in platform_settings.web3.oracle.nativePerMainCurrency).

    Native token transfers are gasless. The clone treasury sponsors Solana transaction fees so members never need SOL for gas. An optional transfer tax can be configured per clone for high-frequency send projects.

    Wallet provisioning is automatic. When a member signs in (Google, Apple, or credentials), the system provisions wallets for all enabled chains via a single atomic setUserWallets() write — no JSONB race, max one wallet per chain. No manual setup required.

    PIN-wrapped encryption (2026-07-03): Wallet private keys are encrypted with AES-256-GCM at rest. Members who set a 4-digit PIN get their keys wrapped with PIN-derived entropy — the PIN is verified server-side before any withdrawal operation, never sent to the client in plaintext. Keys stored before PIN setup remain in v1 format (env-only encryption) and are lazily re-encrypted on first PIN-based operation.

    Typical member flows

    • First login — wallet auto-provisioned; credit balance initialized at 0.
    • Check balance — wallet page shows credit balance + native-token balance per chain. Credits push live on Tunnel credit:balance; custodial wallet rows refresh on wallet:list when Tunnel is connected (60s poll only as fallback).
    • Send tokens to a contact — pick recipient from contacts, enter amount, confirm. Gas sponsored by treasury.
    • Top up credits — native-token transfer (tx hash verify) or Add Credit modal (CreditAddFsModal): Card | PayPal tabs → WalletConductor.initiateTopUp → PaymentConductor (amount 25–2000). Browser follows redirect via followCheckoutResult. Balance updates after webhook Approved / capture — not when the browser returns to /wallet.

    Switching a clone’s card rail to Stripe: set PAYMENT_WALLET_TOPUP_PROCESSOR=stripe (or PAYMENT_DEFAULT_PROCESSOR=stripe). The Card tab stays the same — it does not hardcode WayForPay.

    • Set a wallet PIN — profile/security page; 4-digit PIN activates v2 PIN-wrapped encryption for all existing wallets.
    • Authorize a withdrawal — enter PIN; server validates against the stored encryption envelope; single-use access token issued.
    • Convert credits to tokens — use the Desk widget: choose buy/sell direction, get a quote, execute at oracle rate.
    • Pay in store — checkout rail credit_balance debits the fiat ledger (same creditBalanceUnitToMainCurrency rate as ad-hoc spend).
    • Pay with credits (ad-hoc) — spendCredits / POST /api/wallet/credit/spend for non-checkout debits (services, membership fee paths).

    Custodial model: Ring signs transfers server-side for auto-provisioned wallets. External wallets (MetaMask, Coinbase Wallet, WalletConnect via the Wagmi v3 connector picker) connect client-side — they do not use custodial transfer routes. Credits and native-token balances are separate concepts.

    Related documentation

    Related documentation

    Tunnel Protocol

    Depends-on: live `credit:balance` and `wallet:list` updates while the wallet tab stays open.

    Wallet API

    Deep-dive: Server Actions and HTTP SSOT for list, credit, and transfer.

    Token Economics Setup

    See-also: oracle rate, transfer tax, first-settler discounts.

    Multi-Vendor Store

    Same-workflow: credit spend at checkout.

    Authentication

    Prerequisite: Google/Apple/social sign-in triggers wallet provisioning.

    Ethereum wallets (Wagmi v3)

    Deep-dive: Wagmi v3 pathNeedsWeb3 gate, getEvmRpcUrl / getEvmTokenAddress SSOT.

    Public Pools & DAO Jars

    Same-workflow: desk oracle nativePerMainCurrency converts public-pool card chip-ins to pledged native (not wallet_topup 1:1).

    Ring Oracle

    Depends-on: Ring Oracle facade for desk quotes, FX overlay, and credit accounting rates.

    getWalletActivity
    Activity
    Unified feed: credit + chain transactions
    5getCreditHistoryCreditPaginated credit transaction history
    6getCreditBalanceCreditCurrent credit balance (always fiat USD)
    7topUpCreditsCreditAdd credits after on-chain verification
    8spendCreditsCreditAd-hoc fiat debit via WalletConductor.spendCredits (rate = credit.creditBalanceUnitToMainCurrency, not desk oracle)
    9transferCreditsCreditAdmin credit adjustment
    10processMembershipFeeCreditMonthly membership fee from credits
    11transferNativeTokensTokenGasless native-token transfer (treasury-sponsored)
    12getNativeTokenPerMainCurrencyRateOracleCurrent oracle rate
    13setNativeTokenPerMainCurrencyRateOracleUpdate oracle rate (admin-only)
    14signDeskQuoteDeskSign quote for credit↔token conversion
    15executeDeskQuoteDeskExecute signed quote (buy/sell/refund)
    16verifyDeskQuoteDeskVerify signed quote token
    17verifyTopUpTransactionVerifyVerify on-chain ERC20/SPL transfer
    18listWalletTransactionsActivityRead wallet_transactions collection directly
    19setPrimaryWalletWalletSet default wallet address
    20getSpendSummaryCreditPeriod-based credit spending summary
    21getRewardCreditAddEventSummaryCreditCredit rewards summary for user
    22createPinAccessTokenActionSecurityIssue single-use PIN-gated access token (React 19 useActionState)
    23revokePinAccessTokensActionSecurityRevoke all active PIN access tokens for current user
    24initiateCreditTopupPaymentCredit / PaymentsWayForPay card top-up via PaymentConductor wallet_topup (amount 25–2000)

    Integration pattern

    Server actions follow the SSOT pattern: dynamic import of service → auth check → operation → revalidatePath('/[locale]/wallet') for cache invalidation.

    1. 1

      Import and call a server action

      Server actions can be called directly from client components using React 19's useActionState hook.

    2. 2

      Or use with form progressive enhancement

    SSOT service files

    ConcernFileKey exports
    Credit CRUDfeatures/wallet/services/credit-balance-service.tsaddCredits, spendCredits, addFiatUsd, spendFiatUsd, getCreditHistory, hasSufficientBalance
    Gasless transfersfeatures/wallet/chains/native-token-transfer-service.tstransferNativeTokenForUser(userId, toAddress, amount) — treasury sponsors gas
    Treasury opsfeatures/wallet/chains/solana/treasury-transfer-service.tstransferNativeTokenFromTreasury, transferNativeTokenToTreasury, burnRingFromUser
    Desk tradesfeatures/wallet/chains/solana/desk-service.tsquoteDesk, executeDesk — credit↔token conversion
    Oracle ratefeatures/wallet/services/native-token-oracle.tsgetNativeTokenPerMainCurrencyRate, setNativeTokenPerMainCurrencyRate — platform_settings doc web3 via db().readDoc / updateDoc
    Wallet DBlib/wallet/user-wallet-db.tsgetNativeWallet, getUserWallets, setUserWallets, migrateUserWalletsToPin
    Wallet provisioningfeatures/wallet/conductor/wallet-conductor.ts + ensure-wallet.tsApp callers use WalletConductor.ensureNativeWallet; low-level ensureWallets remains the atomic multi-chain writer
    Balance fetchfeatures/wallet/services/list-wallets.tslistWallets — gets on-chain balances via viem/Solana RPC
    Activity feedfeatures/wallet/services/wallet-activity-feed.tsgetWalletActivityFeed — unified credit+chain feed with filters
    Top-up verifyfeatures/wallet/services/topup-verification.tsverifyTopUpTransaction, reserveTopUpTxHash
    Encryptionlib/wallet/encrypt-wallet-secret.tsencryptWalletSecret, decryptSecretWithPin, reencryptLegacyWallet (v1→v2 migration)
    Wallet decryptlib/wallet/decrypt-user-wallet.tsdecryptUserWalletPrivateKey, decryptSolanaWalletSecretKey (Uint8Array), decryptSolanaWalletSecretKeyWithPin
    PIN access tokenslib/wallet/pin-access-token-db.tsissueAccessToken, consumeAccessToken, revokeActiveTokens
    Credit schemaslib/zod/credit-schemas.tsCreditTopUpRequestSchema, CreditSpendRequestSchema, CreditTransactionType
    Desk schemaslib/zod/desk-schemas.tsDeskExecuteRequestSchema, DeskQuoteRequestSchema

    Wallet pages (verified routes)

    RouteComponentDescription
    /walletwallet-client.tsxBalance hero (credit + native-token), DeskWidget, transaction feed
    /wallet/topuptopup-client.tsxNative-token transfer + live WayForPay card (initiateCreditTopupPayment)
    /wallet/sendsend-tokens.tsxGasless transfers to contacts

    PIN-gated access flow

    PIN access token flow

    Encryption envelope format (v2)

    Wallets created after the 2026-07-03 refactor use the versioned v2 format:

    • No PIN: v2:scryptSalt:iv:encrypted:authTag — key derived from WALLET_ENCRYPTION_KEY + random salt.
    • With PIN: same + :sha256(pin) appended — key derived from WALLET_ENCRYPTION_KEY + ":" + sha256(pin) + random salt.
    • Fast reject: stored pinHash enables constant-time PIN rejection before running expensive scrypt (DoS defense).
    • Migration: migrateUserWalletsToPin(userId, pin) re-encrypts all legacy v1 wallets for a user.

    Legacy v1 wallets (iv:encrypted:authTag) remain readable via decryptWalletSecretLegacy().

    Access token properties

    • Storage: wallet_access_tokens table (JSONB, sha256-hashed, never raw token persisted)
    • TTL: 15 minutes (configurable via WALLET_ACCESS_TOKEN_TTL_SECONDS env var)
    • Single-use: consumed atomically via usedAt timestamp after successful validation
    • Auto-revoke: issuing a new token for the same (userId, scope) revokes all prior active tokens — prevents replay
    • Audit: every issuance creates a wallet_transactions row with type pin_access_granted

    Desk trade flow (credit ↔ token conversion)

    Desk trade flow

    WalletChain — SSOT identity model

    The chain identity model was consolidated in the 2026-07-03 refactor:

    TypeDefinitionPurpose
    NativeChain(typeof ringConfig.chains.native)Single chain identity ('solana' on ring-platform.org)
    SupportedChains(typeof ringConfig.chains.supported)[number]All chains the clone supports ('solana' | 'evm' | 'base')
    EnabledChains(typeof ringConfig.chains.enabled)[number]Subset of supported chains actively in use
    WalletChainEnabledChainsChains a user can hold a wallet for (widened from NativeChain for story 5)
    DEFAULT_WALLET_CHAIN'evm'Fallback for legacy chainless wallet rows (pre-Solana EVM era)

    All three previously scattered hardcoded fallbacks ('solana' in utils.ts, 'evm' in user-wallet-db.ts, 'native' bug in ensure-wallet.ts) now point to DEFAULT_WALLET_CHAIN.

    Credit schema (Zod SSOT)

    All credit operations use lib/zod/credit-schemas.ts. Valid transaction types: payment, airdrop, reimbursement, purchase, membership_fee, top_up, bonus, penalty, desk_buy, desk_sell, desk_refund.

    Credit balance is always fiat USD on ring-platform.org — never native-token denomination:

    Conversion formula

    The Token Desk converts at the oracle rate (desk-service.ts):

    Rate stored in platform_settings collection, document id web3, field path oracle.nativePerMainCurrency. native-token-oracle.ts uses db() from @/lib/database — same persistence layer as platform-settings-service.ts. When PLATFORM_SETTINGS_DISABLE_DB=true, reads use RING_ORACLE_DEFAULT_RATE (fallback 100) and writes throw. Superadmin updates via setNativeTokenPerMainCurrencyRate action or POST /api/admin/web3/settings.

    Environment variables

    VariableRequiredUsed by
    WALLET_ENCRYPTION_KEYYesencrypt-wallet-secret.ts — AES-256-GCM encryption of private keys
    SOLANA_RPC_URLYessolana-client.ts — on-chain Solana operations
    SOLANA_TREASURY_PRIVATE_KEYYestreasury-transfer-service.ts — sponsored gas transfers
    WALLET_ACCESS_TOKEN_TTL_SECONDSNo (default: 900)pin-access-token-db.ts — single-use token TTL
    POLYGON_RPC_URL / getEvmRpcUrl()For EVM chainslib/ring-config-chain.ts + features/wallet/chains/evm/evm-token-transfer.ts; live balance via getWalletBalance in app/_actions/wallet.ts
    ORACLE_QUOTE_SECRETFor desknative-token-oracle.ts — desk quote HMAC signing (falls back to WALLET_ENCRYPTION_KEY)
    RING_ORACLE_DEFAULT_RATENo (default 100)Oracle read fallback when DB row missing or PLATFORM_SETTINGS_DISABLE_DB=true
    PLATFORM_SETTINGS_DISABLE_DBNoWhen true, oracle/settings reads use env defaults; writes blocked
    CONFIDENTIAL_TOKEN_ONRAMPNo (default false)PaymentConductor native_token_onramp — confidential+ card/PayPal → treasury RING
    NEXT_PUBLIC_CONFIDENTIAL_TOKEN_ONRAMPNo (default false)Client UI mirror for onramp tabs

    See lib/wallet/encrypt-wallet-secret.ts for the v2 envelope format cryptographic spec.

    Atomic provisioning (race fix)

    ensureWallets() provisions all missing chain wallets into an in-memory Map keyed by chain, then persists via one setUserWallets() write. This eliminates the JSONB read-modify-write race that previous parallel Promise.all(provisionChainWallet()) designs produced, and guarantees the "max one wallet per enabled chain" invariant (appendWalletIfMissing enforces per-chain idempotency).

    Card credit top-up (live) vs native-token rail

    WalletConductor.initiateTopUp / initiateCreditTopupPayment → PaymentConductor.createCheckout({ purpose: 'wallet_topup' }) → processor from env or form processor → normalizeCheckoutResult → UI followCheckoutResult (lib/payments/checkout-redirect.ts).

    Tab (CreditAddFsModal)FormProcessor
    CardNo processor fieldPAYMENT_WALLET_TOPUP_PROCESSOR / PAYMENT_DEFAULT_PROCESSOR (WayForPay HPP form_post or Stripe navigate)
    PayPalprocessor=paypalPayPal Orders v2 approve URL (navigate) when credentials configured

    Webhook → handlers/wallet-topup.ts / wallet-topup-stripe.ts / wallet-topup-paypal.ts → creditBalanceService.addFiatUsd (1:1 fiat points). Amount gate: 25–2000. Card top-up credits the fiat ledger only — it never mints on-chain RING. Entry UI: /wallet/topup and features/wallet/components/credit-add-fs-modal.tsx.

    Deep HPP / returnUrl vs serviceUrl: WayForPay · PaymentConductor architecture.

    To spend native RING directly, use the store native_token rail (POST /api/store/payments/token) — a distinct path from top-up. See PaymentConductor.

    Chain-proof credit add — POST /api/wallet/credit/topup

    Distinct from PaymentConductor card top-up. Verifies an on-chain transfer to treasury: when isChainProofRequired(), the request must include a tx_hash which is reserved (reserveTopUpTxHash, anti-double-spend) and verified (verifyTopUpTransaction) before creditBalanceService.addCredits. Non-top_up transaction types (airdrop/bonus) require platform-admin role.

    WalletConductor (facade — adopted)

    features/wallet/conductor/wallet-conductor.ts is the SSOT orchestration layer for custodial native-token web3 + credit money paths. Thin adapters (app/_actions/wallet.ts, /api/wallet/token/*, /api/wallet/desk/*) call the conductor; it owns PaymentConductor checkouts for wallet_topup / native_token_onramp, Token Desk, send, and user credit-spend. Legacy /api/wallet/ring/* aliases are removed — use /api/wallet/token/*.

    MethodPurpose
    ensureNativeWallet(userOverride)Provision native + enabled-chain wallets; returns { ok, native, wallets } (OAuth / override-safe; no session required)
    ensureFunded(minCredits)Session-gated: provision via ensureNativeWallet, assert minimum credit balance
    initiateTopUpCard / PayPal → credit points (wallet_topup); returns Conductor redirect
    initiateNativeOnrampConfidential+ card/PayPal → treasury native (native_token_onramp; gated by isNativeTokenOnrampEnabled)
    quoteDesk / executeDeskCredit points → native RING (subscriber+; Solana desk)
    getNativeBalanceCustodial native balance
    transferNativeGasless custodial native send (chains.native / chains.enabled)
    spendCreditsUser credit-spend API / FormData path — fiat rate from credit.creditBalanceUnitToMainCurrency (not desk oracle)

    Not in WalletConductor: external EVM wallet USDT/POL via POST /api/wallet/transfer (SupportedCrypto when chains.enabled includes evm) — keep separate from Solana custodial SSOT.

    Fiat credit spend — two-rate boundary

    Fiat accounting (getMainCurrencyCreditAccountingRate → credit.creditBalanceUnitToMainCurrency) applies to ledger debits. Desk oracle (nativePerMainCurrency) applies only to credit ↔ native conversion. Mixing them understates or overstates store/ad-hoc spends.

    PathEntryRate helper
    Ad-hoc debitspendCredits action / POST /api/wallet/credit/spend → WalletConductor.spendCreditsgetMainCurrencyCreditAccountingRate()
    Store checkoutPaymentConductor credit_balance → credit-balance.processor.tsgetMainCurrencyCreditAccountingRate()
    Desk buy/sellquoteDesk / executeDeskgetNativeTokenPerMainCurrencyRate() only

    Deep HTTP tables and Mermaid: Wallet API.

    Oracle rate, transfer tax, first-settler discounts.

    text
    
    v2:<scryptSaltHex>:<ivHex>:<encryptedHex>:<authTagHex>[:<pinHashHex>]
    text
    
    nativeOut = (points × pointFiatValue) / nativePerMainCurrency   // pointFiatValue = 1
    100 credit points (USD) at nativePerMainCurrency=100 → 1 native token

    Credit spend at checkout — ledger debits before on-chain settlement in some flows.

    Core concepts for clone operators

    Credit balance is fiat points denominated in store mainCurrency (USD on ring-platform.org). Accounting multiplier is credit.creditBalanceUnitToMainCurrency in ring-config.json (usually 1 = 1:1). Members earn or top up credits; convert to native token only through the Token Desk (separate oracle rate in platform_settings.web3.oracle.nativePerMainCurrency).

    Native token transfers are gasless. The clone treasury sponsors Solana transaction fees so members never need SOL for gas. An optional transfer tax can be configured per clone for high-frequency send projects.

    Wallet provisioning is automatic. When a member signs in (Google, Apple, or credentials), the system provisions wallets for all enabled chains via a single atomic setUserWallets() write — no JSONB race, max one wallet per chain. No manual setup required.

    PIN-wrapped encryption (2026-07-03): Wallet private keys are encrypted with AES-256-GCM at rest. Members who set a 4-digit PIN get their keys wrapped with PIN-derived entropy — the PIN is verified server-side before any withdrawal operation, never sent to the client in plaintext. Keys stored before PIN setup remain in v1 format (env-only encryption) and are lazily re-encrypted on first PIN-based operation.

    Typical member flows

    • First login — wallet auto-provisioned; credit balance initialized at 0.
    • Check balance — wallet page shows credit balance + native-token balance per chain. Credits push live on Tunnel credit:balance; custodial wallet rows refresh on wallet:list when Tunnel is connected (60s poll only as fallback).
    • Send tokens to a contact — pick recipient from contacts, enter amount, confirm. Gas sponsored by treasury.
    • Top up credits — native-token transfer (tx hash verify) or Add Credit modal (CreditAddFsModal): Card | PayPal tabs → WalletConductor.initiateTopUp → PaymentConductor (amount 25–2000). Browser follows redirect via followCheckoutResult. Balance updates after webhook Approved / capture — not when the browser returns to /wallet.

    Switching a clone’s card rail to Stripe: set PAYMENT_WALLET_TOPUP_PROCESSOR=stripe (or PAYMENT_DEFAULT_PROCESSOR=stripe). The Card tab stays the same — it does not hardcode WayForPay.

    • Set a wallet PIN — profile/security page; 4-digit PIN activates v2 PIN-wrapped encryption for all existing wallets.
    • Authorize a withdrawal — enter PIN; server validates against the stored encryption envelope; single-use access token issued.
    • Convert credits to tokens — use the Desk widget: choose buy/sell direction, get a quote, execute at oracle rate.
    • Pay in store — checkout rail credit_balance debits the fiat ledger (same creditBalanceUnitToMainCurrency rate as ad-hoc spend).
    • Pay with credits (ad-hoc) — spendCredits / POST /api/wallet/credit/spend for non-checkout debits (services, membership fee paths).

    Custodial model: Ring signs transfers server-side for auto-provisioned wallets. External wallets (MetaMask, Coinbase Wallet, WalletConnect via the Wagmi v3 connector picker) connect client-side — they do not use custodial transfer routes. Credits and native-token balances are separate concepts.

    Related documentation

    Related documentation

    Tunnel Protocol

    Depends-on: live `credit:balance` and `wallet:list` updates while the wallet tab stays open.

    Wallet API

    Deep-dive: Server Actions and HTTP SSOT for list, credit, and transfer.

    Token Economics Setup

    See-also: oracle rate, transfer tax, first-settler discounts.

    Multi-Vendor Store

    Same-workflow: credit spend at checkout.

    Authentication

    Prerequisite: Google/Apple/social sign-in triggers wallet provisioning.

    Ethereum wallets (Wagmi v3)

    Deep-dive: Wagmi v3 pathNeedsWeb3 gate, getEvmRpcUrl / getEvmTokenAddress SSOT.

    Public Pools & DAO Jars

    Same-workflow: desk oracle nativePerMainCurrency converts public-pool card chip-ins to pledged native (not wallet_topup 1:1).

    Ring Oracle

    Depends-on: Ring Oracle facade for desk quotes, FX overlay, and credit accounting rates.

    getWalletActivity
    Activity
    Unified feed: credit + chain transactions
    5getCreditHistoryCreditPaginated credit transaction history
    6getCreditBalanceCreditCurrent credit balance (always fiat USD)
    7topUpCreditsCreditAdd credits after on-chain verification
    8spendCreditsCreditAd-hoc fiat debit via WalletConductor.spendCredits (rate = credit.creditBalanceUnitToMainCurrency, not desk oracle)
    9transferCreditsCreditAdmin credit adjustment
    10processMembershipFeeCreditMonthly membership fee from credits
    11transferNativeTokensTokenGasless native-token transfer (treasury-sponsored)
    12getNativeTokenPerMainCurrencyRateOracleCurrent oracle rate
    13setNativeTokenPerMainCurrencyRateOracleUpdate oracle rate (admin-only)
    14signDeskQuoteDeskSign quote for credit↔token conversion
    15executeDeskQuoteDeskExecute signed quote (buy/sell/refund)
    16verifyDeskQuoteDeskVerify signed quote token
    17verifyTopUpTransactionVerifyVerify on-chain ERC20/SPL transfer
    18listWalletTransactionsActivityRead wallet_transactions collection directly
    19setPrimaryWalletWalletSet default wallet address
    20getSpendSummaryCreditPeriod-based credit spending summary
    21getRewardCreditAddEventSummaryCreditCredit rewards summary for user
    22createPinAccessTokenActionSecurityIssue single-use PIN-gated access token (React 19 useActionState)
    23revokePinAccessTokensActionSecurityRevoke all active PIN access tokens for current user
    24initiateCreditTopupPaymentCredit / PaymentsWayForPay card top-up via PaymentConductor wallet_topup (amount 25–2000)

    Integration pattern

    Server actions follow the SSOT pattern: dynamic import of service → auth check → operation → revalidatePath('/[locale]/wallet') for cache invalidation.

    1. 1

      Import and call a server action

      Server actions can be called directly from client components using React 19's useActionState hook.

    2. 2

      Or use with form progressive enhancement

    SSOT service files

    ConcernFileKey exports
    Credit CRUDfeatures/wallet/services/credit-balance-service.tsaddCredits, spendCredits, addFiatUsd, spendFiatUsd, getCreditHistory, hasSufficientBalance
    Gasless transfersfeatures/wallet/chains/native-token-transfer-service.tstransferNativeTokenForUser(userId, toAddress, amount) — treasury sponsors gas
    Treasury opsfeatures/wallet/chains/solana/treasury-transfer-service.tstransferNativeTokenFromTreasury, transferNativeTokenToTreasury, burnRingFromUser
    Desk tradesfeatures/wallet/chains/solana/desk-service.tsquoteDesk, executeDesk — credit↔token conversion
    Oracle ratefeatures/wallet/services/native-token-oracle.tsgetNativeTokenPerMainCurrencyRate, setNativeTokenPerMainCurrencyRate — platform_settings doc web3 via db().readDoc / updateDoc
    Wallet DBlib/wallet/user-wallet-db.tsgetNativeWallet, getUserWallets, setUserWallets, migrateUserWalletsToPin
    Wallet provisioningfeatures/wallet/conductor/wallet-conductor.ts + ensure-wallet.tsApp callers use WalletConductor.ensureNativeWallet; low-level ensureWallets remains the atomic multi-chain writer
    Balance fetchfeatures/wallet/services/list-wallets.tslistWallets — gets on-chain balances via viem/Solana RPC
    Activity feedfeatures/wallet/services/wallet-activity-feed.tsgetWalletActivityFeed — unified credit+chain feed with filters
    Top-up verifyfeatures/wallet/services/topup-verification.tsverifyTopUpTransaction, reserveTopUpTxHash
    Encryptionlib/wallet/encrypt-wallet-secret.tsencryptWalletSecret, decryptSecretWithPin, reencryptLegacyWallet (v1→v2 migration)
    Wallet decryptlib/wallet/decrypt-user-wallet.tsdecryptUserWalletPrivateKey, decryptSolanaWalletSecretKey (Uint8Array), decryptSolanaWalletSecretKeyWithPin
    PIN access tokenslib/wallet/pin-access-token-db.tsissueAccessToken, consumeAccessToken, revokeActiveTokens
    Credit schemaslib/zod/credit-schemas.tsCreditTopUpRequestSchema, CreditSpendRequestSchema, CreditTransactionType
    Desk schemaslib/zod/desk-schemas.tsDeskExecuteRequestSchema, DeskQuoteRequestSchema

    Wallet pages (verified routes)

    RouteComponentDescription
    /walletwallet-client.tsxBalance hero (credit + native-token), DeskWidget, transaction feed
    /wallet/topuptopup-client.tsxNative-token transfer + live WayForPay card (initiateCreditTopupPayment)
    /wallet/sendsend-tokens.tsxGasless transfers to contacts

    PIN-gated access flow

    PIN access token flow

    Encryption envelope format (v2)

    Wallets created after the 2026-07-03 refactor use the versioned v2 format:

    • No PIN: v2:scryptSalt:iv:encrypted:authTag — key derived from WALLET_ENCRYPTION_KEY + random salt.
    • With PIN: same + :sha256(pin) appended — key derived from WALLET_ENCRYPTION_KEY + ":" + sha256(pin) + random salt.
    • Fast reject: stored pinHash enables constant-time PIN rejection before running expensive scrypt (DoS defense).
    • Migration: migrateUserWalletsToPin(userId, pin) re-encrypts all legacy v1 wallets for a user.

    Legacy v1 wallets (iv:encrypted:authTag) remain readable via decryptWalletSecretLegacy().

    Access token properties

    • Storage: wallet_access_tokens table (JSONB, sha256-hashed, never raw token persisted)
    • TTL: 15 minutes (configurable via WALLET_ACCESS_TOKEN_TTL_SECONDS env var)
    • Single-use: consumed atomically via usedAt timestamp after successful validation
    • Auto-revoke: issuing a new token for the same (userId, scope) revokes all prior active tokens — prevents replay
    • Audit: every issuance creates a wallet_transactions row with type pin_access_granted

    Desk trade flow (credit ↔ token conversion)

    Desk trade flow

    WalletChain — SSOT identity model

    The chain identity model was consolidated in the 2026-07-03 refactor:

    TypeDefinitionPurpose
    NativeChain(typeof ringConfig.chains.native)Single chain identity ('solana' on ring-platform.org)
    SupportedChains(typeof ringConfig.chains.supported)[number]All chains the clone supports ('solana' | 'evm' | 'base')
    EnabledChains(typeof ringConfig.chains.enabled)[number]Subset of supported chains actively in use
    WalletChainEnabledChainsChains a user can hold a wallet for (widened from NativeChain for story 5)
    DEFAULT_WALLET_CHAIN'evm'Fallback for legacy chainless wallet rows (pre-Solana EVM era)

    All three previously scattered hardcoded fallbacks ('solana' in utils.ts, 'evm' in user-wallet-db.ts, 'native' bug in ensure-wallet.ts) now point to DEFAULT_WALLET_CHAIN.

    Credit schema (Zod SSOT)

    All credit operations use lib/zod/credit-schemas.ts. Valid transaction types: payment, airdrop, reimbursement, purchase, membership_fee, top_up, bonus, penalty, desk_buy, desk_sell, desk_refund.

    Credit balance is always fiat USD on ring-platform.org — never native-token denomination:

    Conversion formula

    The Token Desk converts at the oracle rate (desk-service.ts):

    Rate stored in platform_settings collection, document id web3, field path oracle.nativePerMainCurrency. native-token-oracle.ts uses db() from @/lib/database — same persistence layer as platform-settings-service.ts. When PLATFORM_SETTINGS_DISABLE_DB=true, reads use RING_ORACLE_DEFAULT_RATE (fallback 100) and writes throw. Superadmin updates via setNativeTokenPerMainCurrencyRate action or POST /api/admin/web3/settings.

    Environment variables

    VariableRequiredUsed by
    WALLET_ENCRYPTION_KEYYesencrypt-wallet-secret.ts — AES-256-GCM encryption of private keys
    SOLANA_RPC_URLYessolana-client.ts — on-chain Solana operations
    SOLANA_TREASURY_PRIVATE_KEYYestreasury-transfer-service.ts — sponsored gas transfers
    WALLET_ACCESS_TOKEN_TTL_SECONDSNo (default: 900)pin-access-token-db.ts — single-use token TTL
    POLYGON_RPC_URL / getEvmRpcUrl()For EVM chainslib/ring-config-chain.ts + features/wallet/chains/evm/evm-token-transfer.ts; live balance via getWalletBalance in app/_actions/wallet.ts
    ORACLE_QUOTE_SECRETFor desknative-token-oracle.ts — desk quote HMAC signing (falls back to WALLET_ENCRYPTION_KEY)
    RING_ORACLE_DEFAULT_RATENo (default 100)Oracle read fallback when DB row missing or PLATFORM_SETTINGS_DISABLE_DB=true
    PLATFORM_SETTINGS_DISABLE_DBNoWhen true, oracle/settings reads use env defaults; writes blocked
    CONFIDENTIAL_TOKEN_ONRAMPNo (default false)PaymentConductor native_token_onramp — confidential+ card/PayPal → treasury RING
    NEXT_PUBLIC_CONFIDENTIAL_TOKEN_ONRAMPNo (default false)Client UI mirror for onramp tabs

    See lib/wallet/encrypt-wallet-secret.ts for the v2 envelope format cryptographic spec.

    Atomic provisioning (race fix)

    ensureWallets() provisions all missing chain wallets into an in-memory Map keyed by chain, then persists via one setUserWallets() write. This eliminates the JSONB read-modify-write race that previous parallel Promise.all(provisionChainWallet()) designs produced, and guarantees the "max one wallet per enabled chain" invariant (appendWalletIfMissing enforces per-chain idempotency).

    Card credit top-up (live) vs native-token rail

    WalletConductor.initiateTopUp / initiateCreditTopupPayment → PaymentConductor.createCheckout({ purpose: 'wallet_topup' }) → processor from env or form processor → normalizeCheckoutResult → UI followCheckoutResult (lib/payments/checkout-redirect.ts).

    Tab (CreditAddFsModal)FormProcessor
    CardNo processor fieldPAYMENT_WALLET_TOPUP_PROCESSOR / PAYMENT_DEFAULT_PROCESSOR (WayForPay HPP form_post or Stripe navigate)
    PayPalprocessor=paypalPayPal Orders v2 approve URL (navigate) when credentials configured

    Webhook → handlers/wallet-topup.ts / wallet-topup-stripe.ts / wallet-topup-paypal.ts → creditBalanceService.addFiatUsd (1:1 fiat points). Amount gate: 25–2000. Card top-up credits the fiat ledger only — it never mints on-chain RING. Entry UI: /wallet/topup and features/wallet/components/credit-add-fs-modal.tsx.

    Deep HPP / returnUrl vs serviceUrl: WayForPay · PaymentConductor architecture.

    To spend native RING directly, use the store native_token rail (POST /api/store/payments/token) — a distinct path from top-up. See PaymentConductor.

    Chain-proof credit add — POST /api/wallet/credit/topup

    Distinct from PaymentConductor card top-up. Verifies an on-chain transfer to treasury: when isChainProofRequired(), the request must include a tx_hash which is reserved (reserveTopUpTxHash, anti-double-spend) and verified (verifyTopUpTransaction) before creditBalanceService.addCredits. Non-top_up transaction types (airdrop/bonus) require platform-admin role.

    WalletConductor (facade — adopted)

    features/wallet/conductor/wallet-conductor.ts is the SSOT orchestration layer for custodial native-token web3 + credit money paths. Thin adapters (app/_actions/wallet.ts, /api/wallet/token/*, /api/wallet/desk/*) call the conductor; it owns PaymentConductor checkouts for wallet_topup / native_token_onramp, Token Desk, send, and user credit-spend. Legacy /api/wallet/ring/* aliases are removed — use /api/wallet/token/*.

    MethodPurpose
    ensureNativeWallet(userOverride)Provision native + enabled-chain wallets; returns { ok, native, wallets } (OAuth / override-safe; no session required)
    ensureFunded(minCredits)Session-gated: provision via ensureNativeWallet, assert minimum credit balance
    initiateTopUpCard / PayPal → credit points (wallet_topup); returns Conductor redirect
    initiateNativeOnrampConfidential+ card/PayPal → treasury native (native_token_onramp; gated by isNativeTokenOnrampEnabled)
    quoteDesk / executeDeskCredit points → native RING (subscriber+; Solana desk)
    getNativeBalanceCustodial native balance
    transferNativeGasless custodial native send (chains.native / chains.enabled)
    spendCreditsUser credit-spend API / FormData path — fiat rate from credit.creditBalanceUnitToMainCurrency (not desk oracle)

    Not in WalletConductor: external EVM wallet USDT/POL via POST /api/wallet/transfer (SupportedCrypto when chains.enabled includes evm) — keep separate from Solana custodial SSOT.

    Fiat credit spend — two-rate boundary

    Fiat accounting (getMainCurrencyCreditAccountingRate → credit.creditBalanceUnitToMainCurrency) applies to ledger debits. Desk oracle (nativePerMainCurrency) applies only to credit ↔ native conversion. Mixing them understates or overstates store/ad-hoc spends.

    PathEntryRate helper
    Ad-hoc debitspendCredits action / POST /api/wallet/credit/spend → WalletConductor.spendCreditsgetMainCurrencyCreditAccountingRate()
    Store checkoutPaymentConductor credit_balance → credit-balance.processor.tsgetMainCurrencyCreditAccountingRate()
    Desk buy/sellquoteDesk / executeDeskgetNativeTokenPerMainCurrencyRate() only

    Deep HTTP tables and Mermaid: Wallet API.

    Oracle rate, transfer tax, first-settler discounts.

    text
    
    v2:<scryptSaltHex>:<ivHex>:<encryptedHex>:<authTagHex>[:<pinHashHex>]
    text
    
    nativeOut = (points × pointFiatValue) / nativePerMainCurrency   // pointFiatValue = 1
    100 credit points (USD) at nativePerMainCurrency=100 → 1 native token

    Credit spend at checkout — ledger debits before on-chain settlement in some flows.

    Core concepts for clone operators

    Credit balance is fiat points denominated in store mainCurrency (USD on ring-platform.org). Accounting multiplier is credit.creditBalanceUnitToMainCurrency in ring-config.json (usually 1 = 1:1). Members earn or top up credits; convert to native token only through the Token Desk (separate oracle rate in platform_settings.web3.oracle.nativePerMainCurrency).

    Native token transfers are gasless. The clone treasury sponsors Solana transaction fees so members never need SOL for gas. An optional transfer tax can be configured per clone for high-frequency send projects.

    Wallet provisioning is automatic. When a member signs in (Google, Apple, or credentials), the system provisions wallets for all enabled chains via a single atomic setUserWallets() write — no JSONB race, max one wallet per chain. No manual setup required.

    PIN-wrapped encryption (2026-07-03): Wallet private keys are encrypted with AES-256-GCM at rest. Members who set a 4-digit PIN get their keys wrapped with PIN-derived entropy — the PIN is verified server-side before any withdrawal operation, never sent to the client in plaintext. Keys stored before PIN setup remain in v1 format (env-only encryption) and are lazily re-encrypted on first PIN-based operation.

    Typical member flows

    • First login — wallet auto-provisioned; credit balance initialized at 0.
    • Check balance — wallet page shows credit balance + native-token balance per chain. Credits push live on Tunnel credit:balance; custodial wallet rows refresh on wallet:list when Tunnel is connected (60s poll only as fallback).
    • Send tokens to a contact — pick recipient from contacts, enter amount, confirm. Gas sponsored by treasury.
    • Top up credits — native-token transfer (tx hash verify) or Add Credit modal (CreditAddFsModal): Card | PayPal tabs → WalletConductor.initiateTopUp → PaymentConductor (amount 25–2000). Browser follows redirect via followCheckoutResult. Balance updates after webhook Approved / capture — not when the browser returns to /wallet.

    Switching a clone’s card rail to Stripe: set PAYMENT_WALLET_TOPUP_PROCESSOR=stripe (or PAYMENT_DEFAULT_PROCESSOR=stripe). The Card tab stays the same — it does not hardcode WayForPay.

    • Set a wallet PIN — profile/security page; 4-digit PIN activates v2 PIN-wrapped encryption for all existing wallets.
    • Authorize a withdrawal — enter PIN; server validates against the stored encryption envelope; single-use access token issued.
    • Convert credits to tokens — use the Desk widget: choose buy/sell direction, get a quote, execute at oracle rate.
    • Pay in store — checkout rail credit_balance debits the fiat ledger (same creditBalanceUnitToMainCurrency rate as ad-hoc spend).
    • Pay with credits (ad-hoc) — spendCredits / POST /api/wallet/credit/spend for non-checkout debits (services, membership fee paths).

    Custodial model: Ring signs transfers server-side for auto-provisioned wallets. External wallets (MetaMask, Coinbase Wallet, WalletConnect via the Wagmi v3 connector picker) connect client-side — they do not use custodial transfer routes. Credits and native-token balances are separate concepts.

    Related documentation

    Related documentation

    Tunnel Protocol

    Depends-on: live `credit:balance` and `wallet:list` updates while the wallet tab stays open.

    Wallet API

    Deep-dive: Server Actions and HTTP SSOT for list, credit, and transfer.

    Token Economics Setup

    See-also: oracle rate, transfer tax, first-settler discounts.

    Multi-Vendor Store

    Same-workflow: credit spend at checkout.

    Authentication

    Prerequisite: Google/Apple/social sign-in triggers wallet provisioning.

    Ethereum wallets (Wagmi v3)

    Deep-dive: Wagmi v3 pathNeedsWeb3 gate, getEvmRpcUrl / getEvmTokenAddress SSOT.

    Public Pools & DAO Jars

    Same-workflow: desk oracle nativePerMainCurrency converts public-pool card chip-ins to pledged native (not wallet_topup 1:1).

    Ring Oracle

    Depends-on: Ring Oracle facade for desk quotes, FX overlay, and credit accounting rates.

    getWalletActivity
    Activity
    Unified feed: credit + chain transactions
    5getCreditHistoryCreditPaginated credit transaction history
    6getCreditBalanceCreditCurrent credit balance (always fiat USD)
    7topUpCreditsCreditAdd credits after on-chain verification
    8spendCreditsCreditAd-hoc fiat debit via WalletConductor.spendCredits (rate = credit.creditBalanceUnitToMainCurrency, not desk oracle)
    9transferCreditsCreditAdmin credit adjustment
    10processMembershipFeeCreditMonthly membership fee from credits
    11transferNativeTokensTokenGasless native-token transfer (treasury-sponsored)
    12getNativeTokenPerMainCurrencyRateOracleCurrent oracle rate
    13setNativeTokenPerMainCurrencyRateOracleUpdate oracle rate (admin-only)
    14signDeskQuoteDeskSign quote for credit↔token conversion
    15executeDeskQuoteDeskExecute signed quote (buy/sell/refund)
    16verifyDeskQuoteDeskVerify signed quote token
    17verifyTopUpTransactionVerifyVerify on-chain ERC20/SPL transfer
    18listWalletTransactionsActivityRead wallet_transactions collection directly
    19setPrimaryWalletWalletSet default wallet address
    20getSpendSummaryCreditPeriod-based credit spending summary
    21getRewardCreditAddEventSummaryCreditCredit rewards summary for user
    22createPinAccessTokenActionSecurityIssue single-use PIN-gated access token (React 19 useActionState)
    23revokePinAccessTokensActionSecurityRevoke all active PIN access tokens for current user
    24initiateCreditTopupPaymentCredit / PaymentsWayForPay card top-up via PaymentConductor wallet_topup (amount 25–2000)

    Integration pattern

    Server actions follow the SSOT pattern: dynamic import of service → auth check → operation → revalidatePath('/[locale]/wallet') for cache invalidation.

    1. 1

      Import and call a server action

      Server actions can be called directly from client components using React 19's useActionState hook.

    2. 2

      Or use with form progressive enhancement

    SSOT service files

    ConcernFileKey exports
    Credit CRUDfeatures/wallet/services/credit-balance-service.tsaddCredits, spendCredits, addFiatUsd, spendFiatUsd, getCreditHistory, hasSufficientBalance
    Gasless transfersfeatures/wallet/chains/native-token-transfer-service.tstransferNativeTokenForUser(userId, toAddress, amount) — treasury sponsors gas
    Treasury opsfeatures/wallet/chains/solana/treasury-transfer-service.tstransferNativeTokenFromTreasury, transferNativeTokenToTreasury, burnRingFromUser
    Desk tradesfeatures/wallet/chains/solana/desk-service.tsquoteDesk, executeDesk — credit↔token conversion
    Oracle ratefeatures/wallet/services/native-token-oracle.tsgetNativeTokenPerMainCurrencyRate, setNativeTokenPerMainCurrencyRate — platform_settings doc web3 via db().readDoc / updateDoc
    Wallet DBlib/wallet/user-wallet-db.tsgetNativeWallet, getUserWallets, setUserWallets, migrateUserWalletsToPin
    Wallet provisioningfeatures/wallet/conductor/wallet-conductor.ts + ensure-wallet.tsApp callers use WalletConductor.ensureNativeWallet; low-level ensureWallets remains the atomic multi-chain writer
    Balance fetchfeatures/wallet/services/list-wallets.tslistWallets — gets on-chain balances via viem/Solana RPC
    Activity feedfeatures/wallet/services/wallet-activity-feed.tsgetWalletActivityFeed — unified credit+chain feed with filters
    Top-up verifyfeatures/wallet/services/topup-verification.tsverifyTopUpTransaction, reserveTopUpTxHash
    Encryptionlib/wallet/encrypt-wallet-secret.tsencryptWalletSecret, decryptSecretWithPin, reencryptLegacyWallet (v1→v2 migration)
    Wallet decryptlib/wallet/decrypt-user-wallet.tsdecryptUserWalletPrivateKey, decryptSolanaWalletSecretKey (Uint8Array), decryptSolanaWalletSecretKeyWithPin
    PIN access tokenslib/wallet/pin-access-token-db.tsissueAccessToken, consumeAccessToken, revokeActiveTokens
    Credit schemaslib/zod/credit-schemas.tsCreditTopUpRequestSchema, CreditSpendRequestSchema, CreditTransactionType
    Desk schemaslib/zod/desk-schemas.tsDeskExecuteRequestSchema, DeskQuoteRequestSchema

    Wallet pages (verified routes)

    RouteComponentDescription
    /walletwallet-client.tsxBalance hero (credit + native-token), DeskWidget, transaction feed
    /wallet/topuptopup-client.tsxNative-token transfer + live WayForPay card (initiateCreditTopupPayment)
    /wallet/sendsend-tokens.tsxGasless transfers to contacts

    PIN-gated access flow

    PIN access token flow

    Encryption envelope format (v2)

    Wallets created after the 2026-07-03 refactor use the versioned v2 format:

    • No PIN: v2:scryptSalt:iv:encrypted:authTag — key derived from WALLET_ENCRYPTION_KEY + random salt.
    • With PIN: same + :sha256(pin) appended — key derived from WALLET_ENCRYPTION_KEY + ":" + sha256(pin) + random salt.
    • Fast reject: stored pinHash enables constant-time PIN rejection before running expensive scrypt (DoS defense).
    • Migration: migrateUserWalletsToPin(userId, pin) re-encrypts all legacy v1 wallets for a user.

    Legacy v1 wallets (iv:encrypted:authTag) remain readable via decryptWalletSecretLegacy().

    Access token properties

    • Storage: wallet_access_tokens table (JSONB, sha256-hashed, never raw token persisted)
    • TTL: 15 minutes (configurable via WALLET_ACCESS_TOKEN_TTL_SECONDS env var)
    • Single-use: consumed atomically via usedAt timestamp after successful validation
    • Auto-revoke: issuing a new token for the same (userId, scope) revokes all prior active tokens — prevents replay
    • Audit: every issuance creates a wallet_transactions row with type pin_access_granted

    Desk trade flow (credit ↔ token conversion)

    Desk trade flow

    WalletChain — SSOT identity model

    The chain identity model was consolidated in the 2026-07-03 refactor:

    TypeDefinitionPurpose
    NativeChain(typeof ringConfig.chains.native)Single chain identity ('solana' on ring-platform.org)
    SupportedChains(typeof ringConfig.chains.supported)[number]All chains the clone supports ('solana' | 'evm' | 'base')
    EnabledChains(typeof ringConfig.chains.enabled)[number]Subset of supported chains actively in use
    WalletChainEnabledChainsChains a user can hold a wallet for (widened from NativeChain for story 5)
    DEFAULT_WALLET_CHAIN'evm'Fallback for legacy chainless wallet rows (pre-Solana EVM era)

    All three previously scattered hardcoded fallbacks ('solana' in utils.ts, 'evm' in user-wallet-db.ts, 'native' bug in ensure-wallet.ts) now point to DEFAULT_WALLET_CHAIN.

    Credit schema (Zod SSOT)

    All credit operations use lib/zod/credit-schemas.ts. Valid transaction types: payment, airdrop, reimbursement, purchase, membership_fee, top_up, bonus, penalty, desk_buy, desk_sell, desk_refund.

    Credit balance is always fiat USD on ring-platform.org — never native-token denomination:

    Conversion formula

    The Token Desk converts at the oracle rate (desk-service.ts):

    Rate stored in platform_settings collection, document id web3, field path oracle.nativePerMainCurrency. native-token-oracle.ts uses db() from @/lib/database — same persistence layer as platform-settings-service.ts. When PLATFORM_SETTINGS_DISABLE_DB=true, reads use RING_ORACLE_DEFAULT_RATE (fallback 100) and writes throw. Superadmin updates via setNativeTokenPerMainCurrencyRate action or POST /api/admin/web3/settings.

    Environment variables

    VariableRequiredUsed by
    WALLET_ENCRYPTION_KEYYesencrypt-wallet-secret.ts — AES-256-GCM encryption of private keys
    SOLANA_RPC_URLYessolana-client.ts — on-chain Solana operations
    SOLANA_TREASURY_PRIVATE_KEYYestreasury-transfer-service.ts — sponsored gas transfers
    WALLET_ACCESS_TOKEN_TTL_SECONDSNo (default: 900)pin-access-token-db.ts — single-use token TTL
    POLYGON_RPC_URL / getEvmRpcUrl()For EVM chainslib/ring-config-chain.ts + features/wallet/chains/evm/evm-token-transfer.ts; live balance via getWalletBalance in app/_actions/wallet.ts
    ORACLE_QUOTE_SECRETFor desknative-token-oracle.ts — desk quote HMAC signing (falls back to WALLET_ENCRYPTION_KEY)
    RING_ORACLE_DEFAULT_RATENo (default 100)Oracle read fallback when DB row missing or PLATFORM_SETTINGS_DISABLE_DB=true
    PLATFORM_SETTINGS_DISABLE_DBNoWhen true, oracle/settings reads use env defaults; writes blocked
    CONFIDENTIAL_TOKEN_ONRAMPNo (default false)PaymentConductor native_token_onramp — confidential+ card/PayPal → treasury RING
    NEXT_PUBLIC_CONFIDENTIAL_TOKEN_ONRAMPNo (default false)Client UI mirror for onramp tabs

    See lib/wallet/encrypt-wallet-secret.ts for the v2 envelope format cryptographic spec.

    Atomic provisioning (race fix)

    ensureWallets() provisions all missing chain wallets into an in-memory Map keyed by chain, then persists via one setUserWallets() write. This eliminates the JSONB read-modify-write race that previous parallel Promise.all(provisionChainWallet()) designs produced, and guarantees the "max one wallet per enabled chain" invariant (appendWalletIfMissing enforces per-chain idempotency).

    Card credit top-up (live) vs native-token rail

    WalletConductor.initiateTopUp / initiateCreditTopupPayment → PaymentConductor.createCheckout({ purpose: 'wallet_topup' }) → processor from env or form processor → normalizeCheckoutResult → UI followCheckoutResult (lib/payments/checkout-redirect.ts).

    Tab (CreditAddFsModal)FormProcessor
    CardNo processor fieldPAYMENT_WALLET_TOPUP_PROCESSOR / PAYMENT_DEFAULT_PROCESSOR (WayForPay HPP form_post or Stripe navigate)
    PayPalprocessor=paypalPayPal Orders v2 approve URL (navigate) when credentials configured

    Webhook → handlers/wallet-topup.ts / wallet-topup-stripe.ts / wallet-topup-paypal.ts → creditBalanceService.addFiatUsd (1:1 fiat points). Amount gate: 25–2000. Card top-up credits the fiat ledger only — it never mints on-chain RING. Entry UI: /wallet/topup and features/wallet/components/credit-add-fs-modal.tsx.

    Deep HPP / returnUrl vs serviceUrl: WayForPay · PaymentConductor architecture.

    To spend native RING directly, use the store native_token rail (POST /api/store/payments/token) — a distinct path from top-up. See PaymentConductor.

    Chain-proof credit add — POST /api/wallet/credit/topup

    Distinct from PaymentConductor card top-up. Verifies an on-chain transfer to treasury: when isChainProofRequired(), the request must include a tx_hash which is reserved (reserveTopUpTxHash, anti-double-spend) and verified (verifyTopUpTransaction) before creditBalanceService.addCredits. Non-top_up transaction types (airdrop/bonus) require platform-admin role.

    WalletConductor (facade — adopted)

    features/wallet/conductor/wallet-conductor.ts is the SSOT orchestration layer for custodial native-token web3 + credit money paths. Thin adapters (app/_actions/wallet.ts, /api/wallet/token/*, /api/wallet/desk/*) call the conductor; it owns PaymentConductor checkouts for wallet_topup / native_token_onramp, Token Desk, send, and user credit-spend. Legacy /api/wallet/ring/* aliases are removed — use /api/wallet/token/*.

    MethodPurpose
    ensureNativeWallet(userOverride)Provision native + enabled-chain wallets; returns { ok, native, wallets } (OAuth / override-safe; no session required)
    ensureFunded(minCredits)Session-gated: provision via ensureNativeWallet, assert minimum credit balance
    initiateTopUpCard / PayPal → credit points (wallet_topup); returns Conductor redirect
    initiateNativeOnrampConfidential+ card/PayPal → treasury native (native_token_onramp; gated by isNativeTokenOnrampEnabled)
    quoteDesk / executeDeskCredit points → native RING (subscriber+; Solana desk)
    getNativeBalanceCustodial native balance
    transferNativeGasless custodial native send (chains.native / chains.enabled)
    spendCreditsUser credit-spend API / FormData path — fiat rate from credit.creditBalanceUnitToMainCurrency (not desk oracle)

    Not in WalletConductor: external EVM wallet USDT/POL via POST /api/wallet/transfer (SupportedCrypto when chains.enabled includes evm) — keep separate from Solana custodial SSOT.

    Fiat credit spend — two-rate boundary

    Fiat accounting (getMainCurrencyCreditAccountingRate → credit.creditBalanceUnitToMainCurrency) applies to ledger debits. Desk oracle (nativePerMainCurrency) applies only to credit ↔ native conversion. Mixing them understates or overstates store/ad-hoc spends.

    PathEntryRate helper
    Ad-hoc debitspendCredits action / POST /api/wallet/credit/spend → WalletConductor.spendCreditsgetMainCurrencyCreditAccountingRate()
    Store checkoutPaymentConductor credit_balance → credit-balance.processor.tsgetMainCurrencyCreditAccountingRate()
    Desk buy/sellquoteDesk / executeDeskgetNativeTokenPerMainCurrencyRate() only

    Deep HTTP tables and Mermaid: Wallet API.

    Oracle rate, transfer tax, first-settler discounts.

    text
    
    v2:<scryptSaltHex>:<ivHex>:<encryptedHex>:<authTagHex>[:<pinHashHex>]
    text
    
    nativeOut = (points × pointFiatValue) / nativePerMainCurrency   // pointFiatValue = 1
    100 credit points (USD) at nativePerMainCurrency=100 → 1 native token
    1. Docs
    2. /Features
    3. /Wallet & Credit System

    Updated Jul 21, 202613 min listen

    1. Docs
    2. /Features
    3. /Wallet & Credit System

    Updated Jul 21, 202613 min listen

    1. Docs
    2. /Features
    3. /Wallet & Credit System

    Updated Jul 21, 202613 min listen