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
    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
    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
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    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
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    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
    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
    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
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    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
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    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
    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
    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
    Branding
    Themes
    Features
    Localization
    Token Economics Setup
    Payment Gateway Integration
    Reference Ring deployments
    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
    Docker
    Environment Configuration
    Monitoring & Analytics
    Performance Optimization
    Backup & Recovery
    Architecture
    Data Model
    Security
    Real Time
    Discovery Mutation Sync
    PaymentConductor architecture
    WalletConductor architecture
    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)
    1. Docs
    2. /Features
    3. /Referral Codes (Refcodes)

    Updated Jun 12, 20267 min listen

    Ring Platform Logo

    Завантаження документації...

    Підготовка контенту платформи Ring

    1. Docs
    2. /Features
    3. /Referral Codes (Refcodes)

    Updated Jun 12, 20267 min listen

    Ring Platform Logo

    Завантаження документації...

    Підготовка контенту платформи Ring

    1. Docs
    2. /Features
    3. /Referral Codes (Refcodes)

    Updated Jun 12, 20267 min listen

    Ring Platform Logo

    Завантаження документації...

    Підготовка контенту платформи Ring

    Referral Codes (Refcodes)

    The refcodes module turns Ring Platform into a referral growth engine: each connected wallet gets a random shareable code, first-time buyers can be attributed to a referrer, and referrers earn project tokens minted on-chain by a server-side operator wallet.

    Dual-rail context

    Token rewards (this module) run alongside vendor-funded ERP referral commission on the same attribution. See Affiliate & referral enablement for the full dual-rail map and audit keys.

    Architecture: Refcodes architecture · Dual-rail overview: Affiliate enablement · Migrations: Getting started: migrations · Clone install: scripts/install-refcodes-module.sh

    Overview

    LayerResponsibility
    Attribution?ref=CODE → ring_ref + ring_ref_visible cookies (30 days, first-touch)
    SignupNew users persist users.data.referredBy from ring_ref on first sign-in
    CheckoutReferralCheckoutBadge on review step; APIs return referralApplied + referralCode; toast or sessionStorage flash
    VisitsvisitDaily buckets + POST /api/refcodes/track; stats on /refcodes and /admin/refcodes
    LedgerPostgreSQL referral_rewards — off-chain state + mint tracking
    ERP railSame resolveReferralCommissionPercent hierarchy deducts vendor commission — see ERP commissions
    NotifyREFERRAL_REWARD_MINTED in-app notification after mint (locale from modules/refcodes.json)
    On-chainUUPS ReferralRewards contract — idempotent payReferral per order

    Business rules

    • One code per wallet — 8-character random code (Base58-style alphabet, no ambiguous chars).
    • First purchase only — buyer with any prior paid order is not attributed.
    • No self-referral — referrer user ID or any linked buyer wallet blocks attribution.
    • Idempotent rewards — one reward row per orderReference; contract rejects duplicate orderRef hashes.
    • Payment path — WayForPay (rail: 'fiat') creates pending_approval rewards until admin approves; internal credit (rail: 'crypto') auto-approves and mints immediately.
    • Referrer-only incentive — referee checkout discount is deferred; attribution still surfaces in checkout UX.

    User journey

    Routes and UI

    RouteAccessPurpose
    /refcodesAuthenticatedList codes per wallet, copy share URLs, reward history
    /admin/refcodesAdminApprove/reject pending fiat rewards, trigger mint
    GET /api/refcodesSessionJSON: codes + stats for current user
    POST /api/refcodes/mintAdminBatch-mint approved rewards
    POST /api/refcodes/trackPublicVisit beacon — increments visits on the refcode
    GET /api/cron/refcodes-mintCron (CRON_SECRET)Processes approved rewards queue (up to 20 per run)

    Share URL format: {APP_URL}?ref={CODE} (locale prefix optional; cookie is set on any landing page).

    ReferralAttributionEffect (public layout) fires the visit beacon when ring_ref_visible is present.

    Visit analytics

    Visit counts are stored on each refcode document — no separate analytics table.

    FieldMeaning
    visitsAll-time total
    visitDailyYYYY-MM-DD → count map (28-day retention via visit-analytics.ts)

    User dashboard (/refcodes) shows aggregate windows: total, today, last 7d, last 28d, plus per-link counts on each share card.

    Admin dashboard (/admin/refcodes) shows platform-wide visit aggregates alongside reward queue stats.

    Checkout buyer UX

    SurfaceBehavior
    Review stepReferralCheckoutBadge reads ring_ref_visible — shows referred code before place order
    Order createPOST /api/store/orders and /api/store/checkout return referralApplied + referralCode
    Credit / Ring payImmediate toast via referralAppliedToast i18n keys
    WayForPay redirectcheckout-referral-flash.ts stashes sessionStorage; processing page consumes flash and shows toast

    Cookies: httpOnly ring_ref (attribution) + client-readable ring_ref_visible (badge + beacon), both set in proxy.ts on first ?ref= touch.

    Attribution flow

    1. proxy.ts reads ?ref= and sets httpOnly ring_ref plus client-readable ring_ref_visible (30 days) if not already set — first-touch wins.
    2. Signup — Auth.js signIn event (isNewUser) calls persistSignupReferralAttribution so users.data.referredBy survives beyond the cookie TTL.
    3. POST /api/store/orders reads the cookie, resolves the code via RefcodeService.resolveCode, runs resolveOrderReferral guards, and passes attribution into StoreOrdersService.createOrder.
    4. On payment success:
      • WayForPay webhook (handleStoreWayForPayWebhook) → stock + settlements ledger + ReferralRewardService.onOrderPaid (rail: 'fiat').
      • Credit checkout (/api/store/payments/credit) → same pipeline with rail: 'crypto' (auto-approved).
    5. Membership upgrade — handleMembershipWayForPayWebhook calls ReferralRewardService.onMembershipPaid when referredBy is set (no prior store order required).

    Reward calculation

    Token rewards use the same resolveReferralCommissionPercent hierarchy as ERP settlement (features/store/lib/referral-commission.ts). For mixed carts, computeWeightedReferralPercentFromCart yields a subtotal-weighted effective percent stored as rewardPercent on each referral_rewards record.

    Env varDefaultMeaning
    REFERRAL_REWARD_PERCENT5Fallback platform % when DEFAULT_COMMISSION_STRUCTURE unset
    REFERRAL_COMMISSION_MAX_PERCENT50Upper bound for all resolved referral rates
    REFERRAL_UAH_PER_USD40UAH → USD for WayForPay orders
    REFERRAL_CHAIN_ID137Polygon mainnet

    Token amount uses the existing price oracle (priceOracleService.convertUsdToRing).

    Reward status machine

    StatusMeaning
    pending_approvalFiat order paid; awaiting admin
    approvedReady to mint (credit path skips pending)
    mintingTransaction submitted
    mintedOn-chain success; txHash stored
    failedMint reverted or RPC error; failureReason set
    rejectedAdmin rejected fiat reward

    Smart contract

    ReferralRewards (UUPS, OpenZeppelin 5) in contracts/contracts-src/ReferralRewards.sol.

    Role / settingHolder
    DEFAULT_ADMIN_ROLEDeployer — upgrade, pause, token/mode config
    OPERATOR_ROLEServer minter wallet — calls payReferral
    rewardMode0 = MINT (calls IMintableERC20.mint), 1 = TRANSFER

    orderRef = keccak256(utf8(orderReference)) — matches PaymentConductor orderReference strings.

    Deploy

    Save the proxy address as REFERRAL_REWARDS_ADDRESS.

    Grant minter permission (MINT mode)

    The token must allow the ReferralRewards proxy to mint:

    Token typeAction
    Ownable mint (e.g. test MockMintableToken)token.transferOwnership(referralRewardsProxy)
    OpenZeppelin AccessControltoken.grantRole(MINTER_ROLE, referralRewardsProxy)
    Custom Ring tokenGrant your project's mint permission to the proxy

    The operator wallet (REFERRAL_MINTER_PRIVATE_KEY) only needs OPERATOR_ROLE on ReferralRewards — set in initialize(admin, operator, token, mode) at deploy.

    Database

    Migration 005_refcodes_schema.sql creates:

    TableCollection keyPurpose
    refcodesrefcodesCode document; id = code string
    referral_rewardsreferral_rewardsReward ledger

    Dev database name: ring_platform on local Homebrew Postgres or Docker ring-postgres-dev (see infrastructure/postgres/init/README.md). Tables are also in data/schema.sql v4.0.1+; migration 005 remains for incremental apply. This is not ring_file_registry or clone DBs like ring_greenfood_live.

    Environment variables

    VariableRequiredDescription
    REFERRAL_MINTER_PRIVATE_KEYYes (mint)Server wallet with OPERATOR_ROLE — never expose to client
    REFERRAL_REWARDS_ADDRESSYesUUPS proxy address
    REFERRAL_REWARD_TOKEN_ADDRESSYesMintable ERC20 on target chain
    REFERRAL_REWARD_PERCENTNoDefault 5
    REFERRAL_CHAIN_IDNoDefault 137
    REFERRAL_UAH_PER_USDNoDefault 40

    Copy env.local.template to .env.local and fill the REFERRAL CODES MODULE block per clone. Add secrets to .reggie-propagate-exclude.json when propagating to white-label clones.

    Module layout

    Propagation to clones

    Use the installer for ring-connect-software, ring-ringdom-org, and other Ring clones:

    Copies new files, runs migration when DATABASE_URL is set, and prints shared-file patch anchors (proxy.ts, routes.ts, store hooks, i18n).

    Operations

    • On-chain deploy and env checklist: REFERRAL-ONCHAIN-OPS.md (repo root).
    • Cron mint: Authorization: Bearer $CRON_SECRET → GET /api/cron/refcodes-mint (batch up to 20 approved rewards).
    • After mint: REFERRAL_REWARD_MINTED notification — copy from locales/{en,uk,ru}/modules/refcodes.json → notifications.minted via lib/i18n/refcodes-labels.ts (user profile locale, {amount} + {token} interpolation).
    • Server labels: lib/i18n/refcodes-labels.ts (getReferralMintNotificationCopy, getUserPreferredLocaleForNotifications)
    • Do not import resolve-server-locale.ts from minter/cron paths loaded by tsx smokes — use DB-only locale helper above
    • Smoke: DB_BACKEND_MODE=k8s-postgres-fcm + NODE_OPTIONS=--conditions=react-server

    Related

    • Affiliate & referral enablement — dual-rail economics and audit
    • Ring ERP — Commissions
    • Multi-Vendor Store
    • Payment Integration
    • PaymentConductor
    • Web3 Wallet

    Referral Codes (Refcodes)

    The refcodes module turns Ring Platform into a referral growth engine: each connected wallet gets a random shareable code, first-time buyers can be attributed to a referrer, and referrers earn project tokens minted on-chain by a server-side operator wallet.

    Dual-rail context

    Token rewards (this module) run alongside vendor-funded ERP referral commission on the same attribution. See Affiliate & referral enablement for the full dual-rail map and audit keys.

    Architecture: Refcodes architecture · Dual-rail overview: Affiliate enablement · Migrations: Getting started: migrations · Clone install: scripts/install-refcodes-module.sh

    Overview

    LayerResponsibility
    Attribution?ref=CODE → ring_ref + ring_ref_visible cookies (30 days, first-touch)
    SignupNew users persist users.data.referredBy from ring_ref on first sign-in
    CheckoutReferralCheckoutBadge on review step; APIs return referralApplied + referralCode; toast or sessionStorage flash
    VisitsvisitDaily buckets + POST /api/refcodes/track; stats on /refcodes and /admin/refcodes
    LedgerPostgreSQL referral_rewards — off-chain state + mint tracking
    ERP railSame resolveReferralCommissionPercent hierarchy deducts vendor commission — see ERP commissions
    NotifyREFERRAL_REWARD_MINTED in-app notification after mint (locale from modules/refcodes.json)
    On-chainUUPS ReferralRewards contract — idempotent payReferral per order

    Business rules

    • One code per wallet — 8-character random code (Base58-style alphabet, no ambiguous chars).
    • First purchase only — buyer with any prior paid order is not attributed.
    • No self-referral — referrer user ID or any linked buyer wallet blocks attribution.
    • Idempotent rewards — one reward row per orderReference; contract rejects duplicate orderRef hashes.
    • Payment path — WayForPay (rail: 'fiat') creates pending_approval rewards until admin approves; internal credit (rail: 'crypto') auto-approves and mints immediately.
    • Referrer-only incentive — referee checkout discount is deferred; attribution still surfaces in checkout UX.

    User journey

    Routes and UI

    RouteAccessPurpose
    /refcodesAuthenticatedList codes per wallet, copy share URLs, reward history
    /admin/refcodesAdminApprove/reject pending fiat rewards, trigger mint
    GET /api/refcodesSessionJSON: codes + stats for current user
    POST /api/refcodes/mintAdminBatch-mint approved rewards
    POST /api/refcodes/trackPublicVisit beacon — increments visits on the refcode
    GET /api/cron/refcodes-mintCron (CRON_SECRET)Processes approved rewards queue (up to 20 per run)

    Share URL format: {APP_URL}?ref={CODE} (locale prefix optional; cookie is set on any landing page).

    ReferralAttributionEffect (public layout) fires the visit beacon when ring_ref_visible is present.

    Visit analytics

    Visit counts are stored on each refcode document — no separate analytics table.

    FieldMeaning
    visitsAll-time total
    visitDailyYYYY-MM-DD → count map (28-day retention via visit-analytics.ts)

    User dashboard (/refcodes) shows aggregate windows: total, today, last 7d, last 28d, plus per-link counts on each share card.

    Admin dashboard (/admin/refcodes) shows platform-wide visit aggregates alongside reward queue stats.

    Checkout buyer UX

    SurfaceBehavior
    Review stepReferralCheckoutBadge reads ring_ref_visible — shows referred code before place order
    Order createPOST /api/store/orders and /api/store/checkout return referralApplied + referralCode
    Credit / Ring payImmediate toast via referralAppliedToast i18n keys
    WayForPay redirectcheckout-referral-flash.ts stashes sessionStorage; processing page consumes flash and shows toast

    Cookies: httpOnly ring_ref (attribution) + client-readable ring_ref_visible (badge + beacon), both set in proxy.ts on first ?ref= touch.

    Attribution flow

    1. proxy.ts reads ?ref= and sets httpOnly ring_ref plus client-readable ring_ref_visible (30 days) if not already set — first-touch wins.
    2. Signup — Auth.js signIn event (isNewUser) calls persistSignupReferralAttribution so users.data.referredBy survives beyond the cookie TTL.
    3. POST /api/store/orders reads the cookie, resolves the code via RefcodeService.resolveCode, runs resolveOrderReferral guards, and passes attribution into StoreOrdersService.createOrder.
    4. On payment success:
      • WayForPay webhook (handleStoreWayForPayWebhook) → stock + settlements ledger + ReferralRewardService.onOrderPaid (rail: 'fiat').
      • Credit checkout (/api/store/payments/credit) → same pipeline with rail: 'crypto' (auto-approved).
    5. Membership upgrade — handleMembershipWayForPayWebhook calls ReferralRewardService.onMembershipPaid when referredBy is set (no prior store order required).

    Reward calculation

    Token rewards use the same resolveReferralCommissionPercent hierarchy as ERP settlement (features/store/lib/referral-commission.ts). For mixed carts, computeWeightedReferralPercentFromCart yields a subtotal-weighted effective percent stored as rewardPercent on each referral_rewards record.

    Env varDefaultMeaning
    REFERRAL_REWARD_PERCENT5Fallback platform % when DEFAULT_COMMISSION_STRUCTURE unset
    REFERRAL_COMMISSION_MAX_PERCENT50Upper bound for all resolved referral rates
    REFERRAL_UAH_PER_USD40UAH → USD for WayForPay orders
    REFERRAL_CHAIN_ID137Polygon mainnet

    Token amount uses the existing price oracle (priceOracleService.convertUsdToRing).

    Reward status machine

    StatusMeaning
    pending_approvalFiat order paid; awaiting admin
    approvedReady to mint (credit path skips pending)
    mintingTransaction submitted
    mintedOn-chain success; txHash stored
    failedMint reverted or RPC error; failureReason set
    rejectedAdmin rejected fiat reward

    Smart contract

    ReferralRewards (UUPS, OpenZeppelin 5) in contracts/contracts-src/ReferralRewards.sol.

    Role / settingHolder
    DEFAULT_ADMIN_ROLEDeployer — upgrade, pause, token/mode config
    OPERATOR_ROLEServer minter wallet — calls payReferral
    rewardMode0 = MINT (calls IMintableERC20.mint), 1 = TRANSFER

    orderRef = keccak256(utf8(orderReference)) — matches PaymentConductor orderReference strings.

    Deploy

    Save the proxy address as REFERRAL_REWARDS_ADDRESS.

    Grant minter permission (MINT mode)

    The token must allow the ReferralRewards proxy to mint:

    Token typeAction
    Ownable mint (e.g. test MockMintableToken)token.transferOwnership(referralRewardsProxy)
    OpenZeppelin AccessControltoken.grantRole(MINTER_ROLE, referralRewardsProxy)
    Custom Ring tokenGrant your project's mint permission to the proxy

    The operator wallet (REFERRAL_MINTER_PRIVATE_KEY) only needs OPERATOR_ROLE on ReferralRewards — set in initialize(admin, operator, token, mode) at deploy.

    Database

    Migration 005_refcodes_schema.sql creates:

    TableCollection keyPurpose
    refcodesrefcodesCode document; id = code string
    referral_rewardsreferral_rewardsReward ledger

    Dev database name: ring_platform on local Homebrew Postgres or Docker ring-postgres-dev (see infrastructure/postgres/init/README.md). Tables are also in data/schema.sql v4.0.1+; migration 005 remains for incremental apply. This is not ring_file_registry or clone DBs like ring_greenfood_live.

    Environment variables

    VariableRequiredDescription
    REFERRAL_MINTER_PRIVATE_KEYYes (mint)Server wallet with OPERATOR_ROLE — never expose to client
    REFERRAL_REWARDS_ADDRESSYesUUPS proxy address
    REFERRAL_REWARD_TOKEN_ADDRESSYesMintable ERC20 on target chain
    REFERRAL_REWARD_PERCENTNoDefault 5
    REFERRAL_CHAIN_IDNoDefault 137
    REFERRAL_UAH_PER_USDNoDefault 40

    Copy env.local.template to .env.local and fill the REFERRAL CODES MODULE block per clone. Add secrets to .reggie-propagate-exclude.json when propagating to white-label clones.

    Module layout

    Propagation to clones

    Use the installer for ring-connect-software, ring-ringdom-org, and other Ring clones:

    Copies new files, runs migration when DATABASE_URL is set, and prints shared-file patch anchors (proxy.ts, routes.ts, store hooks, i18n).

    Operations

    • On-chain deploy and env checklist: REFERRAL-ONCHAIN-OPS.md (repo root).
    • Cron mint: Authorization: Bearer $CRON_SECRET → GET /api/cron/refcodes-mint (batch up to 20 approved rewards).
    • After mint: REFERRAL_REWARD_MINTED notification — copy from locales/{en,uk,ru}/modules/refcodes.json → notifications.minted via lib/i18n/refcodes-labels.ts (user profile locale, {amount} + {token} interpolation).
    • Server labels: lib/i18n/refcodes-labels.ts (getReferralMintNotificationCopy, getUserPreferredLocaleForNotifications)
    • Do not import resolve-server-locale.ts from minter/cron paths loaded by tsx smokes — use DB-only locale helper above
    • Smoke: DB_BACKEND_MODE=k8s-postgres-fcm + NODE_OPTIONS=--conditions=react-server

    Related

    • Affiliate & referral enablement — dual-rail economics and audit
    • Ring ERP — Commissions
    • Multi-Vendor Store
    • Payment Integration
    • PaymentConductor
    • Web3 Wallet

    Referral Codes (Refcodes)

    The refcodes module turns Ring Platform into a referral growth engine: each connected wallet gets a random shareable code, first-time buyers can be attributed to a referrer, and referrers earn project tokens minted on-chain by a server-side operator wallet.

    Dual-rail context

    Token rewards (this module) run alongside vendor-funded ERP referral commission on the same attribution. See Affiliate & referral enablement for the full dual-rail map and audit keys.

    Architecture: Refcodes architecture · Dual-rail overview: Affiliate enablement · Migrations: Getting started: migrations · Clone install: scripts/install-refcodes-module.sh

    Overview

    LayerResponsibility
    Attribution?ref=CODE → ring_ref + ring_ref_visible cookies (30 days, first-touch)
    SignupNew users persist users.data.referredBy from ring_ref on first sign-in
    CheckoutReferralCheckoutBadge on review step; APIs return referralApplied + referralCode; toast or sessionStorage flash
    VisitsvisitDaily buckets + POST /api/refcodes/track; stats on /refcodes and /admin/refcodes
    LedgerPostgreSQL referral_rewards — off-chain state + mint tracking
    ERP railSame resolveReferralCommissionPercent hierarchy deducts vendor commission — see ERP commissions
    NotifyREFERRAL_REWARD_MINTED in-app notification after mint (locale from modules/refcodes.json)
    On-chainUUPS ReferralRewards contract — idempotent payReferral per order

    Business rules

    • One code per wallet — 8-character random code (Base58-style alphabet, no ambiguous chars).
    • First purchase only — buyer with any prior paid order is not attributed.
    • No self-referral — referrer user ID or any linked buyer wallet blocks attribution.
    • Idempotent rewards — one reward row per orderReference; contract rejects duplicate orderRef hashes.
    • Payment path — WayForPay (rail: 'fiat') creates pending_approval rewards until admin approves; internal credit (rail: 'crypto') auto-approves and mints immediately.
    • Referrer-only incentive — referee checkout discount is deferred; attribution still surfaces in checkout UX.

    User journey

    Routes and UI

    RouteAccessPurpose
    /refcodesAuthenticatedList codes per wallet, copy share URLs, reward history
    /admin/refcodesAdminApprove/reject pending fiat rewards, trigger mint
    GET /api/refcodesSessionJSON: codes + stats for current user
    POST /api/refcodes/mintAdminBatch-mint approved rewards
    POST /api/refcodes/trackPublicVisit beacon — increments visits on the refcode
    GET /api/cron/refcodes-mintCron (CRON_SECRET)Processes approved rewards queue (up to 20 per run)

    Share URL format: {APP_URL}?ref={CODE} (locale prefix optional; cookie is set on any landing page).

    ReferralAttributionEffect (public layout) fires the visit beacon when ring_ref_visible is present.

    Visit analytics

    Visit counts are stored on each refcode document — no separate analytics table.

    FieldMeaning
    visitsAll-time total
    visitDailyYYYY-MM-DD → count map (28-day retention via visit-analytics.ts)

    User dashboard (/refcodes) shows aggregate windows: total, today, last 7d, last 28d, plus per-link counts on each share card.

    Admin dashboard (/admin/refcodes) shows platform-wide visit aggregates alongside reward queue stats.

    Checkout buyer UX

    SurfaceBehavior
    Review stepReferralCheckoutBadge reads ring_ref_visible — shows referred code before place order
    Order createPOST /api/store/orders and /api/store/checkout return referralApplied + referralCode
    Credit / Ring payImmediate toast via referralAppliedToast i18n keys
    WayForPay redirectcheckout-referral-flash.ts stashes sessionStorage; processing page consumes flash and shows toast

    Cookies: httpOnly ring_ref (attribution) + client-readable ring_ref_visible (badge + beacon), both set in proxy.ts on first ?ref= touch.

    Attribution flow

    1. proxy.ts reads ?ref= and sets httpOnly ring_ref plus client-readable ring_ref_visible (30 days) if not already set — first-touch wins.
    2. Signup — Auth.js signIn event (isNewUser) calls persistSignupReferralAttribution so users.data.referredBy survives beyond the cookie TTL.
    3. POST /api/store/orders reads the cookie, resolves the code via RefcodeService.resolveCode, runs resolveOrderReferral guards, and passes attribution into StoreOrdersService.createOrder.
    4. On payment success:
      • WayForPay webhook (handleStoreWayForPayWebhook) → stock + settlements ledger + ReferralRewardService.onOrderPaid (rail: 'fiat').
      • Credit checkout (/api/store/payments/credit) → same pipeline with rail: 'crypto' (auto-approved).
    5. Membership upgrade — handleMembershipWayForPayWebhook calls ReferralRewardService.onMembershipPaid when referredBy is set (no prior store order required).

    Reward calculation

    Token rewards use the same resolveReferralCommissionPercent hierarchy as ERP settlement (features/store/lib/referral-commission.ts). For mixed carts, computeWeightedReferralPercentFromCart yields a subtotal-weighted effective percent stored as rewardPercent on each referral_rewards record.

    Env varDefaultMeaning
    REFERRAL_REWARD_PERCENT5Fallback platform % when DEFAULT_COMMISSION_STRUCTURE unset
    REFERRAL_COMMISSION_MAX_PERCENT50Upper bound for all resolved referral rates
    REFERRAL_UAH_PER_USD40UAH → USD for WayForPay orders
    REFERRAL_CHAIN_ID137Polygon mainnet

    Token amount uses the existing price oracle (priceOracleService.convertUsdToRing).

    Reward status machine

    StatusMeaning
    pending_approvalFiat order paid; awaiting admin
    approvedReady to mint (credit path skips pending)
    mintingTransaction submitted
    mintedOn-chain success; txHash stored
    failedMint reverted or RPC error; failureReason set
    rejectedAdmin rejected fiat reward

    Smart contract

    ReferralRewards (UUPS, OpenZeppelin 5) in contracts/contracts-src/ReferralRewards.sol.

    Role / settingHolder
    DEFAULT_ADMIN_ROLEDeployer — upgrade, pause, token/mode config
    OPERATOR_ROLEServer minter wallet — calls payReferral
    rewardMode0 = MINT (calls IMintableERC20.mint), 1 = TRANSFER

    orderRef = keccak256(utf8(orderReference)) — matches PaymentConductor orderReference strings.

    Deploy

    Save the proxy address as REFERRAL_REWARDS_ADDRESS.

    Grant minter permission (MINT mode)

    The token must allow the ReferralRewards proxy to mint:

    Token typeAction
    Ownable mint (e.g. test MockMintableToken)token.transferOwnership(referralRewardsProxy)
    OpenZeppelin AccessControltoken.grantRole(MINTER_ROLE, referralRewardsProxy)
    Custom Ring tokenGrant your project's mint permission to the proxy

    The operator wallet (REFERRAL_MINTER_PRIVATE_KEY) only needs OPERATOR_ROLE on ReferralRewards — set in initialize(admin, operator, token, mode) at deploy.

    Database

    Migration 005_refcodes_schema.sql creates:

    TableCollection keyPurpose
    refcodesrefcodesCode document; id = code string
    referral_rewardsreferral_rewardsReward ledger

    Dev database name: ring_platform on local Homebrew Postgres or Docker ring-postgres-dev (see infrastructure/postgres/init/README.md). Tables are also in data/schema.sql v4.0.1+; migration 005 remains for incremental apply. This is not ring_file_registry or clone DBs like ring_greenfood_live.

    Environment variables

    VariableRequiredDescription
    REFERRAL_MINTER_PRIVATE_KEYYes (mint)Server wallet with OPERATOR_ROLE — never expose to client
    REFERRAL_REWARDS_ADDRESSYesUUPS proxy address
    REFERRAL_REWARD_TOKEN_ADDRESSYesMintable ERC20 on target chain
    REFERRAL_REWARD_PERCENTNoDefault 5
    REFERRAL_CHAIN_IDNoDefault 137
    REFERRAL_UAH_PER_USDNoDefault 40

    Copy env.local.template to .env.local and fill the REFERRAL CODES MODULE block per clone. Add secrets to .reggie-propagate-exclude.json when propagating to white-label clones.

    Module layout

    Propagation to clones

    Use the installer for ring-connect-software, ring-ringdom-org, and other Ring clones:

    Copies new files, runs migration when DATABASE_URL is set, and prints shared-file patch anchors (proxy.ts, routes.ts, store hooks, i18n).

    Operations

    • On-chain deploy and env checklist: REFERRAL-ONCHAIN-OPS.md (repo root).
    • Cron mint: Authorization: Bearer $CRON_SECRET → GET /api/cron/refcodes-mint (batch up to 20 approved rewards).
    • After mint: REFERRAL_REWARD_MINTED notification — copy from locales/{en,uk,ru}/modules/refcodes.json → notifications.minted via lib/i18n/refcodes-labels.ts (user profile locale, {amount} + {token} interpolation).
    • Server labels: lib/i18n/refcodes-labels.ts (getReferralMintNotificationCopy, getUserPreferredLocaleForNotifications)
    • Do not import resolve-server-locale.ts from minter/cron paths loaded by tsx smokes — use DB-only locale helper above
    • Smoke: DB_BACKEND_MODE=k8s-postgres-fcm + NODE_OPTIONS=--conditions=react-server

    Related

    • Affiliate & referral enablement — dual-rail economics and audit
    • Ring ERP — Commissions
    • Multi-Vendor Store
    • Payment Integration
    • PaymentConductor
    • Web3 Wallet
    REFERRAL_REWARD_MODENo0 MINT, 1 TRANSFER
    POLYGON_RPC_URLYes (mint)Server RPC for viem
    REFERRAL_MINTER_ADDRESSNoDeploy script operator fallback
    DEPLOYER_PRIVATE_KEYDeploy onlyHardhat deployer
    CRON_SECRETProd cronBearer token for GET /api/cron/refcodes-mint
    typescript
    
    // POST /api/refcodes/track — body: { code: string }
    // features/refcodes/services/attribution-service.ts → trackRefcodeVisit
    typescript
    
    // Simplified — see features/refcodes/services/referral-reward-service.ts
    const rewardPercent = computeWeightedReferralPercentFromCart(order.items, merchantConfigByEntityId)
    const usdValue = orderTotalInUsd * (rewardPercent / 100)
    const { token_amount } = await priceOracleService.convertUsdToRing(usdValue)
    solidity
    
    function payReferral(address refWallet, uint256 amount, bytes32 orderRef)
        external onlyRole(OPERATOR_ROLE) whenNotPaused
    bash
    
    cd contracts
    rm -rf node_modules package-lock.json && npm install
    REFERRAL_REWARD_TOKEN_ADDRESS=0xYourToken \
    REFERRAL_MINTER_ADDRESS=0xOperator \
    REFERRAL_REWARD_MODE=0 \
    DEPLOYER_PRIVATE_KEY=0x... \
    npx hardhat run scripts/deploy-referral-rewards.js --network polygon
    bash
    
    # Dev
    export DATABASE_URL=postgresql://ring_user:ring_password_2024@localhost:5432/ring_platform
    ./scripts/apply-refcodes-migrations-dev.sh
    
    # Prod (k8s)
    K8S_NAMESPACE=ring-platform-org POSTGRES_DB=ring_platform POSTGRES_USER=ring_user \
      ./scripts/apply-refcodes-migrations-prod.sh
    bash
    
    ./scripts/install-refcodes-module.sh /path/to/clone
    text
    
    features/refcodes/
      constants.ts               # Cookie names, collection names, env defaults
      types.ts
      abi/referral-rewards.json
      lib/
        visit-analytics.ts         # visitDaily buckets + window stats
        checkout-referral-flash.ts # WayForPay redirect toast
      services/
        refcode-service.ts         # Generate/list/resolve codes + visitStats enrich
        attribution-service.ts     # Cookie → order guards + trackRefcodeVisit
        referral-reward-service.ts
        reward-minter.ts           # viem payReferral + localized notification
    lib/i18n/refcodes-labels.ts    # Server mint notification copy (EN/UK/RU)
    lib/web3/server-wallet.ts      # Operator wallet client
    components/refcodes/
      referral-attribution-effect.tsx
      referral-checkout-badge.tsx
    app/(authenticated)/[locale]/refcodes/
    app/(admin)/[locale]/admin/refcodes/
    app/api/refcodes/
    REFERRAL_REWARD_MODENo0 MINT, 1 TRANSFER
    POLYGON_RPC_URLYes (mint)Server RPC for viem
    REFERRAL_MINTER_ADDRESSNoDeploy script operator fallback
    DEPLOYER_PRIVATE_KEYDeploy onlyHardhat deployer
    CRON_SECRETProd cronBearer token for GET /api/cron/refcodes-mint
    typescript
    
    // POST /api/refcodes/track — body: { code: string }
    // features/refcodes/services/attribution-service.ts → trackRefcodeVisit
    typescript
    
    // Simplified — see features/refcodes/services/referral-reward-service.ts
    const rewardPercent = computeWeightedReferralPercentFromCart(order.items, merchantConfigByEntityId)
    const usdValue = orderTotalInUsd * (rewardPercent / 100)
    const { token_amount } = await priceOracleService.convertUsdToRing(usdValue)
    solidity
    
    function payReferral(address refWallet, uint256 amount, bytes32 orderRef)
        external onlyRole(OPERATOR_ROLE) whenNotPaused
    bash
    
    cd contracts
    rm -rf node_modules package-lock.json && npm install
    REFERRAL_REWARD_TOKEN_ADDRESS=0xYourToken \
    REFERRAL_MINTER_ADDRESS=0xOperator \
    REFERRAL_REWARD_MODE=0 \
    DEPLOYER_PRIVATE_KEY=0x... \
    npx hardhat run scripts/deploy-referral-rewards.js --network polygon
    bash
    
    # Dev
    export DATABASE_URL=postgresql://ring_user:ring_password_2024@localhost:5432/ring_platform
    ./scripts/apply-refcodes-migrations-dev.sh
    
    # Prod (k8s)
    K8S_NAMESPACE=ring-platform-org POSTGRES_DB=ring_platform POSTGRES_USER=ring_user \
      ./scripts/apply-refcodes-migrations-prod.sh
    bash
    
    ./scripts/install-refcodes-module.sh /path/to/clone
    text
    
    features/refcodes/
      constants.ts               # Cookie names, collection names, env defaults
      types.ts
      abi/referral-rewards.json
      lib/
        visit-analytics.ts         # visitDaily buckets + window stats
        checkout-referral-flash.ts # WayForPay redirect toast
      services/
        refcode-service.ts         # Generate/list/resolve codes + visitStats enrich
        attribution-service.ts     # Cookie → order guards + trackRefcodeVisit
        referral-reward-service.ts
        reward-minter.ts           # viem payReferral + localized notification
    lib/i18n/refcodes-labels.ts    # Server mint notification copy (EN/UK/RU)
    lib/web3/server-wallet.ts      # Operator wallet client
    components/refcodes/
      referral-attribution-effect.tsx
      referral-checkout-badge.tsx
    app/(authenticated)/[locale]/refcodes/
    app/(admin)/[locale]/admin/refcodes/
    app/api/refcodes/
    REFERRAL_REWARD_MODENo0 MINT, 1 TRANSFER
    POLYGON_RPC_URLYes (mint)Server RPC for viem
    REFERRAL_MINTER_ADDRESSNoDeploy script operator fallback
    DEPLOYER_PRIVATE_KEYDeploy onlyHardhat deployer
    CRON_SECRETProd cronBearer token for GET /api/cron/refcodes-mint
    typescript
    
    // POST /api/refcodes/track — body: { code: string }
    // features/refcodes/services/attribution-service.ts → trackRefcodeVisit
    typescript
    
    // Simplified — see features/refcodes/services/referral-reward-service.ts
    const rewardPercent = computeWeightedReferralPercentFromCart(order.items, merchantConfigByEntityId)
    const usdValue = orderTotalInUsd * (rewardPercent / 100)
    const { token_amount } = await priceOracleService.convertUsdToRing(usdValue)
    solidity
    
    function payReferral(address refWallet, uint256 amount, bytes32 orderRef)
        external onlyRole(OPERATOR_ROLE) whenNotPaused
    bash
    
    cd contracts
    rm -rf node_modules package-lock.json && npm install
    REFERRAL_REWARD_TOKEN_ADDRESS=0xYourToken \
    REFERRAL_MINTER_ADDRESS=0xOperator \
    REFERRAL_REWARD_MODE=0 \
    DEPLOYER_PRIVATE_KEY=0x... \
    npx hardhat run scripts/deploy-referral-rewards.js --network polygon
    bash
    
    # Dev
    export DATABASE_URL=postgresql://ring_user:ring_password_2024@localhost:5432/ring_platform
    ./scripts/apply-refcodes-migrations-dev.sh
    
    # Prod (k8s)
    K8S_NAMESPACE=ring-platform-org POSTGRES_DB=ring_platform POSTGRES_USER=ring_user \
      ./scripts/apply-refcodes-migrations-prod.sh
    bash
    
    ./scripts/install-refcodes-module.sh /path/to/clone
    text
    
    features/refcodes/
      constants.ts               # Cookie names, collection names, env defaults
      types.ts
      abi/referral-rewards.json
      lib/
        visit-analytics.ts         # visitDaily buckets + window stats
        checkout-referral-flash.ts # WayForPay redirect toast
      services/
        refcode-service.ts         # Generate/list/resolve codes + visitStats enrich
        attribution-service.ts     # Cookie → order guards + trackRefcodeVisit
        referral-reward-service.ts
        reward-minter.ts           # viem payReferral + localized notification
    lib/i18n/refcodes-labels.ts    # Server mint notification copy (EN/UK/RU)
    lib/web3/server-wallet.ts      # Operator wallet client
    components/refcodes/
      referral-attribution-effect.tsx
      referral-checkout-badge.tsx
    app/(authenticated)/[locale]/refcodes/
    app/(admin)/[locale]/admin/refcodes/
    app/api/refcodes/