Smart Contract
FlowVaultsClosedBeta
A.b1d63873c3cc9f79.FlowVaultsClosedBeta
1access(all) contract FlowVaultsClosedBeta {
2
3 access(all) entitlement Admin
4 access(all) entitlement Beta
5
6 access(all) resource BetaBadge {
7 access(all) let assignedTo: Address
8 init(_ addr: Address) {
9 self.assignedTo = addr
10 }
11 access(all) view fun getOwner(): Address {
12 return self.assignedTo
13 }
14 }
15
16 // --- Paths ---
17 access(all) let UserBetaCapStoragePath: StoragePath
18 access(all) let AdminHandleStoragePath: StoragePath
19
20 // --- Registry: which capability was issued to which address, and revocation flags ---
21 access(all) struct AccessInfo {
22 access(all) let capID: UInt64
23 access(all) let isRevoked: Bool
24
25 init(_ capID: UInt64, _ isRevoked: Bool) {
26 self.capID = capID
27 self.isRevoked = isRevoked
28 }
29 }
30 access(all) var issuedCapIDs: {Address: AccessInfo}
31
32 // --- Events ---
33 access(all) event BetaGranted(addr: Address, capID: UInt64)
34 access(all) event BetaRevoked(addr: Address, capID: UInt64?)
35
36 /// Per-user badge storage path (under the *contract/deployer* account)
37 access(contract) fun _badgePath(_ addr: Address): StoragePath {
38 return StoragePath(identifier: "TY_BetaBadge_".concat(addr.toString()))!
39 }
40
41 /// Ensure the admin-owned badge exists for the user
42 access(contract) fun _ensureBadge(_ addr: Address) {
43 let p = self._badgePath(addr)
44 if self.account.storage.type(at: p) == nil {
45 self.account.storage.save(<-create BetaBadge(addr), to: p)
46 }
47 }
48
49 access(contract) fun _destroyBadge(_ addr: Address) {
50 let p = self._badgePath(addr)
51 if let badge <- self.account.storage.load<@BetaBadge>(from: p) {
52 destroy badge
53 }
54 }
55
56 /// Issue a capability from the contract/deployer account and record its ID
57 access(contract) fun _issueBadgeCap(_ addr: Address): Capability<auth(Beta) &BetaBadge> {
58 let p = self._badgePath(addr)
59 let cap: Capability<auth(Beta) &BetaBadge> =
60 self.account.capabilities.storage.issue<auth(Beta) &BetaBadge>(p)
61
62 self.issuedCapIDs[addr] = AccessInfo(cap.id, false)
63
64 if let ctrl = self.account.capabilities.storage.getController(byCapabilityID: cap.id) {
65 ctrl.setTag("flowvaults-beta")
66 }
67
68 emit BetaGranted(addr: addr, capID: cap.id)
69 return cap
70 }
71
72 /// Delete the recorded controller, revoking *all copies* of the capability
73 access(contract) fun _revokeByAddress(_ addr: Address) {
74 let info = self.issuedCapIDs[addr] ?? panic("No cap recorded for address")
75 let ctrl = self.account.capabilities.storage.getController(byCapabilityID: info.capID)
76 ?? panic("Missing controller for recorded cap ID")
77 ctrl.delete()
78 self.issuedCapIDs[addr] = AccessInfo(info.capID, true)
79 self._destroyBadge(addr)
80 emit BetaRevoked(addr: addr, capID: info.capID)
81 }
82
83 // 2) A small in-account helper resource that performs privileged ops
84 access(all) resource AdminHandle {
85 access(Admin) fun grantBeta(addr: Address): Capability<auth(FlowVaultsClosedBeta.Beta) &FlowVaultsClosedBeta.BetaBadge> {
86 FlowVaultsClosedBeta._ensureBadge(addr)
87 return FlowVaultsClosedBeta._issueBadgeCap(addr)
88 }
89
90 access(Admin) fun revokeByAddress(addr: Address) {
91 FlowVaultsClosedBeta._revokeByAddress(addr)
92 }
93 }
94
95 /// Read-only check used by any gated entrypoint
96 access(all) view fun getBetaCapID(_ addr: Address): UInt64? {
97 if let info = self.issuedCapIDs[addr] {
98 if info.isRevoked {
99 assert(info.isRevoked, message: "Beta access revoked")
100 return nil
101 }
102 return info.capID
103 }
104 return nil
105 }
106
107 access(all) view fun validateBeta(_ addr: Address?, _ betaRef: auth(Beta) &BetaBadge): Bool {
108 if (addr == nil) {
109 assert(addr == nil, message: "Address is required for Beta verification")
110 return false
111 }
112 let recordedID: UInt64? = self.getBetaCapID(addr!);
113 if recordedID == nil {
114 assert(recordedID == nil, message: "No Beta access")
115 return false
116 }
117
118 if betaRef.getOwner() != addr {
119 assert(betaRef.getOwner() != addr, message: "BetaBadge may only be used by its assigned owner")
120 return false
121 }
122
123 return true
124 }
125
126 init() {
127 self.AdminHandleStoragePath = StoragePath(
128 identifier: "FlowVaultsClosedBetaAdmin_\(self.account.address)"
129 )!
130 self.UserBetaCapStoragePath = StoragePath(
131 identifier: "FlowVaultsUserBetaCap_\(self.account.address)"
132 )!
133
134 self.issuedCapIDs = {}
135
136 // Create and store the admin handle in *this* (deployer) account
137 self.account.storage.save(<-create AdminHandle(), to: self.AdminHandleStoragePath)
138 }
139}
140