TransactionSEALED

◆■◇◇&■#%#~◆▒▒╳$@◇◇▓▒%◇▪~▓&#●▪○▪╱?▪█░*■▒*■●#◆◆▓◇○○□$^#╳▫▓$~▓?█●▒$

Transaction ID

Timestamp

Aug 20, 2024, 02:44:08 AM UTC
1y ago

Block Height

84,920,032

Computation

0

Execution Fee

0.00005619 FLOW

Transaction Summary

Contract Call

Called 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}