Smart Contract

DelayedTransfer

A.08dd120226ec2213.DelayedTransfer

Deployed

14h ago
Feb 28, 2026, 02:31:18 AM UTC

Dependents

0 imports
1import FungibleToken from 0xf233dcee88fe0abe
2
3access(all) contract DelayedTransfer {
4  // path for admin resource
5  access(all) let AdminPath: StoragePath
6  // ========== events ==========
7  access(all) event DelayedTransferAdded(id: String)
8  access(all) event DelayedTransferExecuted(
9    id: String,
10    receiver: Address,
11    token: String,
12    amount: UFix64
13  )
14  access(all) event DelayPeriodUpdated(period: UInt64)
15
16  // ========== structs ==========
17  /// one delayed transfer info, will release if current block.timestamp >= info.blockTs + delayPeriod
18  access(all) struct Info {
19    // block.timestamp when this xfer is added
20    access(all) let blockTs: UFix64
21    // .borrow then .deposit
22    access(all) let receiverCap: Capability<&{FungibleToken.Receiver}>
23
24    init(blockTs: UFix64, receiverCap: Capability<&{FungibleToken.Receiver}>){
25      self.blockTs = blockTs
26      self.receiverCap = receiverCap
27    }
28  }
29
30  // ========== contract states and maps ==========
31  // how many seconds for delayed transfer to wait,
32  // default is 3600*24 = 86400, can be changed by Admin
33  access(all) var delayPeriod: UInt64
34  // map from unique ID string to Info struct and from vault, have to keep fromVault separate as struct can't include Resource
35  // and Capability can only be acquired by path which doesn't exist in mint case
36  access(account) var infoMap: {String: Info}
37  access(account) var vaultMap: @{String: {FungibleToken.Vault}}
38  // when SafeBox/PegBridge is paused, this will also be paused
39  access(all) var isPaused: Bool
40
41  // ========== resource ==========
42  access(all) resource Admin {
43    access(all) fun setDelayPeriod(newP: UInt64) {
44      DelayedTransfer.delayPeriod = newP
45      emit DelayPeriodUpdated(
46        period: newP
47      )
48    }
49    // createNewAdmin creates a new Admin resource
50    access(all) fun createNewAdmin(): @Admin {
51      return <-create Admin()
52    }
53  }
54
55  // ========== functions ==========
56  init() {
57    self.delayPeriod = 86400
58    self.infoMap = {}
59    self.vaultMap <- {}
60    self.isPaused = false
61    self.AdminPath = /storage/DelayedTransferAdmin
62    self.account.storage.save<@Admin>(<- create Admin(), to: self.AdminPath)
63  }
64
65  access(account) fun pause() {
66    self.isPaused = true
67  }
68  access(account) fun unPause() {
69    self.isPaused = false
70  }
71
72  // only accessible by contracts in the same account to avoid spam (storage cost and valid ids)
73  access(account) fun addDelayXfer(id: String, receiverCap: Capability<&{FungibleToken.Receiver}>, from: @{FungibleToken.Vault}) {
74    pre {
75      !self.infoMap.containsKey(id): "id already exists!"
76      !self.vaultMap.containsKey(id): "id already exists!"
77    }
78    self.infoMap[id] = Info(blockTs: getCurrentBlock().timestamp, receiverCap: receiverCap)
79    let old <- self.vaultMap[id] <- from
80    destroy old
81    emit DelayedTransferAdded(id: id)
82  }
83
84  // execute xfer, if id is found in infoMap and block is big enough, deposit fund to receiver
85  // we must restrict to account because if safebox/pegbridge contracts are paused, this can't be called either
86  // note if only one contract is paused, we pause all executeDelayXfer for safety
87  access(account) fun executeDelayXfer(_ wdId: String) {
88    pre {
89      !self.isPaused: "delay transfer is paused"
90      self.infoMap.containsKey(wdId): "wdId not found!"
91      self.vaultMap.containsKey(wdId): "wdId not found!"
92    }
93    let xfer = self.infoMap[wdId]!
94    assert(getCurrentBlock().timestamp > xfer.blockTs+UFix64(self.delayPeriod), message: "delayed transfer still locked")
95    // delete from map so no re-trigger
96    self.infoMap.remove(key: wdId)
97    let recRef = xfer.receiverCap.borrow() ?? panic("Could not borrow reference to receiver")
98    let fromVault <- self.vaultMap.remove(key: wdId)!
99    let tokStr = fromVault.getType().identifier
100    let amt = fromVault.balance
101    recRef.deposit(from: <- fromVault)
102    emit DelayedTransferExecuted(
103      id: wdId,
104      receiver: recRef.owner!.address,
105      token: tokStr,
106      amount: amt
107    )
108  }
109
110  access(all) view fun delayTransferExist(id: String): Bool {
111    return self.infoMap.containsKey(id)
112  }
113
114  access(all) view fun getDelayBlockTs(id: String): UFix64 {
115    let info = self.infoMap[id] ?? panic("token not support in contract")
116    return info.blockTs
117  }
118}