Smart Contract
StreamVestScheduler
A.5ec90e3dcf0067c4.StreamVestScheduler
1// StreamVestScheduler.cdc
2// Companion contract that integrates StreamVest with Flow's
3// scheduled-transaction infrastructure for autonomous streaming.
4//
5// Deployed alongside StreamVest — calls the public triggerStream()
6// function on schedule so tokens flow automatically.
7
8import FlowTransactionScheduler from 0xe467b9dd11fa00df
9import StreamVest from 0x5ec90e3dcf0067c4
10import FlowToken from 0x1654653399040a61
11import FungibleToken from 0xf233dcee88fe0abe
12
13access(all) contract StreamVestScheduler {
14
15 // ───────── Events ─────────
16 access(all) event HandlerCreated(nftID: UInt64, collectionAddress: Address, intervalSeconds: UFix64)
17 access(all) event StreamTriggered(nftID: UInt64)
18 access(all) event HandlerCompleted(nftID: UInt64)
19 access(all) event FeesLow(nftID: UInt64, remainingFees: UFix64)
20
21 // ═══════════════════════════════════════════════════════════════════
22 // ScheduledStreamHandler
23 // ═══════════════════════════════════════════════════════════════════
24
25 access(all) resource ScheduledStreamHandler: FlowTransactionScheduler.TransactionHandler {
26 access(self) let collectionAddress: Address
27 access(self) let nftID: UInt64
28 access(self) let intervalSeconds: UFix64
29 access(all) var isComplete: Bool
30 access(self) var feeVault: @FlowToken.Vault
31 access(self) var selfCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>?
32
33 init(
34 collectionAddress: Address,
35 nftID: UInt64,
36 intervalSeconds: UFix64,
37 feeVault: @FlowToken.Vault
38 ) {
39 self.collectionAddress = collectionAddress
40 self.nftID = nftID
41 self.intervalSeconds = intervalSeconds
42 self.isComplete = false
43 self.feeVault <- feeVault
44 self.selfCap = nil
45 }
46
47 /// Must be called once after storing the handler and issuing its capability.
48 access(all) fun setSelfCapability(
49 cap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>
50 ) {
51 pre { self.selfCap == nil: "Self capability already set" }
52 self.selfCap = cap
53 }
54
55 /// Called by the Flow blockchain at each scheduled interval.
56 access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) {
57 if self.isComplete { return }
58
59 // Borrow the public collection from the owner's account.
60 let account = getAccount(self.collectionAddress)
61 let collection = account.capabilities
62 .get<&StreamVest.Collection>(StreamVest.CollectionPublicPath)
63 .borrow()
64 ?? panic("StreamVestScheduler: cannot borrow collection at ".concat(self.collectionAddress.toString()))
65
66 // Deliver vested tokens to the destination.
67 collection.triggerStream(nftID: self.nftID)
68 emit StreamTriggered(nftID: self.nftID)
69
70 // Check if the stream is finished.
71 if let nft = collection.borrowStreamVestNFT(id: self.nftID) {
72 if !nft.isActive {
73 self.isComplete = true
74 emit HandlerCompleted(nftID: self.nftID)
75 return
76 }
77 } else {
78 // NFT no longer in collection.
79 self.isComplete = true
80 emit HandlerCompleted(nftID: self.nftID)
81 return
82 }
83
84 // ── Reschedule for the next interval ──
85 let nextTime = getCurrentBlock().timestamp + self.intervalSeconds
86 let priority = FlowTransactionScheduler.Priority(rawValue: 0)!
87
88 let est = FlowTransactionScheduler.estimate(
89 data: nil,
90 timestamp: nextTime,
91 priority: priority,
92 executionEffort: 1000
93 )
94
95 let fee = est.flowFee ?? 0.0
96 if fee == 0.0 || self.feeVault.balance < fee {
97 emit FeesLow(nftID: self.nftID, remainingFees: self.feeVault.balance)
98 return
99 }
100
101 let fees <- self.feeVault.withdraw(amount: fee) as! @FlowToken.Vault
102
103 let receipt <- FlowTransactionScheduler.schedule(
104 handlerCap: self.selfCap!,
105 data: nil,
106 timestamp: nextTime,
107 priority: priority,
108 executionEffort: 1000,
109 fees: <- fees
110 )
111 destroy receipt
112 }
113
114 /// How much FLOW remains for scheduling fees.
115 access(all) view fun getFeeBalance(): UFix64 {
116 return self.feeVault.balance
117 }
118
119 /// Top up the fee vault so the handler can keep rescheduling.
120 access(all) fun depositFees(from: @FlowToken.Vault) {
121 self.feeVault.deposit(from: <- from)
122 }
123 }
124
125 // ═══════════════════════════════════════════════════════════════════
126 // Factory
127 // ═══════════════════════════════════════════════════════════════════
128
129 access(all) fun createHandler(
130 collectionAddress: Address,
131 nftID: UInt64,
132 intervalSeconds: UFix64,
133 feeVault: @FlowToken.Vault
134 ): @ScheduledStreamHandler {
135 emit HandlerCreated(
136 nftID: nftID,
137 collectionAddress: collectionAddress,
138 intervalSeconds: intervalSeconds
139 )
140 return <- create ScheduledStreamHandler(
141 collectionAddress: collectionAddress,
142 nftID: nftID,
143 intervalSeconds: intervalSeconds,
144 feeVault: <- feeVault
145 )
146 }
147
148 init() {}
149}
150