Smart Contract

Teleport

A.e81193c424cfd3fb.Teleport

Deployed

2d ago
Feb 26, 2026, 01:49:14 PM UTC

Dependents

0 imports
1
2import NonFungibleToken from 0x1d7e57aa55817448
3import Clock from 0xe81193c424cfd3fb
4import Wearables from 0xe81193c424cfd3fb
5import FindUtils from 0x097bafa4e0b48eef
6import FindRelatedAccounts from 0x097bafa4e0b48eef
7import FlowToken from 0x1654653399040a61
8import FungibleToken from 0xf233dcee88fe0abe
9import TokenForwarding from 0xe544175ee0461c4b
10// import "DapperStorageRent"
11import GenesisBoxRegistry from 0xe81193c424cfd3fb
12
13
14access(all) contract Teleport {
15
16	//mapping of remoteId so DoodleId to data
17	access(contract) let registryRemote:  {UInt64: Data}
18
19	//mapping of dooplicator id to doodleId
20	access(contract) let registryTeleporter:  {UInt64: UInt64}
21
22	//was misspelled in an earlier version, needs to be here...
23	access(all) event Refueled()
24	access(all) event Refuelled(address:Address, amount:UFix64, missingStorage:UInt64, storageUsed:UInt64, storageCapacity:UInt64)
25
26	access(all) event Teleported(data:Data)
27	access(all) event Opened(data:GenesisBoxRegistry.Data)
28
29	access(all) struct Data {
30
31		//the receiver address on flow
32		access(all) let receiver: Address
33
34		//the doodleId of the asset beeing teleported
35		access(all) let remoteId:UInt64
36
37		//the id of the teleporter token
38		access(all) let teleporterId:UInt64
39
40		//the remote address on ETH
41		access(all) let remoteAddress:String
42
43		//context like ethTx or other info to add to event
44		access(all) let context: {String:String}
45
46		//the wearable template ids that will be minted and sent
47		access(all) let wearableTemplateIds:[UInt64]
48
49
50		init(receiver: Address,remoteId:UInt64,remoteAddress:String, templateId: UInt64, wearableTemplateIds:[UInt64],context: {String:String}, teleporterId:UInt64) {
51			self.receiver=receiver
52			self.remoteId=remoteId
53			self.remoteAddress=remoteAddress
54			self.context=context
55			self.wearableTemplateIds=wearableTemplateIds
56			self.teleporterId=teleporterId
57		}
58	}
59
60	//store the proxy for the admin
61	access(all) let TeleportProxyPublicPath: PublicPath
62	access(all) let TeleportProxyStoragePath: StoragePath
63	access(all) let TeleportServerStoragePath: StoragePath
64
65
66	// This is just an empty resource to signal that you can control the admin, more logic can be added here or changed later if you want to
67	access(all) resource Server {
68
69	}
70
71	/// ===================================================================================
72	// Teleport things
73	/// ===================================================================================
74
75	//Teleport client to use for capability receiver pattern
76	access(all) fun createTeleportProxyClient() : @TeleportProxy {
77		return <- create TeleportProxy()
78	}
79
80	//interface to use for capability receiver pattern
81	access(all) resource interface TeleportProxyClient {
82		access(all) fun addCapability(_ cap: Capability<&Server>)
83	}
84
85	// Entitlement for TeleportProxy Owner
86	access(all) entitlement Owner
87
88	//admin proxy with capability receiver
89	access(all) resource TeleportProxy: TeleportProxyClient {
90
91		access(self) var capability: Capability<&Server>?
92
93		access(all) fun addCapability(_ cap: Capability<&Server>) {
94			pre {
95				cap.check() : "Invalid server capablity"
96				self.capability == nil : "Server already set"
97			}
98			self.capability = cap
99		}
100
101		access(Owner) fun teleport(_ data:Data) {
102			pre {
103				self.capability != nil: "Cannot create Teleport, capability is not set"
104			}
105
106			let status = Teleport.isAllowed(remoteId: data.remoteId, teleporterId: data.teleporterId)
107			if !status.allowed {
108				panic(status.message)
109			}
110
111			let trust = Teleport.isTrusted(flow: data.receiver, ethereum: data.remoteAddress)
112			if !trust.allowed{
113				panic(trust.message)
114			}
115
116
117			let account=getAccount(data.receiver)
118			let wearable= account.capabilities.get<&Wearables.Collection>(Wearables.CollectionPublicPath)!.borrow() ?? panic("cannot borrow werable cap")
119
120			let context=data.context
121			for id in data.wearableTemplateIds {
122				Wearables.mintNFT(recipient: wearable, template:id, context:context)
123			}
124
125			Teleport.registryRemote[data.remoteId]=data
126			Teleport.registryTeleporter[data.teleporterId]=data.remoteId
127
128			emit Teleported(data:data)
129
130			if let receiver= account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!.borrow() {
131				let isDapper=receiver.isInstance(Type<@TokenForwarding.Forwarder>())
132
133				if !isDapper {
134					return
135				}
136			}
137
138			//try to refill first and then fill up if we need to?
139			// DapperStorageRent.tryRefill(data.receiver)
140
141			let buffer=1000 as UInt64
142
143			let recipient=getAccount(data.receiver)
144			var used: UInt64 = recipient.storage.used
145			var capacity: UInt64 = recipient.storage.capacity
146
147
148			var missingStorage=0 as UInt64
149			if used > capacity {
150				missingStorage=(used-capacity)+buffer
151			} else {
152				let remainingStorage=capacity-used
153				if remainingStorage < buffer{
154					missingStorage=buffer-remainingStorage
155				}
156			}
157
158			// if missingStorage > 0 {
159			// 	let amount=0.1 //we just give a fixed amount right now
160			// 	emit Refuelled(address:data.receiver, amount:amount, missingStorage:missingStorage, storageUsed:used, storageCapacity:capacity)
161			// 	let vaultRef = Teleport.account.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("cannot get flow token vault")
162			// 	let newVault <- DapperStorageRent.fundedRefillV2(address: data.receiver, tokens: <- vaultRef.withdraw(amount: amount))
163			// 	vaultRef.deposit(from: <-newVault)
164		  	// }
165		}
166
167		access(Owner) fun openBox(_ data:GenesisBoxRegistry.Data) {
168			pre {
169				self.capability != nil: "Cannot create Teleport, capability is not set"
170			}
171
172			let record = GenesisBoxRegistry.getGenesisBoxStatus(data.genesisBoxId)
173			if record != nil {
174				panic("Genesis Box ID : ".concat(data.genesisBoxId.toString()).concat(" has already been opened by ").concat(record!.receiver.toString()))
175			}
176
177			let trust = Teleport.isTrusted(flow: data.receiver, ethereum: data.remoteAddress)
178			if !trust.allowed{
179				panic(trust.message)
180			}
181
182
183			let account=getAccount(data.receiver)
184			let wearable= account.capabilities.get<&Wearables.Collection>(Wearables.CollectionPublicPath)!.borrow() ?? panic("cannot borrow werable cap")
185
186			let context=data.context
187			for id in data.wearableTemplateIds {
188				Wearables.mintNFT(recipient: wearable, template:id, context:context)
189			}
190
191			GenesisBoxRegistry.setData(data)
192
193			emit Opened(data:data)
194
195			if let receiver= account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!.borrow() {
196				let isDapper=receiver.isInstance(Type<@TokenForwarding.Forwarder>())
197
198				if !isDapper {
199					return
200				}
201			}
202
203			// //try to refill first and then fill up if we need to?
204			// DapperStorageRent.tryRefill(data.receiver)
205
206			let buffer=1000 as UInt64
207
208			let recipient=getAccount(data.receiver)
209			var used: UInt64 = recipient.storage.used
210			var capacity: UInt64 = recipient.storage.capacity
211
212
213			var missingStorage=0 as UInt64
214			if used > capacity {
215				missingStorage=(used-capacity)+buffer
216			} else {
217				let remainingStorage=capacity-used
218				if remainingStorage < buffer{
219					missingStorage=buffer-remainingStorage
220				}
221			}
222
223			// if missingStorage > 0 {
224			// 	let amount=0.1 //we just give a fixed amount right now
225			// 	emit Refuelled(address:data.receiver, amount:amount, missingStorage:missingStorage, storageUsed:used, storageCapacity:capacity)
226			// 	let vaultRef = Teleport.account.storage.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) ?? panic("cannot get flow token vault")
227			// 	let newVault <- DapperStorageRent.fundedRefillV2(address: data.receiver, tokens: <- vaultRef.withdraw(amount: amount))
228			// 	vaultRef.deposit(from: <-newVault)
229		  	// }
230		}
231
232		init() {
233			self.capability = nil
234		}
235	}
236
237	access(all) fun getRemoteStatus(_ id:UInt64) : Data? {
238		return self.registryRemote[id]
239	}
240
241	access(all) fun getTeleporterStatus(_ id:UInt64) : Data? {
242		if let remoteId = self.registryTeleporter[id] {
243			return self.getRemoteStatus(remoteId)
244		}
245		return nil
246	}
247
248	access(all) fun getGenesisBoxStatus(_ id:UInt64) : GenesisBoxRegistry.Data? {
249		return GenesisBoxRegistry.getGenesisBoxStatus(id)
250	}
251
252	access(all) fun isValid(remoteId: UInt64, teleporterId: UInt64) : Bool {
253		if let checkRemoteId = self.registryTeleporter[teleporterId] {
254			return remoteId == checkRemoteId
255		}
256		return false
257	}
258
259	access(all) struct AllowedStatus {
260		access(all) let allowed: Bool
261		access(all) let message: String
262
263		init(remoteId: UInt64, teleporterId: UInt64) {
264			var remoteMessage = ""
265			var teleporterMessage = ""
266			let ids = "DoodleID : ".concat(remoteId.toString()).concat(" DooplicatorID : ").concat(teleporterId.toString())
267			if let remote = Teleport.getRemoteStatus(remoteId) {
268				remoteMessage = FindUtils.joinMapToString(remote.context)
269			}
270			if let teleporter = Teleport.getTeleporterStatus(teleporterId) {
271				teleporterMessage = FindUtils.joinMapToString(teleporter.context)
272			}
273
274			if remoteMessage == "" && teleporterMessage == "" {
275				self.message = ""
276				self.allowed = true
277
278			} else if remoteMessage == teleporterMessage {
279				self.message = ids.concat(" was teleported as a combination. ").concat(remoteMessage)
280				self.allowed = false
281			} else {
282				var message = ""
283				var remoteTeleported = false
284				message = message.concat("DoodleID : ").concat(remoteId.toString())
285				if remoteMessage != "" {
286					message = message.concat(" was teleported. Remote : ").concat(remoteMessage)
287					remoteTeleported = true
288				} else {
289					message = message.concat(" was not teleported. ")
290				}
291
292				message = message.concat("DooplicatorID : ").concat(teleporterId.toString())
293				if teleporterMessage != "" {
294					message = message.concat(" was teleported. Teleporter : ").concat(teleporterMessage)
295					if remoteTeleported {
296						message = "Combination was teleported separately. ".concat(message)
297					}
298				} else {
299					message = message.concat(" was not teleported. ")
300				}
301				self.message = message
302				self.allowed = false
303			}
304		}
305	}
306
307	access(all) struct TrustStatus{
308		access(all) let allowed:Bool
309		access(all) let message:String
310
311		init(flow:Address, ethereum:String) {
312			let account=getAccount(flow)
313			if let related = account.capabilities.get<&FindRelatedAccounts.Accounts>(FindRelatedAccounts.publicPath)!.borrow() {
314				if !related.verify(network: "Ethereum", address:ethereum) {
315					self.allowed=false
316					self.message="receiver with address ".concat(flow.toString()).concat(" is not set up to teleport from ETH address ").concat(ethereum)
317				} else {
318					self.allowed=true
319					self.message=""
320				}
321			} else {
322				self.allowed=false
323				self.message="No related accounts registered for the account ".concat(flow.toString())
324			}
325
326		}
327	}
328
329
330	access(all) fun isAllowed(remoteId: UInt64, teleporterId: UInt64) : AllowedStatus {
331		return AllowedStatus(remoteId: remoteId, teleporterId: teleporterId)
332	}
333
334	access(all) fun isTrusted(flow:Address, ethereum: String) : TrustStatus{
335		return TrustStatus(flow:flow, ethereum:ethereum)
336	}
337
338	access(all) fun isGenesisBoxAllowed(_ genesisBoxId: UInt64) : GenesisBoxRegistry.AllowedStatus {
339		return GenesisBoxRegistry.AllowedStatus(genesisBoxId)
340	}
341
342	init() {
343
344		self.TeleportProxyPublicPath= /public/teleportProxy
345		self.TeleportProxyStoragePath=/storage/teleportProxy
346		self.registryRemote={}
347		self.registryTeleporter={}
348
349		//create a dummy server for now, if we have a resource later we want to use instead of server we can change to that
350		self.TeleportServerStoragePath=/storage/teleportServer
351		self.account.storage.save(<- create Server(), to: self.TeleportServerStoragePath)
352		let cap = self.account.capabilities.storage.issue<&Server>(self.TeleportServerStoragePath)
353	}
354}