@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.
@@ -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
+ }