1. Overview
Code:
app/services/rolling_credits_service.py, app/services/subscription_service.py, app/services/blockchain_service.py, contracts/contracts/CreditToken.sol
2. Configuration
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
- 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 samedeploy.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
Deploy (examples)
Local Hardhat (persistent node):.env:
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:
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 aUserSubscriptionand flushes.- For
tierin("pro","premium"), callsRollingCreditsService.generate_subscription_credits(user_id, subscription.id, tier, period_start, period_end). generate_subscription_creditsupdates or createsCreditBalance, addsCreditTransactionrows, then_register_credits_on_blockchainifCREDIT_TOKEN_CONTRACTis set and the user haswallet_address.
Renew (renew_subscription)
SubscriptionService.renew_subscription(subscription_id)loads the subscription, computesperiod_start=expires_atandperiod_end=period_start+30or+365.- For
pro/premium, callsgenerate_subscription_creditsfor that window. - Sets
subscription.expires_at = period_endand commits.
Blockchain registration (_register_credits_on_blockchain)
- No
CREDIT_TOKEN_CONTRACTor 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_idare stored onCreditBalance. - Later runs (has
blockchain_token_id):update_credit_token(token_id, credit_type, amount, is_spend=False)for each type withamount > 0.
7. Troubleshooting
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.10 per call (seePLAID_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:
PlaidUsageTrackingrecords each call (endpoint, item_id, optional cost_usd) for billing and analytics. - Config:
PLAID_COST_USD(default0.05) is used as the USD amount in 402 responses when credits are insufficient.
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)