@flowtyio/flow-contracts 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contracts/dapper/TopShot.cdc +1686 -0
- package/contracts/dapper/TopShotLocking.cdc +165 -0
- package/contracts/dapper/offers/DapperOffersV2.cdc +238 -0
- package/contracts/dapper/offers/OffersV2.cdc +344 -0
- package/contracts/dapper/offers/Resolver.cdc +70 -0
- package/contracts/flow-utils/AddressUtils.cdc +109 -0
- package/contracts/flow-utils/ArrayUtils.cdc +69 -0
- package/contracts/flow-utils/StringUtils.cdc +97 -0
- package/flow.json +73 -1
- package/package.json +1 -1
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import "FungibleToken"
|
|
2
|
+
import "NonFungibleToken"
|
|
3
|
+
import "DapperUtilityCoin"
|
|
4
|
+
import "MetadataViews"
|
|
5
|
+
import "Resolver"
|
|
6
|
+
|
|
7
|
+
// OffersV2
|
|
8
|
+
//
|
|
9
|
+
// Contract holds the Offer resource and a public method to create them.
|
|
10
|
+
//
|
|
11
|
+
// Each Offer can have one or more royalties of the sale price that
|
|
12
|
+
// goes to one or more addresses.
|
|
13
|
+
//
|
|
14
|
+
// Owners of NFT can watch for OfferAvailable events and check
|
|
15
|
+
// the Offer amount to see if they wish to accept the offer.
|
|
16
|
+
//
|
|
17
|
+
// Marketplaces and other aggregators can watch for OfferAvailable events
|
|
18
|
+
// and list offers of interest to logged in users.
|
|
19
|
+
//
|
|
20
|
+
pub contract OffersV2 {
|
|
21
|
+
// OfferAvailable
|
|
22
|
+
// An Offer has been created and added to the users DapperOffer resource.
|
|
23
|
+
//
|
|
24
|
+
|
|
25
|
+
pub event OfferAvailable(
|
|
26
|
+
offerAddress: Address,
|
|
27
|
+
offerId: UInt64,
|
|
28
|
+
nftType: Type,
|
|
29
|
+
offerAmount: UFix64,
|
|
30
|
+
royalties: {Address:UFix64},
|
|
31
|
+
offerType: String,
|
|
32
|
+
offerParamsString: {String:String},
|
|
33
|
+
offerParamsUFix64: {String:UFix64},
|
|
34
|
+
offerParamsUInt64: {String:UInt64},
|
|
35
|
+
paymentVaultType: Type,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
// OfferCompleted
|
|
39
|
+
// The Offer has been resolved. The offer has either been accepted
|
|
40
|
+
// by the NFT owner, or the offer has been removed and destroyed.
|
|
41
|
+
//
|
|
42
|
+
pub event OfferCompleted(
|
|
43
|
+
purchased: Bool,
|
|
44
|
+
acceptingAddress: Address?,
|
|
45
|
+
offerAddress: Address,
|
|
46
|
+
offerId: UInt64,
|
|
47
|
+
nftType: Type,
|
|
48
|
+
offerAmount: UFix64,
|
|
49
|
+
royalties: {Address:UFix64},
|
|
50
|
+
offerType: String,
|
|
51
|
+
offerParamsString: {String:String},
|
|
52
|
+
offerParamsUFix64: {String:UFix64},
|
|
53
|
+
offerParamsUInt64: {String:UInt64},
|
|
54
|
+
paymentVaultType: Type,
|
|
55
|
+
nftId: UInt64?,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// Royalty
|
|
59
|
+
// A struct representing a recipient that must be sent a certain amount
|
|
60
|
+
// of the payment when a NFT is sold.
|
|
61
|
+
//
|
|
62
|
+
pub struct Royalty {
|
|
63
|
+
pub let receiver: Capability<&{FungibleToken.Receiver}>
|
|
64
|
+
pub let amount: UFix64
|
|
65
|
+
|
|
66
|
+
init(receiver: Capability<&{FungibleToken.Receiver}>, amount: UFix64) {
|
|
67
|
+
self.receiver = receiver
|
|
68
|
+
self.amount = amount
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// OfferDetails
|
|
73
|
+
// A struct containing Offers' data.
|
|
74
|
+
//
|
|
75
|
+
pub struct OfferDetails {
|
|
76
|
+
// The ID of the offer
|
|
77
|
+
pub let offerId: UInt64
|
|
78
|
+
// The Type of the NFT
|
|
79
|
+
pub let nftType: Type
|
|
80
|
+
// The Type of the FungibleToken that payments must be made in.
|
|
81
|
+
pub let paymentVaultType: Type
|
|
82
|
+
// The Offer amount for the NFT
|
|
83
|
+
pub let offerAmount: UFix64
|
|
84
|
+
// Flag to tracked the purchase state
|
|
85
|
+
pub var purchased: Bool
|
|
86
|
+
// This specifies the division of payment between recipients.
|
|
87
|
+
pub let royalties: [Royalty]
|
|
88
|
+
// Used to hold Offer metadata and offer type information
|
|
89
|
+
pub let offerParamsString: {String: String}
|
|
90
|
+
pub let offerParamsUFix64: {String:UFix64}
|
|
91
|
+
pub let offerParamsUInt64: {String:UInt64}
|
|
92
|
+
|
|
93
|
+
// setToPurchased
|
|
94
|
+
// Irreversibly set this offer as purchased.
|
|
95
|
+
//
|
|
96
|
+
access(contract) fun setToPurchased() {
|
|
97
|
+
self.purchased = true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// initializer
|
|
101
|
+
//
|
|
102
|
+
init(
|
|
103
|
+
offerId: UInt64,
|
|
104
|
+
nftType: Type,
|
|
105
|
+
offerAmount: UFix64,
|
|
106
|
+
royalties: [Royalty],
|
|
107
|
+
offerParamsString: {String: String},
|
|
108
|
+
offerParamsUFix64: {String:UFix64},
|
|
109
|
+
offerParamsUInt64: {String:UInt64},
|
|
110
|
+
paymentVaultType: Type,
|
|
111
|
+
) {
|
|
112
|
+
self.offerId = offerId
|
|
113
|
+
self.nftType = nftType
|
|
114
|
+
self.offerAmount = offerAmount
|
|
115
|
+
self.purchased = false
|
|
116
|
+
self.royalties = royalties
|
|
117
|
+
self.offerParamsString = offerParamsString
|
|
118
|
+
self.offerParamsUFix64 = offerParamsUFix64
|
|
119
|
+
self.offerParamsUInt64 = offerParamsUInt64
|
|
120
|
+
self.paymentVaultType = paymentVaultType
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// OfferPublic
|
|
125
|
+
// An interface providing a useful public interface to an Offer resource.
|
|
126
|
+
//
|
|
127
|
+
pub resource interface OfferPublic {
|
|
128
|
+
// accept
|
|
129
|
+
// This will accept the offer if provided with the NFT id that matches the Offer
|
|
130
|
+
//
|
|
131
|
+
pub fun accept(
|
|
132
|
+
item: @AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
|
|
133
|
+
receiverCapability: Capability<&{FungibleToken.Receiver}>,
|
|
134
|
+
): Void
|
|
135
|
+
// getDetails
|
|
136
|
+
// Return Offer details
|
|
137
|
+
//
|
|
138
|
+
pub fun getDetails(): OfferDetails
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
pub resource Offer: OfferPublic {
|
|
143
|
+
// The OfferDetails struct of the Offer
|
|
144
|
+
access(self) let details: OfferDetails
|
|
145
|
+
// The vault which will handle the payment if the Offer is accepted.
|
|
146
|
+
access(contract) let vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>
|
|
147
|
+
// Receiver address for the NFT when/if the Offer is accepted.
|
|
148
|
+
access(contract) let nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>
|
|
149
|
+
// Resolver capability for the offer type
|
|
150
|
+
access(contract) let resolverCapability: Capability<&{Resolver.ResolverPublic}>
|
|
151
|
+
|
|
152
|
+
init(
|
|
153
|
+
vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>,
|
|
154
|
+
nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
|
|
155
|
+
nftType: Type,
|
|
156
|
+
amount: UFix64,
|
|
157
|
+
royalties: [Royalty],
|
|
158
|
+
offerParamsString: {String: String},
|
|
159
|
+
offerParamsUFix64: {String:UFix64},
|
|
160
|
+
offerParamsUInt64: {String:UInt64},
|
|
161
|
+
resolverCapability: Capability<&{Resolver.ResolverPublic}>,
|
|
162
|
+
) {
|
|
163
|
+
pre {
|
|
164
|
+
nftReceiverCapability.check(): "reward capability not valid"
|
|
165
|
+
}
|
|
166
|
+
self.vaultRefCapability = vaultRefCapability
|
|
167
|
+
self.nftReceiverCapability = nftReceiverCapability
|
|
168
|
+
self.resolverCapability = resolverCapability
|
|
169
|
+
var price: UFix64 = amount
|
|
170
|
+
let royaltyInfo: {Address:UFix64} = {}
|
|
171
|
+
|
|
172
|
+
for royalty in royalties {
|
|
173
|
+
assert(royalty.receiver.check(), message: "invalid royalty receiver")
|
|
174
|
+
price = price - royalty.amount
|
|
175
|
+
royaltyInfo[royalty.receiver.address] = royalty.amount
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
assert(price > 0.0, message: "price must be > 0")
|
|
179
|
+
|
|
180
|
+
self.details = OfferDetails(
|
|
181
|
+
offerId: self.uuid,
|
|
182
|
+
nftType: nftType,
|
|
183
|
+
offerAmount: amount,
|
|
184
|
+
royalties: royalties,
|
|
185
|
+
offerParamsString: offerParamsString,
|
|
186
|
+
offerParamsUFix64: offerParamsUFix64,
|
|
187
|
+
offerParamsUInt64: offerParamsUInt64,
|
|
188
|
+
paymentVaultType: vaultRefCapability.getType(),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
emit OfferAvailable(
|
|
192
|
+
offerAddress: nftReceiverCapability.address,
|
|
193
|
+
offerId: self.details.offerId,
|
|
194
|
+
nftType: self.details.nftType,
|
|
195
|
+
offerAmount: self.details.offerAmount,
|
|
196
|
+
royalties: royaltyInfo,
|
|
197
|
+
offerType: offerParamsString["_type"] ?? "unknown",
|
|
198
|
+
offerParamsString: self.details.offerParamsString,
|
|
199
|
+
offerParamsUFix64: self.details.offerParamsUFix64,
|
|
200
|
+
offerParamsUInt64: self.details.offerParamsUInt64,
|
|
201
|
+
paymentVaultType: self.details.paymentVaultType,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// accept
|
|
206
|
+
// Accept the offer if...
|
|
207
|
+
// - Calling from an Offer that hasn't been purchased/desetoryed.
|
|
208
|
+
// - Provided with a NFT matching the NFT id within the Offer details.
|
|
209
|
+
// - Provided with a NFT matching the NFT Type within the Offer details.
|
|
210
|
+
//
|
|
211
|
+
pub fun accept(
|
|
212
|
+
item: @AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
|
|
213
|
+
receiverCapability: Capability<&{FungibleToken.Receiver}>,
|
|
214
|
+
): Void {
|
|
215
|
+
|
|
216
|
+
pre {
|
|
217
|
+
!self.details.purchased: "Offer has already been purchased"
|
|
218
|
+
item.isInstance(self.details.nftType): "item NFT is not of specified type"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let resolverCapability = self.resolverCapability.borrow() ?? panic("could not borrow resolver")
|
|
222
|
+
let resolverResult = resolverCapability.checkOfferResolver(
|
|
223
|
+
item: &item as &AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
|
|
224
|
+
offerParamsString: self.details.offerParamsString,
|
|
225
|
+
offerParamsUInt64: self.details.offerParamsUInt64,
|
|
226
|
+
offerParamsUFix64: self.details.offerParamsUFix64,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if !resolverResult {
|
|
230
|
+
panic("Resolver failed, invalid NFT please check Offer criteria")
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
self.details.setToPurchased()
|
|
234
|
+
let nft <- item as! @NonFungibleToken.NFT
|
|
235
|
+
let nftId: UInt64 = nft.id
|
|
236
|
+
self.nftReceiverCapability.borrow()!.deposit(token: <- nft)
|
|
237
|
+
|
|
238
|
+
let initalDucSupply = self.vaultRefCapability.borrow()!.balance
|
|
239
|
+
let payment <- self.vaultRefCapability.borrow()!.withdraw(amount: self.details.offerAmount)
|
|
240
|
+
|
|
241
|
+
// Payout royalties
|
|
242
|
+
for royalty in self.details.royalties {
|
|
243
|
+
if let receiver = royalty.receiver.borrow() {
|
|
244
|
+
let amount = royalty.amount
|
|
245
|
+
let part <- payment.withdraw(amount: amount)
|
|
246
|
+
receiver.deposit(from: <- part)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
receiverCapability.borrow()!.deposit(from: <- payment)
|
|
251
|
+
|
|
252
|
+
// If a DUC vault is being used for payment we must assert that no DUC is leaking from the transactions.
|
|
253
|
+
let isDucVault = self.vaultRefCapability.isInstance(
|
|
254
|
+
Type<Capability<&DapperUtilityCoin.Vault{FungibleToken.Provider, FungibleToken.Balance}>>()
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if isDucVault {
|
|
258
|
+
assert(self.vaultRefCapability.borrow()!.balance == initalDucSupply, message: "DUC is leaking")
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
emit OfferCompleted(
|
|
262
|
+
purchased: self.details.purchased,
|
|
263
|
+
acceptingAddress: receiverCapability.address,
|
|
264
|
+
offerAddress: self.nftReceiverCapability.address,
|
|
265
|
+
offerId: self.details.offerId,
|
|
266
|
+
nftType: self.details.nftType,
|
|
267
|
+
offerAmount: self.details.offerAmount,
|
|
268
|
+
royalties: self.getRoyaltyInfo(),
|
|
269
|
+
offerType: self.details.offerParamsString["_type"] ?? "unknown",
|
|
270
|
+
offerParamsString: self.details.offerParamsString,
|
|
271
|
+
offerParamsUFix64: self.details.offerParamsUFix64,
|
|
272
|
+
offerParamsUInt64: self.details.offerParamsUInt64,
|
|
273
|
+
paymentVaultType: self.details.paymentVaultType,
|
|
274
|
+
nftId: nftId,
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// getDetails
|
|
279
|
+
// Return Offer details
|
|
280
|
+
//
|
|
281
|
+
pub fun getDetails(): OfferDetails {
|
|
282
|
+
return self.details
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// getRoyaltyInfo
|
|
286
|
+
// Return royalty details
|
|
287
|
+
//
|
|
288
|
+
pub fun getRoyaltyInfo(): {Address:UFix64} {
|
|
289
|
+
let royaltyInfo: {Address:UFix64} = {}
|
|
290
|
+
|
|
291
|
+
for royalty in self.details.royalties {
|
|
292
|
+
royaltyInfo[royalty.receiver.address] = royalty.amount
|
|
293
|
+
}
|
|
294
|
+
return royaltyInfo;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
destroy() {
|
|
298
|
+
if !self.details.purchased {
|
|
299
|
+
emit OfferCompleted(
|
|
300
|
+
purchased: self.details.purchased,
|
|
301
|
+
acceptingAddress: nil,
|
|
302
|
+
offerAddress: self.nftReceiverCapability.address,
|
|
303
|
+
offerId: self.details.offerId,
|
|
304
|
+
nftType: self.details.nftType,
|
|
305
|
+
offerAmount: self.details.offerAmount,
|
|
306
|
+
royalties: self.getRoyaltyInfo(),
|
|
307
|
+
offerType: self.details.offerParamsString["_type"] ?? "unknown",
|
|
308
|
+
offerParamsString: self.details.offerParamsString,
|
|
309
|
+
offerParamsUFix64: self.details.offerParamsUFix64,
|
|
310
|
+
offerParamsUInt64: self.details.offerParamsUInt64,
|
|
311
|
+
paymentVaultType: self.details.paymentVaultType,
|
|
312
|
+
nftId: nil,
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// makeOffer
|
|
319
|
+
pub fun makeOffer(
|
|
320
|
+
vaultRefCapability: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>,
|
|
321
|
+
nftReceiverCapability: Capability<&{NonFungibleToken.CollectionPublic}>,
|
|
322
|
+
nftType: Type,
|
|
323
|
+
amount: UFix64,
|
|
324
|
+
royalties: [Royalty],
|
|
325
|
+
offerParamsString: {String:String},
|
|
326
|
+
offerParamsUFix64: {String:UFix64},
|
|
327
|
+
offerParamsUInt64: {String:UInt64},
|
|
328
|
+
resolverCapability: Capability<&{Resolver.ResolverPublic}>,
|
|
329
|
+
): @Offer {
|
|
330
|
+
let newOfferResource <- create Offer(
|
|
331
|
+
vaultRefCapability: vaultRefCapability,
|
|
332
|
+
nftReceiverCapability: nftReceiverCapability,
|
|
333
|
+
nftType: nftType,
|
|
334
|
+
amount: amount,
|
|
335
|
+
royalties: royalties,
|
|
336
|
+
offerParamsString: offerParamsString,
|
|
337
|
+
offerParamsUFix64: offerParamsUFix64,
|
|
338
|
+
offerParamsUInt64: offerParamsUInt64,
|
|
339
|
+
resolverCapability: resolverCapability,
|
|
340
|
+
)
|
|
341
|
+
return <-newOfferResource
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import "NonFungibleToken"
|
|
2
|
+
import "MetadataViews"
|
|
3
|
+
import "TopShot"
|
|
4
|
+
|
|
5
|
+
pub contract Resolver {
|
|
6
|
+
// Current list of supported resolution rules.
|
|
7
|
+
pub enum ResolverType: UInt8 {
|
|
8
|
+
pub case NFT
|
|
9
|
+
pub case TopShotEdition
|
|
10
|
+
pub case MetadataViewsEditions
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Public resource interface that defines a method signature for checkOfferResolver
|
|
14
|
+
// which is used within the Resolver resource for offer acceptance validation
|
|
15
|
+
pub resource interface ResolverPublic {
|
|
16
|
+
pub fun checkOfferResolver(
|
|
17
|
+
item: &AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
|
|
18
|
+
offerParamsString: {String:String},
|
|
19
|
+
offerParamsUInt64: {String:UInt64},
|
|
20
|
+
offerParamsUFix64: {String:UFix64}): Bool
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// Resolver resource holds the Offer exchange resolution rules.
|
|
25
|
+
pub resource OfferResolver: ResolverPublic {
|
|
26
|
+
// checkOfferResolver
|
|
27
|
+
// Holds the validation rules for resolver each type of supported ResolverType
|
|
28
|
+
// Function returns TRUE if the provided nft item passes the criteria for exchange
|
|
29
|
+
pub fun checkOfferResolver(
|
|
30
|
+
item: &AnyResource{NonFungibleToken.INFT, MetadataViews.Resolver},
|
|
31
|
+
offerParamsString: {String:String},
|
|
32
|
+
offerParamsUInt64: {String:UInt64},
|
|
33
|
+
offerParamsUFix64: {String:UFix64}): Bool {
|
|
34
|
+
if offerParamsString["resolver"] == ResolverType.NFT.rawValue.toString() {
|
|
35
|
+
assert(item.id.toString() == offerParamsString["nftId"], message: "item NFT does not have specified ID")
|
|
36
|
+
return true
|
|
37
|
+
} else if offerParamsString["resolver"] == ResolverType.TopShotEdition.rawValue.toString() {
|
|
38
|
+
// // Get the Top Shot specific metadata for this NFT
|
|
39
|
+
let view = item.resolveView(Type<TopShot.TopShotMomentMetadataView>())!
|
|
40
|
+
let metadata = view as! TopShot.TopShotMomentMetadataView
|
|
41
|
+
if offerParamsString["playId"] == metadata.playID.toString() && offerParamsString["setId"] == metadata.setID.toString() {
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
} else if offerParamsString["resolver"] == ResolverType.MetadataViewsEditions.rawValue.toString() {
|
|
45
|
+
if let views = item.resolveView(Type<MetadataViews.Editions>()) {
|
|
46
|
+
let editions = views as! [MetadataViews.Edition]
|
|
47
|
+
var hasCorrectMetadataView = false
|
|
48
|
+
for edition in editions {
|
|
49
|
+
if edition.name == offerParamsString["editionName"] {
|
|
50
|
+
hasCorrectMetadataView = true
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
assert(hasCorrectMetadataView == true, message: "editionId does not exist on NFT")
|
|
54
|
+
return true
|
|
55
|
+
} else {
|
|
56
|
+
panic("NFT does not use MetadataViews.Editions")
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
panic("Invalid Resolver on Offer")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
pub fun createResolver(): @OfferResolver {
|
|
68
|
+
return <-create OfferResolver()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import "StringUtils"
|
|
2
|
+
|
|
3
|
+
pub contract AddressUtils {
|
|
4
|
+
|
|
5
|
+
pub fun withoutPrefix(_ input: String): String {
|
|
6
|
+
var address = input
|
|
7
|
+
|
|
8
|
+
// get rid of 0x
|
|
9
|
+
if address.length > 1 && address.utf8[1] == 120 {
|
|
10
|
+
address = address.slice(from: 2, upTo: address.length)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ensure even length
|
|
14
|
+
if address.length % 2 == 1 {
|
|
15
|
+
address = "0".concat(address)
|
|
16
|
+
}
|
|
17
|
+
return address
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub fun parseUInt64(_ input: AnyStruct): UInt64? {
|
|
21
|
+
var stringValue = ""
|
|
22
|
+
|
|
23
|
+
if let string = input as? String {
|
|
24
|
+
stringValue = string
|
|
25
|
+
} else if let address = input as? Address {
|
|
26
|
+
stringValue = address.toString()
|
|
27
|
+
} else if let type = input as? Type {
|
|
28
|
+
let parts = StringUtils.split(type.identifier, ".")
|
|
29
|
+
if parts.length == 1 {
|
|
30
|
+
return nil
|
|
31
|
+
}
|
|
32
|
+
stringValue = parts[1]
|
|
33
|
+
} else {
|
|
34
|
+
return nil
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let address = self.withoutPrefix(stringValue)
|
|
38
|
+
let bytes = address.decodeHex()
|
|
39
|
+
var r: UInt64 = 0
|
|
40
|
+
var length = bytes.length
|
|
41
|
+
for byte in bytes {
|
|
42
|
+
length = length - 1
|
|
43
|
+
r = r + (UInt64(byte) << UInt64(length * 8))
|
|
44
|
+
}
|
|
45
|
+
return r
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub fun parseAddress(_ input: AnyStruct): Address? {
|
|
49
|
+
if let parsed = self.parseUInt64(input) {
|
|
50
|
+
return Address(parsed)
|
|
51
|
+
}
|
|
52
|
+
return nil
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pub fun isValidAddress(_ input: AnyStruct, forNetwork: String): Bool {
|
|
56
|
+
let address = self.parseUInt64(input)
|
|
57
|
+
if address == nil {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let codeWords: {String: UInt64} = {
|
|
62
|
+
"MAINNET" : 0,
|
|
63
|
+
"TESTNET" : 0x6834ba37b3980209,
|
|
64
|
+
"EMULATOR": 0x1cb159857af02018
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let parityCheckMatrixColumns: [UInt64] = [
|
|
68
|
+
0x00001, 0x00002, 0x00004, 0x00008, 0x00010, 0x00020, 0x00040, 0x00080,
|
|
69
|
+
0x00100, 0x00200, 0x00400, 0x00800, 0x01000, 0x02000, 0x04000, 0x08000,
|
|
70
|
+
0x10000, 0x20000, 0x40000, 0x7328d, 0x6689a, 0x6112f, 0x6084b, 0x433fd,
|
|
71
|
+
0x42aab, 0x41951, 0x233ce, 0x22a81, 0x21948, 0x1ef60, 0x1deca, 0x1c639,
|
|
72
|
+
0x1bdd8, 0x1a535, 0x194ac, 0x18c46, 0x1632b, 0x1529b, 0x14a43, 0x13184,
|
|
73
|
+
0x12942, 0x118c1, 0x0f812, 0x0e027, 0x0d00e, 0x0c83c, 0x0b01d, 0x0a831,
|
|
74
|
+
0x0982b, 0x07034, 0x0682a, 0x05819, 0x03807, 0x007d2, 0x00727, 0x0068e,
|
|
75
|
+
0x0067c, 0x0059d, 0x004eb, 0x003b4, 0x0036a, 0x002d9, 0x001c7, 0x0003f
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
var parity: UInt64 = 0
|
|
79
|
+
var codeWord: UInt64 = codeWords[forNetwork]!
|
|
80
|
+
codeWord = codeWord ^ address!
|
|
81
|
+
|
|
82
|
+
if codeWord == 0 {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for column in parityCheckMatrixColumns{
|
|
87
|
+
if codeWord & 1 == 1 {
|
|
88
|
+
parity = parity ^ column
|
|
89
|
+
}
|
|
90
|
+
codeWord = codeWord >> 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return parity == 0 && codeWord == 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub fun getNetworkFromAddress(_ input: AnyStruct): String? {
|
|
97
|
+
for network in ["MAINNET", "TESTNET", "EMULATOR"]{
|
|
98
|
+
if self.isValidAddress(input, forNetwork: network){
|
|
99
|
+
return network
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return nil
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pub fun currentNetwork(): String {
|
|
106
|
+
return self.getNetworkFromAddress(self.account.address)!
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copied from https://github.com/bluesign/flow-utils/blob/dnz/cadence/contracts/ArrayUtils.cdc with minor adjustments
|
|
2
|
+
|
|
3
|
+
pub contract ArrayUtils {
|
|
4
|
+
|
|
5
|
+
pub fun rangeFunc(_ start: Int, _ end: Int, _ f: ((Int): Void)) {
|
|
6
|
+
var current = start
|
|
7
|
+
while current < end {
|
|
8
|
+
f(current)
|
|
9
|
+
current = current + 1
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub fun range(_ start: Int, _ end: Int): [Int] {
|
|
14
|
+
var res: [Int] = []
|
|
15
|
+
self.rangeFunc(start, end, fun (i: Int) {
|
|
16
|
+
res.append(i)
|
|
17
|
+
})
|
|
18
|
+
return res
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub fun reverse(_ array: [Int]): [Int] {
|
|
22
|
+
var res: [Int] = []
|
|
23
|
+
var i: Int = array.length - 1
|
|
24
|
+
while i >= 0 {
|
|
25
|
+
res.append(array[i])
|
|
26
|
+
i = i - 1
|
|
27
|
+
}
|
|
28
|
+
return res
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub fun transform(_ array: &[AnyStruct], _ f : ((AnyStruct): AnyStruct)){
|
|
32
|
+
for i in self.range(0, array.length){
|
|
33
|
+
array[i] = f(array[i])
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub fun iterate(_ array: [AnyStruct], _ f : ((AnyStruct): Bool)){
|
|
38
|
+
for item in array{
|
|
39
|
+
if !f(item){
|
|
40
|
+
break
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub fun map(_ array: [AnyStruct], _ f : ((AnyStruct): AnyStruct)) : [AnyStruct] {
|
|
46
|
+
var res : [AnyStruct] = []
|
|
47
|
+
for item in array{
|
|
48
|
+
res.append(f(item))
|
|
49
|
+
}
|
|
50
|
+
return res
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fun mapStrings(_ array: [String], _ f: ((String) : String) ) : [String] {
|
|
54
|
+
var res : [String] = []
|
|
55
|
+
for item in array{
|
|
56
|
+
res.append(f(item))
|
|
57
|
+
}
|
|
58
|
+
return res
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub fun reduce(_ array: [AnyStruct], _ initial: AnyStruct, _ f : ((AnyStruct, AnyStruct): AnyStruct)) : AnyStruct{
|
|
62
|
+
var res: AnyStruct = f(initial, array[0])
|
|
63
|
+
for i in self.range(1, array.length){
|
|
64
|
+
res = f(res, array[i])
|
|
65
|
+
}
|
|
66
|
+
return res
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import "ArrayUtils"
|
|
2
|
+
|
|
3
|
+
pub contract StringUtils {
|
|
4
|
+
|
|
5
|
+
pub fun format(_ s: String, _ args: {String:String}): String{
|
|
6
|
+
var formatted = s
|
|
7
|
+
for key in args.keys{
|
|
8
|
+
formatted = StringUtils.replaceAll(formatted, "{".concat(key).concat("}"), args[key]!)
|
|
9
|
+
}
|
|
10
|
+
return formatted
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub fun explode(_ s: String): [String]{
|
|
14
|
+
var chars : [String] = []
|
|
15
|
+
for i in ArrayUtils.range(0, s.length){
|
|
16
|
+
chars.append(s[i].toString())
|
|
17
|
+
}
|
|
18
|
+
return chars
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub fun trimLeft(_ s: String): String{
|
|
22
|
+
for i in ArrayUtils.range(0, s.length){
|
|
23
|
+
if s[i] != " "{
|
|
24
|
+
return s.slice(from: i, upTo: s.length)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return ""
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub fun trim(_ s: String): String{
|
|
31
|
+
return self.trimLeft(s)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub fun replaceAll(_ s: String, _ search: String, _ replace: String): String{
|
|
35
|
+
return self.join(self.split(s, search), replace)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fun hasPrefix(_ s: String, _ prefix: String) : Bool{
|
|
39
|
+
return s.length >= prefix.length && s.slice(from:0, upTo: prefix.length)==prefix
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub fun hasSuffix(_ s: String, _ suffix: String) : Bool{
|
|
43
|
+
return s.length >= suffix.length && s.slice(from:s.length-suffix.length, upTo: s.length)==suffix
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
pub fun index(_ s : String, _ substr : String, _ startIndex: Int): Int?{
|
|
47
|
+
for i in ArrayUtils.range(startIndex,s.length-substr.length+1){
|
|
48
|
+
if s[i]==substr[0] && s.slice(from:i, upTo:i+substr.length) == substr{
|
|
49
|
+
return i
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return nil
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pub fun count(_ s: String, _ substr: String): Int{
|
|
56
|
+
var pos = [self.index(s, substr, 0)]
|
|
57
|
+
while pos[0]!=nil {
|
|
58
|
+
pos.insert(at:0, self.index(s, substr, pos[0]!+pos.length*substr.length+1))
|
|
59
|
+
}
|
|
60
|
+
return pos.length-1
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub fun contains(_ s: String, _ substr: String): Bool {
|
|
64
|
+
if let index = self.index(s, substr, 0) {
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fun substringUntil(_ s: String, _ until: String, _ startIndex: Int): String{
|
|
71
|
+
if let index = self.index( s, until, startIndex){
|
|
72
|
+
return s.slice(from:startIndex, upTo: index)
|
|
73
|
+
}
|
|
74
|
+
return s.slice(from:startIndex, upTo:s.length)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub fun split(_ s: String, _ delimiter: String): [String] {
|
|
78
|
+
let segments: [String] = []
|
|
79
|
+
var p = 0
|
|
80
|
+
while p<=s.length{
|
|
81
|
+
var preDelimiter = self.substringUntil(s, delimiter, p)
|
|
82
|
+
segments.append(preDelimiter)
|
|
83
|
+
p = p + preDelimiter.length + delimiter.length
|
|
84
|
+
}
|
|
85
|
+
return segments
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub fun join(_ strs: [String], _ separator: String): String {
|
|
89
|
+
var joinedStr = ""
|
|
90
|
+
for s in strs {
|
|
91
|
+
joinedStr = joinedStr.concat(s).concat(separator)
|
|
92
|
+
}
|
|
93
|
+
return joinedStr.slice(from: 0, upTo: joinedStr.length - separator.length)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
}
|