Smart Contract

EncryptedUsageSubscriptions

A.6daee039a7b9c2f0.EncryptedUsageSubscriptions

Valid From

123,855,418

Deployed

1w ago
Feb 19, 2026, 05:43:18 AM UTC

Dependents

12 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import FlareFDCTriggers from 0x6daee039a7b9c2f0
4import FTSOPriceFeedConnector from 0x6daee039a7b9c2f0
5import DeFiActions from 0x92195d814edf9cb0
6
7/// EncryptedUsageSubscriptions: Dynamic pricing with encrypted on-chain API key storage
8/// Includes real-time USD to FLOW conversion via Flare FTSO and secure API key management
9access(all) contract EncryptedUsageSubscriptions {
10    
11    /// Events
12    access(all) event SubscriptionCreated(vaultId: UInt64, owner: Address, provider: Address)
13    access(all) event UsageDataReceived(vaultId: UInt64, usage: UsageReport, source: String)
14    access(all) event PriceCalculated(vaultId: UInt64, basePrice: UFix64, usageMultiplier: UFix64, finalPrice: UFix64)
15    access(all) event PaymentProcessed(vaultId: UInt64, amount: UFix64, provider: Address)
16    access(all) event AutomaticPaymentProcessed(vaultId: UInt64, amount: UFix64, provider: Address, totalPaidToDate: UFix64)
17    access(all) event EntitlementUpdated(vaultId: UInt64, withdrawLimit: UFix64, validUntil: UFix64)
18    access(all) event UsageTierChanged(vaultId: UInt64, oldTier: String, newTier: String)
19    access(all) event PriceConversion(vaultId: UInt64, usdCost: UFix64, flowPrice: UFix64, flowAmount: UFix64)
20    
21    /// Storage paths
22    access(all) let VaultStoragePath: StoragePath
23    access(all) let VaultPublicPath: PublicPath
24    access(all) let ProviderStoragePath: StoragePath
25    
26    /// Global registry
27    access(all) var totalVaults: UInt64
28    access(all) let vaultRegistry: {UInt64: Address}
29    
30    /// Pricing tiers based on usage
31    access(all) struct PricingTier {
32        access(all) let name: String
33        access(all) let minUsage: UInt64      // Min API calls/tokens
34        access(all) let maxUsage: UInt64      // Max API calls/tokens
35        access(all) let pricePerUnit: UFix64  // Price per 1000 tokens/calls
36        access(all) let discountRate: UFix64  // Volume discount (0.0 - 1.0)
37        
38        init(name: String, minUsage: UInt64, maxUsage: UInt64, pricePerUnit: UFix64, discountRate: UFix64) {
39            self.name = name
40            self.minUsage = minUsage
41            self.maxUsage = maxUsage
42            self.pricePerUnit = pricePerUnit
43            self.discountRate = discountRate
44        }
45    }
46    
47    /// Usage report from LiteLLM via FDC
48    access(all) struct UsageReport {
49        access(all) let timestamp: UFix64
50        access(all) let period: String         // "daily", "weekly", "monthly"
51        access(all) let totalTokens: UInt64    // Total tokens consumed
52        access(all) let apiCalls: UInt64       // Number of API calls
53        access(all) let models: {String: UInt64}  // Usage by model (gpt-4, claude, etc)
54        access(all) let costEstimate: UFix64   // Provider's cost estimate in USD
55        access(all) let metadata: {String: String}
56        
57        init(
58            timestamp: UFix64,
59            period: String,
60            totalTokens: UInt64,
61            apiCalls: UInt64,
62            models: {String: UInt64},
63            costEstimate: UFix64,
64            metadata: {String: String}
65        ) {
66            self.timestamp = timestamp
67            self.period = period
68            self.totalTokens = totalTokens
69            self.apiCalls = apiCalls
70            self.models = models
71            self.costEstimate = costEstimate
72            self.metadata = metadata
73        }
74    }
75    
76    /// Entitlement types
77    access(all) enum EntitlementType: UInt8 {
78        access(all) case fixed      // Fixed withdrawal limit set by user
79        access(all) case dynamic    // Grows with usage as long as vault is funded
80    }
81    
82    /// Dynamic entitlement for automated withdrawals
83    access(all) struct Entitlement {
84        access(all) let vaultId: UInt64
85        access(all) var withdrawLimit: UFix64   // Max amount provider can withdraw
86        access(all) var usedAmount: UFix64      // Amount already withdrawn
87        access(all) var validUntil: UFix64      // Expiration timestamp
88        access(all) var lastUpdate: UFix64      // Last FDC update
89        access(all) var isActive: Bool
90        access(all) let entitlementType: EntitlementType  // Fixed or Dynamic
91        access(all) let fixedLimit: UFix64      // Original fixed limit (if fixed type)
92        
93        access(all) fun updateLimit(newLimit: UFix64, validityPeriod: UFix64) {
94            // For fixed entitlements, never exceed the original fixed limit
95            if self.entitlementType == EntitlementType.fixed {
96                self.withdrawLimit = newLimit > self.fixedLimit ? self.fixedLimit : newLimit
97            } else {
98                // Dynamic entitlements can grow with usage
99                self.withdrawLimit = newLimit
100            }
101            
102            self.validUntil = getCurrentBlock().timestamp + validityPeriod
103            self.lastUpdate = getCurrentBlock().timestamp
104        }
105        
106        access(all) fun recordWithdrawal(amount: UFix64) {
107            self.usedAmount = self.usedAmount + amount
108        }
109        
110        access(all) fun getRemainingAllowance(): UFix64 {
111            if getCurrentBlock().timestamp > self.validUntil {
112                return 0.0
113            }
114            return self.withdrawLimit > self.usedAmount 
115                ? self.withdrawLimit - self.usedAmount 
116                : 0.0
117        }
118        
119        init(vaultId: UInt64, entitlementType: EntitlementType, initialLimit: UFix64, validityPeriod: UFix64) {
120            self.vaultId = vaultId
121            self.entitlementType = entitlementType
122            self.fixedLimit = entitlementType == EntitlementType.fixed ? initialLimit : 0.0
123            self.withdrawLimit = initialLimit
124            self.usedAmount = 0.0
125            self.validUntil = getCurrentBlock().timestamp + validityPeriod
126            self.lastUpdate = getCurrentBlock().timestamp
127            self.isActive = true
128        }
129    }
130    
131    /// Public interface for subscription vault - exposes safe public methods
132    access(all) resource interface SubscriptionVaultPublic {
133        access(all) let id: UInt64
134        access(all) let customer: Address
135        access(all) let provider: Address
136        access(all) let serviceName: String
137        access(all) var encryptedApiKey: String?
138        access(all) var keyEncryptionSalt: String?
139        
140        // Public methods
141        access(all) fun deposit(from: @{FungibleToken.Vault})
142        access(all) fun getBalance(): UFix64
143        access(all) fun getPricingInfo(): {String: AnyStruct}
144        access(all) fun getVaultInfo(): {String: AnyStruct}
145        access(all) fun processUsageData(usage: UsageReport)
146        access(all) fun withdrawWithEntitlement(amount: UFix64): @{FungibleToken.Vault}
147        access(all) fun getEncryptedLiteLLMKeyData(): {String: String?}
148        access(all) fun hasApiKey(): Bool
149    }
150    
151    /// Restricted interface for key management - only accessible to authorized parties
152    access(all) resource interface SubscriptionVaultKeyManagement {
153        access(all) fun setEncryptedLiteLLMApiKey(encryptedKey: String, salt: String, caller: Address)
154    }
155
156    /// Usage-based subscription vault
157    access(all) resource SubscriptionVault: SubscriptionVaultPublic, SubscriptionVaultKeyManagement {
158        access(all) let id: UInt64
159        access(all) let customer: Address
160        access(all) let provider: Address
161        access(all) let serviceName: String
162        
163        // Funding
164        access(self) let vault: @{FungibleToken.Vault}
165        
166        // Usage tracking
167        access(all) var currentUsage: UsageReport?
168        access(all) var usageHistory: [UsageReport]
169        access(all) var currentTier: PricingTier
170        
171        // Cumulative usage tracking for differential payments
172        access(all) var lastPaidTokens: UInt64      // Tokens we've already paid for
173        access(all) var lastPaidRequests: UInt64    // Requests we've already paid for
174        access(all) var totalPaidAmount: UFix64     // Total FLOW paid to provider
175        access(all) var lastOracleUpdate: UFix64    // Timestamp of last oracle confirmation
176        
177        // Dynamic pricing
178        access(all) var basePrice: UFix64
179        access(all) var usageMultiplier: UFix64
180        access(all) var currentPrice: UFix64
181        
182        // Entitlements
183        access(all) var entitlement: Entitlement
184        
185        // Settings
186        access(all) var autoPay: Bool
187        access(all) var maxMonthlySpend: UFix64
188        
189        // Selected AI models (max 3)
190        access(all) let selectedModels: [String]  // Model IDs like ["gpt-4", "claude-3-sonnet"]
191        access(all) let modelPricing: {String: UFix64}  // Model-specific pricing overrides
192        
193        // Encrypted LiteLLM API Key - stored on-chain securely
194        access(all) var encryptedApiKey: String?  // Encrypted LiteLLM API key
195        access(all) var keyEncryptionSalt: String?  // Salt used for encryption
196        
197        /// Set encrypted LiteLLM API key - RESTRICTED ACCESS
198        /// Only vault owner, provider, or contract can call this
199        access(all) fun setEncryptedLiteLLMApiKey(encryptedKey: String, salt: String, caller: Address) {
200            // Access control: only allow owner or provider
201            pre {
202                caller == self.customer || caller == self.provider: 
203                "Only vault owner or provider can set encrypted API key"
204            }
205            
206            self.encryptedApiKey = encryptedKey
207            self.keyEncryptionSalt = salt
208            log("βœ… Encrypted LiteLLM API key set by authorized caller: ".concat(caller.toString()))
209        }
210        
211        /// Get encrypted LiteLLM API key data - PUBLIC ACCESS (safe since encrypted)
212        access(all) fun getEncryptedLiteLLMKeyData(): {String: String?} {
213            return {
214                "encryptedKey": self.encryptedApiKey,
215                "salt": self.keyEncryptionSalt
216            }
217        }
218        
219        /// Check if this vault has an API key - PUBLIC ACCESS
220        access(all) fun hasApiKey(): Bool {
221            return self.encryptedApiKey != nil && self.keyEncryptionSalt != nil
222        }
223        
224        /// Process usage data from FDC and update pricing
225        access(all) fun processUsageData(usage: UsageReport) {
226            // Store usage report
227            self.currentUsage = usage
228            self.usageHistory.append(usage)
229            
230            // Calculate NEW usage since last payment (differential)
231            let newTokens = usage.totalTokens > self.lastPaidTokens ? usage.totalTokens - self.lastPaidTokens : 0
232            let newRequests = usage.apiCalls > self.lastPaidRequests ? usage.apiCalls - self.lastPaidRequests : 0
233            
234            log("πŸ“Š Processing differential usage:")
235            log("   Total tokens: ".concat(usage.totalTokens.toString()).concat(" (+").concat(newTokens.toString()).concat(" new)"))
236            log("   Total requests: ".concat(usage.apiCalls.toString()).concat(" (+").concat(newRequests.toString()).concat(" new)"))
237            log("   Last paid tokens: ".concat(self.lastPaidTokens.toString()))
238            
239            // Only process payment if there's NEW usage
240            if UInt64(newTokens) > 0 || UInt64(newRequests) > 0 {
241                // Update pricing tier based on TOTAL usage
242                let newTier = EncryptedUsageSubscriptions.calculateTier(usage.totalTokens)
243                if newTier.name != self.currentTier.name {
244                    emit UsageTierChanged(
245                        vaultId: self.id,
246                        oldTier: self.currentTier.name,
247                        newTier: newTier.name
248                    )
249                    self.currentTier = newTier
250                }
251                
252                // Calculate price for NEW usage only
253                let newUsageReport = UsageReport(
254                    timestamp: usage.timestamp,
255                    period: usage.period,
256                    totalTokens: UInt64(newTokens),
257                    apiCalls: UInt64(newRequests),
258                    models: usage.models,
259                    costEstimate: 0.0, // Will be calculated
260                    metadata: usage.metadata
261                )
262                
263                self.calculateDynamicPrice(newUsageReport)
264                
265                // Process automatic payment for new usage
266                self.processAutomaticPayment(newUsageAmount: self.currentPrice)
267                
268                // Update paid tracking
269                self.lastPaidTokens = usage.totalTokens
270                self.lastPaidRequests = usage.apiCalls
271                self.lastOracleUpdate = getCurrentBlock().timestamp
272            }
273            
274            emit UsageDataReceived(
275                vaultId: self.id,
276                usage: usage,
277                source: "LiteLLM via FDC"
278            )
279        }
280        
281        /// Calculate dynamic price based on usage
282        /// Calculate price in FLOW tokens from USD cost using Flare price oracle
283        access(self) fun calculateDynamicPrice(_ usage: UsageReport) {
284            // Use cost estimate from LiteLLM if available (in USD)
285            let usdCost = usage.costEstimate
286            
287            if usdCost > 0.0 {
288                // Convert USD to FLOW using Flare FTSO price feed
289                let flowPrice = self.getFlowPriceInUSD()
290                let flowAmount = usdCost / flowPrice
291                
292                // Store pricing data
293                self.basePrice = usdCost
294                self.usageMultiplier = 1.0
295                self.currentPrice = flowAmount
296                
297                emit PriceConversion(
298                    vaultId: self.id,
299                    usdCost: usdCost,
300                    flowPrice: flowPrice,
301                    flowAmount: flowAmount
302                )
303                
304                emit PriceCalculated(
305                    vaultId: self.id,
306                    basePrice: self.basePrice,
307                    usageMultiplier: self.usageMultiplier,
308                    finalPrice: self.currentPrice
309                )
310            } else {
311                // Fallback to old token-based pricing if no USD cost provided
312                let tokenThousands = UFix64(usage.totalTokens) / 1000.0
313                var calculatedPrice = tokenThousands * self.currentTier.pricePerUnit
314                
315                // Apply volume discount
316                calculatedPrice = calculatedPrice * (1.0 - self.currentTier.discountRate)
317                
318                // Apply model-specific multipliers based on selected models
319                var modelMultiplier = 1.0
320                var modelCount = 0
321                
322                for model in usage.models.keys {
323                    // Only apply pricing for selected models
324                    if self.selectedModels.contains(model) {
325                        let multiplier = self.modelPricing[model] ?? 1.0
326                        modelMultiplier = modelMultiplier + multiplier
327                        modelCount = modelCount + 1
328                    }
329                }
330                
331                // Average the multipliers if multiple models were used
332                if modelCount > 0 {
333                    modelMultiplier = modelMultiplier / UFix64(modelCount)
334                }
335                
336                self.usageMultiplier = modelMultiplier
337                self.currentPrice = calculatedPrice * modelMultiplier
338                
339                emit PriceCalculated(
340                    vaultId: self.id,
341                    basePrice: self.basePrice,
342                    usageMultiplier: self.usageMultiplier,
343                    finalPrice: self.currentPrice
344                )
345            }
346        }
347        
348        /// Get current FLOW price in USD from Flare FTSO price feed
349        access(self) fun getFlowPriceInUSD(): UFix64 {
350            // Get FLOW/USD price from FTSO price feed
351            if let priceData = FTSOPriceFeedConnector.getCurrentPrice(symbol: "FLOW/USD") {
352                // Ensure price is reasonable (between $0.10 and $100.00) and verified
353                if priceData.verified && priceData.price > 0.1 && priceData.price < 100.0 {
354                    log("πŸ’± FLOW price from FTSO: $".concat(priceData.price.toString()).concat(" (verified)"))
355                    return priceData.price
356                }
357            }
358            
359            // Fallback price if FTSO is unavailable (approximate current FLOW price)
360            let fallbackPrice = 0.75  // $0.75 per FLOW as fallback
361            log("⚠️ Using fallback FLOW price: $".concat(fallbackPrice.toString()))
362            return fallbackPrice
363        }
364        
365        /// Update entitlement for provider withdrawals
366        access(self) fun updateEntitlement() {
367            let withdrawLimit = self.currentPrice
368            let validityPeriod = 86400.0 * 30.0  // 30 days
369            
370            self.entitlement.updateLimit(
371                newLimit: withdrawLimit,
372                validityPeriod: validityPeriod
373            )
374            
375            emit EntitlementUpdated(
376                vaultId: self.id,
377                withdrawLimit: withdrawLimit,
378                validUntil: self.entitlement.validUntil
379            )
380        }
381        
382        /// Provider withdraws based on entitlement
383        access(all) fun withdrawWithEntitlement(amount: UFix64): @{FungibleToken.Vault} {
384            // Check entitlement allowance
385            let remainingAllowance = self.entitlement.getRemainingAllowance()
386            assert(amount <= remainingAllowance, message: "Amount exceeds entitlement allowance")
387            assert(amount <= self.vault.balance, message: "Insufficient vault balance")
388            
389            let payment <- self.vault.withdraw(amount: amount)
390            self.entitlement.recordWithdrawal(amount: amount)
391            
392            emit PaymentProcessed(
393                vaultId: self.id,
394                amount: amount,
395                provider: self.provider
396            )
397            
398            return <- payment
399        }
400        
401        /// Process automatic payment to provider based on new usage
402        access(self) fun processAutomaticPayment(newUsageAmount: UFix64) {
403            // Check if automatic payments are enabled
404            if !self.autoPay {
405                log("⏸️ Auto-pay disabled, skipping automatic payment")
406                return
407            }
408            
409            // Check if vault has sufficient balance
410            if self.vault.balance < newUsageAmount {
411                log("⚠️ Insufficient vault balance for automatic payment")
412                log("   Required: ".concat(newUsageAmount.toString()).concat(" FLOW"))
413                log("   Available: ".concat(self.vault.balance.toString()).concat(" FLOW"))
414                return
415            }
416            
417            // Check monthly spending limits
418            if self.totalPaidAmount + newUsageAmount > self.maxMonthlySpend {
419                log("⚠️ Monthly spending limit exceeded, skipping automatic payment")
420                log("   Would exceed limit by: ".concat((self.totalPaidAmount + newUsageAmount - self.maxMonthlySpend).toString()).concat(" FLOW"))
421                return
422            }
423            
424            // Process automatic payment
425            log("πŸ’° Processing automatic payment:")
426            log("   Amount: ".concat(newUsageAmount.toString()).concat(" FLOW"))
427            log("   Provider: ".concat(self.provider.toString()))
428            
429            // Transfer funds directly to provider
430            let payment <- self.vault.withdraw(amount: newUsageAmount)
431            
432            // Get provider's Flow vault and deposit payment
433            let providerAccount = getAccount(self.provider)
434            let providerReceiver = providerAccount.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
435            let receiverRef = providerReceiver.borrow()!
436            receiverRef.deposit(from: <- payment)
437            
438            // Update tracking
439            self.totalPaidAmount = self.totalPaidAmount + newUsageAmount
440            self.entitlement.recordWithdrawal(amount: newUsageAmount)
441            
442            emit PaymentProcessed(
443                vaultId: self.id,
444                amount: newUsageAmount,
445                provider: self.provider
446            )
447            
448            emit AutomaticPaymentProcessed(
449                vaultId: self.id,
450                amount: newUsageAmount,
451                provider: self.provider,
452                totalPaidToDate: self.totalPaidAmount
453            )
454            
455            log("βœ… Automatic payment completed successfully")
456        }
457        
458        /// Deposit funds to vault
459        access(all) fun deposit(from: @{FungibleToken.Vault}) {
460            self.vault.deposit(from: <- from)
461        }
462        
463        /// Get vault balance
464        access(all) fun getBalance(): UFix64 {
465            return self.vault.balance
466        }
467        
468        /// Get current pricing info
469        access(all) fun getPricingInfo(): {String: AnyStruct} {
470            return {
471                "currentTier": self.currentTier.name,
472                "basePrice": self.basePrice,
473                "usageMultiplier": self.usageMultiplier,
474                "currentPrice": self.currentPrice,
475                "remainingEntitlement": self.entitlement.getRemainingAllowance()
476            }
477        }
478        
479        /// Get complete vault information for UI display
480        access(all) fun getVaultInfo(): {String: AnyStruct} {
481            return {
482                "vaultId": self.id,
483                "owner": self.customer,
484                "provider": self.provider,
485                "serviceName": self.serviceName,
486                "balance": self.vault.balance,
487                "selectedModels": self.selectedModels,
488                "modelPricing": self.modelPricing,
489                "entitlementType": self.entitlement.entitlementType.rawValue,
490                "withdrawLimit": self.entitlement.withdrawLimit,
491                "usedAmount": self.entitlement.usedAmount,
492                "validUntil": self.entitlement.validUntil,
493                "isActive": self.entitlement.isActive,
494                "currentTier": self.currentTier.name,
495                "basePrice": self.basePrice,
496                "currentPrice": self.currentPrice,
497                "autoPay": self.autoPay,
498                "maxMonthlySpend": self.maxMonthlySpend,
499                "lastPaidTokens": self.lastPaidTokens,
500                "lastPaidRequests": self.lastPaidRequests,
501                "totalPaidAmount": self.totalPaidAmount,
502                "lastOracleUpdate": self.lastOracleUpdate,
503                "hasApiKey": self.hasApiKey(),
504                "encryptedApiKey": self.encryptedApiKey,
505                "keyEncryptionSalt": self.keyEncryptionSalt
506            }
507        }
508        
509        init(
510            owner: Address,
511            provider: Address,
512            serviceName: String,
513            vault: @{FungibleToken.Vault},
514            entitlementType: EntitlementType,
515            initialWithdrawLimit: UFix64,
516            validityPeriod: UFix64,
517            selectedModels: [String]
518        ) {
519            self.id = EncryptedUsageSubscriptions.totalVaults
520            EncryptedUsageSubscriptions.totalVaults = EncryptedUsageSubscriptions.totalVaults + 1
521            
522            self.customer = owner
523            self.provider = provider
524            self.serviceName = serviceName
525            self.vault <- vault
526            
527            self.currentUsage = nil
528            self.usageHistory = []
529            self.currentTier = EncryptedUsageSubscriptions.getDefaultTier()
530            
531            // Initialize cumulative usage tracking
532            self.lastPaidTokens = 0
533            self.lastPaidRequests = 0
534            self.totalPaidAmount = 0.0
535            self.lastOracleUpdate = 0.0
536            
537            self.basePrice = 10.0  // $10 base
538            self.usageMultiplier = 1.0
539            self.currentPrice = 10.0
540            
541            // Create entitlement with user-specified settings
542            self.entitlement = Entitlement(
543                vaultId: self.id,
544                entitlementType: entitlementType,
545                initialLimit: initialWithdrawLimit,
546                validityPeriod: validityPeriod
547            )
548            
549            self.autoPay = true
550            self.maxMonthlySpend = 1000.0
551            
552            // Initialize selected models and pricing
553            self.selectedModels = selectedModels
554            self.modelPricing = {}
555            
556            // Validate model selection (max 3 models)
557            assert(self.selectedModels.length > 0, message: "At least 1 model must be selected")
558            assert(self.selectedModels.length <= 3, message: "Maximum 3 models allowed per subscription")
559            
560            // Set up model-specific pricing overrides
561            for model in self.selectedModels {
562                if model == "gpt-4" || model == "claude-3-opus" {
563                    self.modelPricing[model] = 1.5  // Premium models cost 50% more
564                } else if model == "gpt-3.5-turbo" || model == "claude-3-haiku" {
565                    self.modelPricing[model] = 0.8  // Budget models cost 20% less
566                } else {
567                    self.modelPricing[model] = 1.0  // Standard pricing
568                }
569            }
570            
571            // Initialize encrypted LiteLLM API key fields as nil (will be set after creation)
572            self.encryptedApiKey = nil
573            self.keyEncryptionSalt = nil
574            
575            EncryptedUsageSubscriptions.vaultRegistry[self.id] = owner
576        }
577    }
578    
579    /// FDC Handler for LiteLLM usage data
580    access(all) resource LiteLLMUsageHandler: FlareFDCTriggers.TriggerHandler {
581        access(self) var isHandlerActive: Bool
582        
583        access(all) fun handleTrigger(trigger: FlareFDCTriggers.FDCTrigger): Bool {
584            // Extract usage data from FDC trigger
585            let vaultId = trigger.payload["vaultId"] as? UInt64 ?? 0
586            let totalTokens = trigger.payload["totalTokens"] as? UInt64 ?? 0
587            let apiCalls = trigger.payload["apiCalls"] as? UInt64 ?? 0
588            
589            // Create usage report
590            let models: {String: UInt64} = {}
591            if let modelUsage = trigger.payload["models"] as? {String: UInt64} {
592                for key in modelUsage.keys {
593                    models[key] = modelUsage[key]!
594                }
595            }
596            
597            let usage = UsageReport(
598                timestamp: trigger.timestamp,
599                period: trigger.payload["period"] as? String ?? "daily",
600                totalTokens: totalTokens,
601                apiCalls: apiCalls,
602                models: models,
603                costEstimate: trigger.payload["costEstimate"] as? UFix64 ?? 0.0,  // USD cost from LiteLLM
604                metadata: {}
605            )
606            
607            // Update subscription vault
608            if let ownerAddress = EncryptedUsageSubscriptions.vaultRegistry[vaultId] {
609                // Vault access should be done via transactions with proper authorization
610                log("Usage update requested for vault ID: ".concat(vaultId.toString()))
611                return true
612            }
613            
614            return false
615        }
616        
617        access(all) fun getSupportedTriggerTypes(): [FlareFDCTriggers.TriggerType] {
618            return [
619                FlareFDCTriggers.TriggerType.DefiProtocolEvent
620            ]
621        }
622        
623        access(all) fun isActive(): Bool {
624            return self.isHandlerActive
625        }
626        
627        init() {
628            self.isHandlerActive = true
629        }
630    }
631    
632    /// Provider resource for managing subscriptions
633    access(all) resource ServiceProvider {
634        access(all) let address: Address
635        access(all) let serviceName: String
636        access(all) var totalEarnings: UFix64
637        access(all) var activeSubscriptions: {UInt64: Bool}
638        
639        /// Withdraw from customer vault based on entitlement
640        access(all) fun collectPayment(vaultId: UInt64, amount: UFix64): @{FungibleToken.Vault}? {
641            if let ownerAddress = EncryptedUsageSubscriptions.vaultRegistry[vaultId] {
642                // Vault access should be done via transactions with proper authorization
643                log("Payment collection requested for vault ID: ".concat(vaultId.toString()))
644                // Return nil for now - should be handled via transactions
645                return nil
646            }
647            return nil
648        }
649        
650        init(address: Address, serviceName: String) {
651            self.address = address
652            self.serviceName = serviceName
653            self.totalEarnings = 0.0
654            self.activeSubscriptions = {}
655        }
656    }
657    
658    /// Public functions
659    
660    /// Create a new subscription vault with entitlement settings
661    access(all) fun createSubscriptionVault(
662        owner: Address,
663        provider: Address,
664        serviceName: String,
665        initialDeposit: @{FungibleToken.Vault},
666        entitlementType: EntitlementType,
667        initialWithdrawLimit: UFix64,
668        validityPeriod: UFix64,
669        selectedModels: [String]
670    ): @SubscriptionVault {
671        let vault <- create SubscriptionVault(
672            owner: owner,
673            provider: provider,
674            serviceName: serviceName,
675            vault: <- initialDeposit,
676            entitlementType: entitlementType,
677            initialWithdrawLimit: initialWithdrawLimit,
678            validityPeriod: validityPeriod,
679            selectedModels: selectedModels
680        )
681        
682        emit SubscriptionCreated(vaultId: vault.id, owner: owner, provider: provider)
683        
684        return <- vault
685    }
686    
687    /// Get vault storage path
688    access(all) fun getVaultStoragePath(): StoragePath {
689        return self.VaultStoragePath
690    }
691    
692    /// Get all vault IDs for a user
693    access(all) fun getUserVaultIds(owner: Address): [UInt64] {
694        let vaultIds: [UInt64] = []
695        
696        for vaultId in self.vaultRegistry.keys {
697            if self.vaultRegistry[vaultId] == owner {
698                vaultIds.append(vaultId)
699            }
700        }
701        
702        return vaultIds
703    }
704    
705    /// Get vault information by vault ID
706    access(all) fun getVaultInfo(vaultId: UInt64): {String: AnyStruct}? {
707        if let ownerAddress = self.vaultRegistry[vaultId] {
708            // Vault info access should be done via transactions
709            return {"vaultId": vaultId, "owner": ownerAddress}
710        }
711        return nil
712    }
713    
714    /// Calculate pricing tier based on usage
715    access(all) fun calculateTier(_ totalTokens: UInt64): PricingTier {
716        let tiers = self.getPricingTiers()
717        
718        for tier in tiers {
719            if totalTokens >= tier.minUsage && totalTokens <= tier.maxUsage {
720                return tier
721            }
722        }
723        
724        return self.getDefaultTier()
725    }
726    
727    /// Get pricing tiers
728    access(all) fun getPricingTiers(): [PricingTier] {
729        return [
730            PricingTier(name: "Starter", minUsage: 0, maxUsage: 100000, pricePerUnit: 0.02, discountRate: 0.0),
731            PricingTier(name: "Growth", minUsage: 100001, maxUsage: 1000000, pricePerUnit: 0.015, discountRate: 0.1),
732            PricingTier(name: "Scale", minUsage: 1000001, maxUsage: 10000000, pricePerUnit: 0.01, discountRate: 0.2),
733            PricingTier(name: "Enterprise", minUsage: 10000001, maxUsage: UInt64.max, pricePerUnit: 0.008, discountRate: 0.3)
734        ]
735    }
736    
737    /// Get default tier
738    access(all) fun getDefaultTier(): PricingTier {
739        return PricingTier(name: "Starter", minUsage: 0, maxUsage: 100000, pricePerUnit: 0.02, discountRate: 0.0)
740    }
741    
742    /// Helper function to convert time periods to seconds
743    access(all) fun convertToSeconds(amount: UInt64, unit: String): UFix64 {
744        switch unit {
745            case "hours":
746                return UFix64(amount) * 3600.0
747            case "days":
748                return UFix64(amount) * 86400.0
749            case "months":
750                return UFix64(amount) * 2592000.0  // 30 days
751            default:
752                return UFix64(amount) * 86400.0    // Default to days
753        }
754    }
755    
756    /// Create service provider
757    access(all) fun createServiceProvider(address: Address, serviceName: String): @ServiceProvider {
758        return <- create ServiceProvider(address: address, serviceName: serviceName)
759    }
760    
761    /// Create LiteLLM usage handler
762    access(all) fun createLiteLLMHandler(): @LiteLLMUsageHandler {
763        return <- create LiteLLMUsageHandler()
764    }
765    
766    init() {
767        self.VaultStoragePath = /storage/UsageBasedSubscriptionVault
768        self.VaultPublicPath = /public/UsageBasedSubscriptionVault
769        self.ProviderStoragePath = /storage/UsageBasedServiceProvider
770        
771        self.totalVaults = 0
772        self.vaultRegistry = {}
773    }
774}