TransactionSEALED
◆■◇◇&■#%#~◆▒▒╳$@◇◇▓▒%◇▪~▓&#●▪○▪╱?▪█░*■▒*■●#◆◆▓◇○○□$^#╳▫▓$~▓?█●▒$
Transaction ID
Execution Fee
0.00005619 FLOWTransaction Summary
Contract CallCalled Swap, Utils, MetadataViews +5 more
Script Arguments
0rightUserAddressAddress
1leftUserNfts{ String
{
"A.329feb3ab062d289.UFC_NFT.NFT": [
"2653000",
"2651792",
"2655719",
"2650290"
]
}2rightUserNfts{ String
{
"A.329feb3ab062d289.UFC_NFT.NFT": [
"2695232"
]
}3expirationInMinutesUFix64
4320.00000000
4feeVaultIdentifierString?
null
5metadata{ String
{
"environmentId": "evaluate"
}Cadence Script
1import Swap from 0x15f55a75d7843780
2import Utils from 0x15f55a75d7843780
3import MetadataViews from 0x1d7e57aa55817448
4import NonFungibleToken from 0x1d7e57aa55817448
5import FungibleToken from 0xf233dcee88fe0abe
6import FungibleTokenCatalog from 0x9066631feda9e518
7import DapperWalletRestrictions from 0x2d4cebdb9eca6f49
8import DapperWalletCollections from 0x42a54b4f70e7dc81
9
10// v1
11transaction(rightUserAddress: Address, leftUserNfts: { String: [UInt64] }, rightUserNfts: { String: [UInt64] }, expirationInMinutes: UFix64, feeVaultIdentifier: String?, metadata: { String: String }?) {
12
13 let leftUserOffer: Swap.UserOffer
14 let rightUserOffer: Swap.UserOffer
15 let leftCollectionReceiverCapabilities: {String: Capability<&{NonFungibleToken.Receiver}>}
16 let leftCollectionProviderCapabilities: {String: Capability<&{NonFungibleToken.Provider}>}
17 let leftFeeProviderCapabilities: {String: Capability<&{FungibleToken.Provider, FungibleToken.Balance}>}
18 let leftUserAccount: AuthAccount
19
20 prepare(signer: AuthAccount) {
21
22 let unsupportedVaultError = "unsupported vault for "
23 let missingProviderError = "missing or invalid provider capability for "
24 let providerLinkFailedError = "unable to create private link to collection provider for "
25 let nonDapperError = "you cannot trade with a non dapper wallet for "
26 let sameTypeNftError = "your trading partner must have at least one NFT of type "
27 let tradingDisabledError = "trading disabled for "
28
29 self.leftUserAccount = signer
30
31 let normalizeNfts = fun (_ inputNfts: { String: [UInt64] }): [String] {
32
33 let identifiers: [String] = []
34
35 inputNfts.forEachKey(fun (key: String): Bool {
36
37 identifiers.append(key.concat(".").concat(inputNfts[key]![0]!.toString()))
38
39 return true
40 })
41
42 return identifiers
43 }
44
45 let leftCollectionDataMap = Utils.getNFTCollectionData(ownerAddress: signer.address, nftIdentifiers: normalizeNfts(leftUserNfts))
46 let rightCollectionDataMap = Utils.getNFTCollectionData(ownerAddress: rightUserAddress, nftIdentifiers: normalizeNfts(rightUserNfts))
47
48 let mapNfts = fun (ownerAddress: Address, _ inputNfts: { String: [UInt64] }): [Swap.ProposedTradeAsset] {
49
50 let proposedNfts: [Swap.ProposedTradeAsset] = []
51
52 inputNfts.forEachKey(fun (key: String): Bool {
53
54 for nftID in inputNfts[key]! {
55
56 proposedNfts.append(Swap.ProposedTradeAsset(
57 nftID: nftID,
58 type: key,
59 collectionData: leftCollectionDataMap[key] ?? rightCollectionDataMap[key] ?? panic("no collection data for: ".concat(key))
60 ))
61 }
62
63 return true
64 })
65
66 return proposedNfts
67 }
68
69 let getRestrictions = fun (_ nfts: [Swap.ProposedTradeAsset]): { Type: DapperWalletRestrictions.TypeConfig } {
70
71 let res: { Type: DapperWalletRestrictions.TypeConfig } = { }
72
73 for nft in nfts {
74
75 if (res.containsKey(nft.type)) {
76
77 continue
78 }
79
80 let config = DapperWalletRestrictions.getConfig(nft.type)
81 if (config == nil) {
82
83 continue
84 }
85
86 res.insert(key: nft.type, config!)
87 }
88
89 return res
90}
91
92let anyHasRestriction = fun (_ restrictions: [DapperWalletRestrictions.TypeConfig], _ key: String, _ value: Bool): Bool {
93
94 for restriction in restrictions {
95
96 if (restriction.flags[key] == value) {
97
98 return true
99 }
100 }
101
102 return false
103}
104
105let validateRestrictions = fun (_ restrictions: { Type: DapperWalletRestrictions.TypeConfig }, _ otherRestrictions: { Type: DapperWalletRestrictions.TypeConfig }): Void {
106
107 restrictions.forEachKey(fun (type: Type): Bool {
108
109 let restriction = restrictions[type]!
110
111 assert(restriction.flags["CAN_TRADE"] != false, message: tradingDisabledError.concat(type.identifier))
112
113 // other side must have at least one NFT with the same restriction
114 assert(restriction.flags["CAN_TRADE_EXTERNAL"] != false || anyHasRestriction(otherRestrictions.values, "CAN_TRADE_EXTERNAL", false), message: nonDapperError.concat(type.identifier))
115
116 // other side must have at least one restricted NFT of the same type
117 assert(restriction.flags["CAN_TRADE_DIFF_NFT"] != false || otherRestrictions[type] != nil, message: sameTypeNftError.concat(type.identifier))
118
119 // other side must have at least one NFT with the same restriction
120 assert(restriction.flags["CAN_WITHDRAW"] != false || anyHasRestriction(otherRestrictions.values, "CAN_WITHDRAW", false), message: nonDapperError.concat(type.identifier))
121
122 return true
123 })
124}
125
126 let leftProposedNfts: [Swap.ProposedTradeAsset] = mapNfts(ownerAddress: signer.address, leftUserNfts)
127 let rightProposedNfts: [Swap.ProposedTradeAsset] = mapNfts(ownerAddress: rightUserAddress, rightUserNfts)
128
129 let initializedTypes: [Type] = []
130
131let initializeCollections = fun (signer: AuthAccount, _ inputNfts: [Swap.ProposedTradeAsset]): Void {
132
133 for nft in inputNfts {
134
135 if (initializedTypes.contains(nft.type)) {
136
137 continue
138 }
139
140 let contractType = Utils.getIdentifierContractType(identifier: nft.type.identifier)
141
142 assert(DapperWalletCollections.containsType(contractType), message: "NFT type not allowed: ".concat(nft.type.identifier))
143
144 let collectionData = leftCollectionDataMap[nft.type.identifier] ?? rightCollectionDataMap[nft.type.identifier] ?? panic("collection data lookup failed for: ".concat(nft.type.identifier))
145
146 if (signer.getCapability<&{NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver}>(collectionData.publicPath).borrow() != nil) {
147
148 initializedTypes.append(nft.type)
149 continue
150 }
151
152 if (signer.borrow<&AnyResource>(from: collectionData.storagePath) == nil) {
153
154 signer.save(<-collectionData.createEmptyCollection(), to: collectionData.storagePath)
155 }
156
157 if (signer.getCapability<&{NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver}>(collectionData.publicPath).borrow() == nil) {
158
159 signer.unlink(collectionData.publicPath)
160 signer.link<&{NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(collectionData.publicPath, target: collectionData.storagePath)
161 }
162
163 initializedTypes.append(nft.type)
164 }
165}
166
167 initializeCollections(signer: signer, leftProposedNfts)
168 initializeCollections(signer: signer, rightProposedNfts)
169
170 let leftRestrictedTypes = getRestrictions(leftProposedNfts)
171 let rightRestrictedTypes = getRestrictions(rightProposedNfts)
172
173 validateRestrictions(leftRestrictedTypes, rightRestrictedTypes)
174 validateRestrictions(rightRestrictedTypes, leftRestrictedTypes)
175
176 self.leftFeeProviderCapabilities = { }
177
178 if (feeVaultIdentifier != nil) {
179
180 let vaultType = CompositeType(feeVaultIdentifier!) ?? panic(unsupportedVaultError.concat(feeVaultIdentifier!))
181
182 let entry = FungibleTokenCatalog.getVaultForType(vaultType: vaultType) ?? panic(unsupportedVaultError.concat(feeVaultIdentifier!))
183
184 let feePrivatePath = entry.privatePath ?? panic(unsupportedVaultError.concat(feeVaultIdentifier!))
185
186 var feeProvider = signer.getCapability<&{FungibleToken.Provider, FungibleToken.Balance}>(feePrivatePath)
187 if (feeProvider == nil || !feeProvider.check()) {
188
189 let vaultRef = signer.borrow<&{FungibleToken.Provider}>(from: entry.storagePath) ?? panic("Unable to find vault")
190 signer.unlink(feePrivatePath)
191 feeProvider = signer.link<&{FungibleToken.Provider, FungibleToken.Balance}>(feePrivatePath, target: entry.storagePath)!
192 }
193
194 self.leftFeeProviderCapabilities[entry.type.identifier] = feeProvider
195 }
196
197 self.leftUserOffer = Swap.UserOffer(userAddress: signer.address, proposedNfts: leftProposedNfts, metadata: nil)
198 self.rightUserOffer = Swap.UserOffer(userAddress: rightUserAddress, proposedNfts: rightProposedNfts, metadata: nil)
199
200 self.leftCollectionReceiverCapabilities = { }
201
202 let partnerPublicAccount: PublicAccount = getAccount(rightUserAddress)
203
204 for partnerProposedNft in self.rightUserOffer.proposedNfts {
205
206 if (self.leftCollectionReceiverCapabilities[partnerProposedNft.type.identifier] == nil) {
207
208 if (signer.type(at: partnerProposedNft.collectionData.storagePath) != nil) {
209
210 let receiverCapability = signer.getCapability<&{NonFungibleToken.Receiver}>(partnerProposedNft.collectionData.publicPath)
211 if (receiverCapability.check()) {
212
213 self.leftCollectionReceiverCapabilities[partnerProposedNft.type.identifier] = receiverCapability
214 continue
215 }
216 }
217
218 panic(missingProviderError.concat(partnerProposedNft.type.identifier))
219 }
220 }
221
222 self.leftCollectionProviderCapabilities = { }
223
224 for proposedNft in self.leftUserOffer.proposedNfts {
225
226 if (self.leftCollectionProviderCapabilities[proposedNft.type.identifier] == nil) {
227
228 if (signer.getCapability<&{NonFungibleToken.Provider}>(proposedNft.collectionData.providerPath).borrow() == nil) {
229
230 signer.unlink(proposedNft.collectionData.providerPath)
231 signer.link<&{NonFungibleToken.Provider}>(proposedNft.collectionData.providerPath, target: proposedNft.collectionData.storagePath)
232 }
233
234 let providerCapability = signer.getCapability<&{NonFungibleToken.Provider}>(proposedNft.collectionData.providerPath)
235 if (providerCapability.check()) {
236
237 self.leftCollectionProviderCapabilities[proposedNft.type.identifier] = providerCapability
238 continue
239 }
240
241 panic(providerLinkFailedError.concat(proposedNft.type.identifier))
242 }
243 }
244 }
245
246 execute {
247
248 let storedType = self.leftUserAccount.type(at: Swap.SwapCollectionStoragePath)
249
250 if (storedType != nil && storedType != Type<@Swap.SwapCollection>()) {
251
252 let oldCollection <- self.leftUserAccount.load<@AnyResource>(from: Swap.SwapCollectionStoragePath)
253 destroy oldCollection
254 }
255
256 if (self.leftUserAccount.type(at: Swap.SwapCollectionStoragePath) == nil) {
257
258 let newCollection <- Swap.createEmptySwapCollection()
259 self.leftUserAccount.save(<-newCollection, to: Swap.SwapCollectionStoragePath)
260 }
261
262 if (self.leftUserAccount.getCapability<&{Swap.SwapCollectionPublic}>(Swap.SwapCollectionPublicPath).borrow() == nil) {
263
264 self.leftUserAccount.unlink(Swap.SwapCollectionPublicPath)
265 self.leftUserAccount.link<&{Swap.SwapCollectionPublic}>(Swap.SwapCollectionPublicPath, target: Swap.SwapCollectionStoragePath)
266 }
267
268 if (self.leftUserAccount.getCapability<&{Swap.SwapCollectionManager}>(Swap.SwapCollectionPrivatePath).borrow() == nil) {
269
270 self.leftUserAccount.unlink(Swap.SwapCollectionPrivatePath)
271 self.leftUserAccount.link<&{Swap.SwapCollectionManager}>(Swap.SwapCollectionPrivatePath, target: Swap.SwapCollectionStoragePath)
272 }
273
274 let swapCollectionManagerCapability = self.leftUserAccount.getCapability<&{Swap.SwapCollectionManager}>(Swap.SwapCollectionPrivatePath)
275 assert(swapCollectionManagerCapability.check(), message: "Got invalid SwapCollectionManager capability")
276 let swapCollectionManager = swapCollectionManagerCapability.borrow()!
277
278 swapCollectionManager.createProposal(
279 leftUserOffer: self.leftUserOffer,
280 rightUserOffer: self.rightUserOffer,
281 leftUserCapabilities: Swap.UserCapabilities(
282 collectionReceiverCapabilities: self.leftCollectionReceiverCapabilities,
283 collectionProviderCapabilities: self.leftCollectionProviderCapabilities,
284 feeProviderCapabilities: self.leftFeeProviderCapabilities,
285 extraCapabilities: nil
286 ),
287 expirationOffsetMinutes: expirationInMinutes,
288 metadata: metadata
289 )
290 }
291}