Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions purchase-genesispass-v4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Purchase Genesis Pass V4

This transaction allows users to purchase a Genesis Library Pass NFT from the NFTStorefrontV2 marketplace using DapperUtilityCoin (DUC).

## Overview

Genesis Library Pass is a lifetime access pass to the Published NFT library ecosystem, providing access to 2,000+ books, audiobooks, magazines, and exclusive content.

## Transaction Details

- **Contract**: GenesisPassV4
- **Contract Address (Testnet)**: 0x4c55dc21a9da7476
- **Payment Token**: DapperUtilityCoin (DUC)
- **Marketplace**: NFTStorefrontV2

## Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| storefrontAddress | Address | Address of the NFT seller's storefront |
| listingResourceID | UInt64 | ID of the listing to purchase |
| commissionRecipient | Address? | Optional commission recipient address |

## Features

- Auto-initializes buyer's NFT collection if needed
- Supports optional commission payments
- DUC leakage protection via post-condition
- MetadataViews compliant for proper NFT display

## Files

- `purchase-genesispass-v4.cdc` - Main purchase transaction
- `purchase-genesispass-v4-metadata.cdc` - Metadata script for purchase preview
- `testnet.env` - Testnet contract addresses

## Security

- All DUC must remain in Dapper's vault (no leakage)
- Collection auto-initialization prevents failed deposits
- Commission recipient validation ensures proper capability
64 changes: 64 additions & 0 deletions purchase-genesispass-v4/purchase-genesispass-v4-metadata.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import NonFungibleToken from ${NonFungibleToken}
import MetadataViews from ${MetadataViews}
import NFTStorefrontV2 from ${NFTStorefrontV2}
import ${NFTContractName} from ${NFTContractAddress}

access(all) struct PurchaseData {
access(all) let id: UInt64
access(all) let name: String
access(all) let amount: UFix64
access(all) let description: String
access(all) let imageURL: String
access(all) let paymentVaultTypeID: Type

init(id: UInt64, name: String, amount: UFix64, description: String, imageURL: String, paymentVaultTypeID: Type) {
self.id = id
self.name = name
self.amount = amount
self.description = description
self.imageURL = imageURL
self.paymentVaultTypeID = paymentVaultTypeID
}
}

access(all) fun main(storefrontAddress: Address, listingResourceID: UInt64, commissionRecipient: Address?): PurchaseData {

let account = getAccount(storefrontAddress)
let marketCollectionRef = account.capabilities.get<&NFTStorefrontV2.Storefront>(
NFTStorefrontV2.StorefrontPublicPath
).borrow()
?? panic("Could not borrow Storefront from provided address")

let saleItem = marketCollectionRef.borrowListing(listingResourceID: listingResourceID)
?? panic("No item with that ID")

let listingDetails = saleItem.getDetails()!

let collection = account.capabilities.get<&{NonFungibleToken.Collection}>(
${NFTContractName}.CollectionPublicPath
).borrow()
?? panic("Could not borrow a reference to the collection")

let nft = collection.borrowNFT(listingDetails.nftID)
?? panic("Could not borrow a reference to the NFT")

let viewSerial = nft.resolveView(Type<MetadataViews.Serial>())!
let displaySerial = viewSerial as! MetadataViews.Serial

if let view = nft.resolveView(Type<MetadataViews.Display>()) {

let display = view as! MetadataViews.Display

let purchaseData = PurchaseData(
id: displaySerial.number,
name: display.name,
amount: listingDetails.salePrice,
description: display.description,
imageURL: display.thumbnail.uri(),
paymentVaultTypeID: listingDetails.salePaymentVaultType
)

return purchaseData
}
panic("No NFT")
}
73 changes: 73 additions & 0 deletions purchase-genesispass-v4/purchase-genesispass-v4.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import FungibleToken from ${FungibleToken}
import NonFungibleToken from ${NonFungibleToken}
import MetadataViews from ${MetadataViews}
import DapperUtilityCoin from ${DapperUtilityCoin}
import NFTStorefrontV2 from ${NFTStorefrontV2}
import ${NFTContractName} from ${NFTContractAddress}

