Documentation Index
Fetch the complete documentation index at: https://tonic-ai.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Rolling credits are subscription-based credits (signing, document_review, verification, trading, stock prediction, etc.) generated on activate and renew. They can optionally be registered on-chain via the CreditToken ERC-721 contract.
1. Overview
| Component | Role |
|---|
| RollingCreditsService | generate_subscription_credits() — prorated allocation, DB (CreditBalance, CreditTransaction), optional blockchain registration |
| SubscriptionService | create_subscription (activate) and renew_subscription → call RollingCreditsService.generate_subscription_credits for pro / premium |
| CreditToken.sol | ERC-721 NFT: one token per user, CreditBalanceStruct with 14 credit types; mintCredits, updateCredits (4 decimals: 10000 = 1.0) |
| BlockchainService | Loads CreditToken ABI, mint_credit_token(), update_credit_token() |
Code: app/services/rolling_credits_service.py, app/services/subscription_service.py, app/services/blockchain_service.py, contracts/contracts/CreditToken.sol
2. Configuration
| Variable | Description | Default |
|---|
CREDIT_TOKEN_CONTRACT | CreditToken (ERC-721) contract address. If empty, credits are still generated in DB but not registered on-chain. | "" |
X402_NETWORK_RPC_URL | RPC for the chain where CreditToken is deployed (Base, Base Sepolia, or local Hardhat). Must match deployment. | https://mainnet.base.org |
BLOCKCHAIN_DEPLOYER_PRIVATE_KEY | Private key of the account that deploys and is owner of CreditToken. Required for mintCredits / updateCredits (both are onlyOwner). | — |
Wallet for users: On-chain registration runs only when the user has wallet_address set. If missing, credits are stored in DB only (blockchain_registered=false).
3. Tier Allocation and Proration
| Tier | Example (full month) |
|---|
| pro | signing 20, document_review 20, verification 20, trading 30, loaning 20, borrowing 20, compliance_check 20, securitization 15, risk_analysis 20, quantitative_analysis 15, stock_prediction_* 5 each, universal 100 |
| premium | ~3× pro (e.g. universal 300, stock_prediction_* 15 each) |
- Proration:
min(1.0, billing_days / 30). For a 15-day period, allocation is halved.
- Activate: Billing window =
[started_at, expires_at] or [started_at, started_at+30] for lifetime.
- Renew: Billing window =
[expires_at, expires_at+30] (monthly) or [expires_at, expires_at+365] (yearly).
4. Deploying CreditToken
CreditToken is deployed by the same deploy.js as the securitization contracts. Use the same network (local, Base Sepolia, or Base mainnet) and the same PRIVATE_KEY / BLOCKCHAIN_DEPLOYER_PRIVATE_KEY.
Compile
cd contracts
npm install
npx hardhat compile
Deploy (examples)
Local Hardhat (persistent node):
# Terminal 1
npm run node
# Terminal 2
npm run deploy:localhost
# or, if your npm script runs deploy.js: npx hardhat run scripts/deploy.js --network localhost
Base Sepolia / Base mainnet:
npx hardhat run scripts/deploy.js --network base-sepolia
# or
npx hardhat run scripts/deploy.js --network base
The script prints:
CREDIT_TOKEN_CONTRACT=0x...
Add to the project root .env:
CREDIT_TOKEN_CONTRACT=0x...
Ensure X402_NETWORK_RPC_URL matches the deployment network (e.g. http://127.0.0.1:8545, https://sepolia.base.org, https://mainnet.base.org).
5. Artifacts and ABI
BlockchainService loads the CreditToken ABI from:
contracts/artifacts/contracts/CreditToken.sol/CreditToken.json
This file is created by npx hardhat compile. If it is missing, mint_credit_token / update_credit_token are skipped ("CreditToken ABI not loaded"). Credits are still written to the database.
6. Flows
Activate (create_subscription)
SubscriptionService.create_subscription(...) creates a UserSubscription and flushes.
- For
tier in ("pro","premium"), calls RollingCreditsService.generate_subscription_credits(user_id, subscription.id, tier, period_start, period_end).
generate_subscription_credits updates or creates CreditBalance, adds CreditTransaction rows, then _register_credits_on_blockchain if CREDIT_TOKEN_CONTRACT is set and the user has wallet_address.
Renew (renew_subscription)
SubscriptionService.renew_subscription(subscription_id) loads the subscription, computes period_start=expires_at and period_end=period_start+30 or +365.
- For
pro / premium, calls generate_subscription_credits for that window.
- Sets
subscription.expires_at = period_end and commits.
Blockchain registration (_register_credits_on_blockchain)
- No
CREDIT_TOKEN_CONTRACT or no RPC/deployer: registered: false, reason: "blockchain_not_configured" or "blockchain_not_connected".
- User has no
wallet_address: registered: false, reason: "no_wallet".
- First run (no
blockchain_token_id): mint_credit_token(user_address, struct_from(generated_credits)); on success, blockchain_token_id, blockchain_tx_hash, blockchain_chain_id are stored on CreditBalance.
- Later runs (has
blockchain_token_id): update_credit_token(token_id, credit_type, amount, is_spend=False) for each type with amount > 0.
7. Troubleshooting
| Symptom | Checks |
|---|
| No credits in DB on activate | Tier must be pro or premium; create_subscription must complete without raising before commit. Check logs for "Rolling credits generated on activate" or "Rolling credits on activate failed". |
Credits in DB but blockchain_registered=false | CREDIT_TOKEN_CONTRACT set? X402_NETWORK_RPC_URL correct and RPC reachable? User wallet_address present? BlockchainService deployer (owner) and credit_token ABI loaded? Check blockchain object in generate_subscription_credits result (reason). |
| ”CreditToken ABI not loaded” | Run npx hardhat compile in contracts/ and ensure contracts/artifacts/contracts/CreditToken.sol/CreditToken.json exists. |
| ”blockchain_not_configured” | Set CREDIT_TOKEN_CONTRACT in .env. |
| ”no_wallet” | User needs wallet_address (or a resolved wallet from your auth/wallet flow). |
| Mint/update revert | Deployer must be owner of CreditToken. For updateCredits, on-chain balance for that type must be ≥ amount when isSpend=true (only isSpend=false is used for subscription credits). |
8. Plaid and credits (deduct per call)
Each Plaid API call (accounts, balances, transactions, etc.) deducts 1 trading credit per request. The equivalent cost is configurable and typically 0.02–0.10 per call (see PLAID_COST_USD in app/core/config.py, default 0.05). This cost is used when returning 402 Payment Required so the client can show the correct pay-as-you-go amount.
- Deduct: 1 trading credit per Plaid call (e.g.
/api/banking/accounts, /api/banking/balances, /api/banking/transactions).
- Tracking:
PlaidUsageTracking records each call (endpoint, item_id, optional cost_usd) for billing and analytics.
- Config:
PLAID_COST_USD (default 0.05) is used as the USD amount in 402 responses when credits are insufficient.
Subscription tiers (e.g. TIER_10, TIER_15) include a number of Plaid refreshes via universal/trading credits; beyond that, users consume credits or pay per call.
9. References
- Contracts:
contracts/README.md, contracts/scripts/deploy.js
- Config:
app/core/config.py (CREDIT_TOKEN_CONTRACT), dev/environment.md
- Models:
app/db/models.py (CreditBalance, CreditTransaction, CreditType)