Smart Contract
EncryptedUsageSubscriptions
A.6daee039a7b9c2f0.EncryptedUsageSubscriptions
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}