transaction(storefrontAddress: Address, listingResourceID: UInt64, commissionRecipient: Address?) {

let mainVault: auth(FungibleToken.Withdraw) &DapperUtilityCoin.Vault
let paymentVault: @{FungibleToken.Vault}
let nftReceiver: &{NonFungibleToken.Receiver}
let storefront: &{NFTStorefrontV2.StorefrontPublic}
let listing: &{NFTStorefrontV2.ListingPublic}
let balanceBeforeTransfer: UFix64
var commissionRecipientCap: Capability<&{FungibleToken.Receiver}>?

prepare(dapper: auth(BorrowValue) &Account, buyer: auth(Storage, Capabilities) &Account) {
self.commissionRecipientCap = nil

if buyer.capabilities.borrow<&${NFTContractName}.Collection>(${NFTContractName}.CollectionPublicPath) == nil {
let collection <- ${NFTContractName}.createEmptyCollection(nftType: Type<@${NFTContractName}.NFT>())
buyer.storage.save(<-collection, to: ${NFTContractName}.CollectionStoragePath)

let collectionCap = buyer.capabilities.storage.issue<&${NFTContractName}.Collection>(${NFTContractName}.CollectionStoragePath)
buyer.capabilities.publish(collectionCap, at: ${NFTContractName}.CollectionPublicPath)
}

self.storefront = getAccount(storefrontAddress).capabilities.borrow<&{NFTStorefrontV2.StorefrontPublic}>(
NFTStorefrontV2.StorefrontPublicPath
) ?? panic("Could not borrow Storefront from provided address")

self.listing = self.storefront.borrowListing(listingResourceID: listingResourceID)
?? panic("No Offer with that ID in Storefront")
let price = self.listing.getDetails().salePrice

self.mainVault = dapper.storage.borrow<auth(FungibleToken.Withdraw) &DapperUtilityCoin.Vault>(from: /storage/dapperUtilityCoinVault)
?? panic("Cannot borrow DapperUtilityCoin vault from acct storage")
self.balanceBeforeTransfer = self.mainVault.balance
self.paymentVault <- self.mainVault.withdraw(amount: price)

let collectionData = ${NFTContractName}.resolveContractView(resourceType: nil, viewType: Type<MetadataViews.NFTCollectionData>()) as! MetadataViews.NFTCollectionData?
?? panic("ViewResolver does not resolve NFTCollectionData view")
self.nftReceiver = buyer.capabilities.borrow<&{NonFungibleToken.Receiver}>(collectionData.publicPath)
?? panic("Cannot borrow NFT collection receiver from account")

let commissionAmount = self.listing.getDetails().commissionAmount

if commissionRecipient != nil && commissionAmount != 0.0 {
let _commissionRecipientCap = getAccount(commissionRecipient!).capabilities.get<&{FungibleToken.Receiver}>(
/public/dapperUtilityCoinReceiver
)
assert(_commissionRecipientCap.check(), message: "Commission Recipient doesn't have DapperUtilityCoin receiving capability")
self.commissionRecipientCap = _commissionRecipientCap
} else if commissionAmount == 0.0 {
self.commissionRecipientCap = nil
} else {
panic("Commission recipient can not be empty when commission amount is non zero")
}
}

post {
self.mainVault.balance == self.balanceBeforeTransfer: "DapperUtilityCoin leakage"
}

execute {
let item <- self.listing.purchase(
payment: <-self.paymentVault,
commissionRecipient: self.commissionRecipientCap
)
self.nftReceiver.deposit(token: <-item)
}
}
7 changes: 7 additions & 0 deletions purchase-genesispass-v4/testnet.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FungibleToken=0x9a0766d93b6608b7
NonFungibleToken=0x631e88ae7f1d7c20
DapperUtilityCoin=0x82ec283f88a62e65
NFTStorefrontV2=0x2d55b98eb200daef
MetadataViews=0x631e88ae7f1d7c20
NFTContractName=GenesisPassV4
NFTContractAddress=0x4c55dc21a9da7476