Core Protocol Flow
1. Wallet Initialization
Users obtain a forwarding address through Grindery's account abstraction layer. This is a deterministic smart contract wallet address derived from:
- Authentication method (email/OAuth/SMS via Web3Auth or similar)
- Salt for uniqueness
- Factory contract address
The forwarding address gets associated with a client wallet address through an on-chain transaction:
plain textBillingLedger.associateWallet(forwardingAddress, clientAddress)
This creates a one-way binding - the forwarding address can only deposit to this specific client's ledger balance.
2. Funding Flow
When USDC (or other tokens) arrive at the forwarding address:
- Detection: Cache service monitors
Transfer
events via RPC subscription
- Auto-deposit: Cache service calls
depositFromForwarding()
on behalf of user
- Conversion: Non-USDC tokens get swapped to USDC at current rates via DEX aggregator
- Credit: User's balance in Billing Ledger increases by deposited amount
The Billing Ledger maintains internal accounting:
soliditymapping(address => uint256) public balances; // client => available balance mapping(address => uint256) public locked; // client => locked for pending charges
3. Authorization Commitment
User creates authorization by signing an EIP-712 structured message:
soliditystruct Authorization { address provider; uint256 maxPerCharge; uint256 totalLimit; uint256 rateLimit; // max charges per hour uint256 expiry; }
This gets submitted on-chain:
solidityBillingLedger.commitAuthorization( Authorization auth, bytes signature )
The contract stores this as:
soliditymapping(bytes32 => AuthState) authorizations; // authId => state mapping(address => bytes32[]) userAuths; // user => authIds
4. Charge Request Structure
Providers create charges by packing data into bytes:
soliditystruct ChargeRequest { address user; uint256 amount; uint256 timestamp; bytes32 authId; uint256 nonce; bytes metadata; // optional attribution data }
Provider signs this with their private key, creating a charge tuple:
plain text(chargeRequest, signature)
5. Off-Chain Authorization Check
Before providing service, provider queries the cache service:
plain textGET /authorize?user=0x...&amount=100&taskId=...
Cache service validates against:
- Cached authorization rules
- Current balance minus pending charges
- Rate limits (Redis sliding window)
- Previous charges for idempotency
Response time: <10ms
6. Batch Settlement Process
Providers accumulate signed charges in local buffer. Settlement triggers on:
- 30 second timer
- 1000 charges accumulated
- Manual trigger
Batch submission:
solidityBillingLedger.settleBatch( ChargeRequest[] requests, bytes[] signatures )
Smart contract validates each charge:
solidityfor (uint i = 0; i < requests.length; i++) { // 1. Verify provider signature require(ecrecover(hash, sig) == auth.provider); // 2. Verify authorization exists and is valid require(auth.expiry > block.timestamp); require(auth.totalUsed + amount <= auth.totalLimit); // 3. Verify user has balance require(balances[user] >= amount); // 4. Verify timestamp freshness require(block.timestamp - timestamp < 900); // 15 min // 5. Verify provider nonce require(nonces[provider] == nonce); // 6. Update balances balances[user] -= amount; balances[provider] += amount; nonces[provider] = nonce // 7. Emit settlement event emit ChargeSettled(user, provider, amount, nonce); }
7. Dispute Mechanism
Within dispute window (2 hours), users can reverse charges:
solidityBillingLedger.dispute( ChargeRequest request, bytes providerSignature )
Contract verifies:
- Charge was correctly signed by provider
- Still within dispute window
- Not previously disputed
- User initiated dispute
Then reverses:
soliditybalances[user] += request.amount; balances[provider] -= request.amount; disputes[chargeHash] = true; emit ChargeDisputed(user, provider, amount, nonce);
8. Multi-Hop Attribution
For agent-to-agent billing, metadata contains attribution chain:
json{ "principal": "0xUserWallet", "chain": [ {"agent": "0xAssistant", "task": "orchestrate"}, {"agent": "0xSubAgent", "task": "execute"} ], "rootTaskId": "abc-123" }
Each agent in chain:
- Receives charge from upstream
- Issues charge to downstream
- Profit = upstream charge - downstream costs
Billing Ledger nets all balances in single settlement.
Cache Service Architecture
The cache service runs as a high-performance middleware:
Components:
- Redis: Authorization cache, rate limits, pending charges
- PostgreSQL: Transaction logs, analytics
- WebSocket: Real-time balance updates
- RPC Node: Blockchain monitoring
Key Operations:
- Auto-deposit: Monitors forwarding addresses, submits deposits
- Balance tracking: Maintains real-time balance with pending charges
- Charge aggregation: Collects charges from multiple providers
- Batch optimization: Groups charges by gas efficiency
- Dispute monitoring: Tracks dispute windows and outcomes
Security Considerations
Double-spend prevention: Nonce provides idempotency
Signature validation: EIP-712 prevents replay attacks
Time bounds: 15-minute timestamp window prevents stale charges
Rate limiting: Per-authorization rate limits prevent abuse
Atomic settlement: All-or-nothing batch processing
Gas Optimization
- Lazy wallet deployment: Forwarding addresses only deploy on first transaction
- Batch settlement: 1000 charges = 1 transaction (~300k gas)
- Packed encoding: Charge data packed into bytes32 where possible
- Event logs: Detailed data in logs, minimal on-chain storage
This architecture achieves:
- Latency: <10ms authorization
- Cost: <$0.001 per charge at scale
- Throughput: 10,000+ charges/second off-chain
- Settlement: ~1000 charges per on-chain transaction