Skip to content

Commit fdf444e

Browse files
authored
Merge pull request #85 from dapperlabs/seanp/add-atlas-transactions-and-parser
Add atlas user transaction for pack sales and cadence parser
2 parents 6819dfe + 8857392 commit fdf444e

3 files changed

Lines changed: 151 additions & 0 deletions

File tree

atlas/embed.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package atlas
2+
3+
import (
4+
_ "embed"
5+
)
6+
7+
// Transactions is a list of all the transactions we export with imports mapped
8+
var (
9+
//go:embed transactions/user/buy_packs_primary_sale.cdc
10+
UserBuyPacksPrimarySale []byte
11+
)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import FungibleToken from 0x{{.FungibleTokenContractAddress}}
2+
import NonFungibleToken from 0x{{.NonFungibleTokenContractAddress}}
3+
import DapperUtilityCoin from 0x{{.DapperUtilityCoinContractAddress}}
4+
import {{.NFTProductName}}, AllDay from 0x{{.NFTContractAddress}}
5+
import NFTStorefront from 0x{{.NFTStorefrontV1ContractAddress}}
6+
7+
/// Transaction facilitates the purchase of listed NFT.
8+
/// It takes the storefront address, listing resource that need
9+
/// to be purchased & a address that will takeaway the commission.
10+
///
11+
/// Buyer of the listing (,i.e. underling NFT) would authorize and sign the
12+
/// transaction and if purchase happens then transacted NFT would store in
13+
/// buyer's collection.
14+
15+
transaction() {
16+
let paymentVault: @{FungibleToken.Vault}
17+
let {{.NFTProductName}}Collection: &{NonFungibleToken.CollectionPublic}
18+
let storefront: &NFTStorefront.Storefront
19+
let listings: [&{NFTStorefront.ListingPublic}]
20+
21+
prepare(universalDucPayer: auth(Storage, Capabilities) &Account) {
22+
let sellerAddress: Address = 0x{{.StorefrontAddress}}
23+
let buyerAddress: Address = 0x{{.RecipientAddress}}
24+
let listingIds: [UInt64] = [{{.ListingIds}}]
25+
let buyer = getAccount(buyerAddress)
26+
27+
assert(sellerAddress != buyerAddress, message : "Buyer and seller can not be same")
28+
// Access the storefront public resource of the seller to purchase the listing.
29+
self.storefront = getAccount(sellerAddress)
30+
.capabilities.borrow<&NFTStorefront.Storefront>(
31+
NFTStorefront.StorefrontPublicPath
32+
)
33+
?? panic("Could not borrow Storefront from provided address")
34+
35+
var salePrice: UFix64 = 0.0
36+
self.listings = []
37+
for listingID in listingIds {
38+
let listing = self.storefront.borrowListing(listingResourceID: listingID)
39+
?? panic("No Listing with that ID in Storefront")
40+
self.listings.append(
41+
listing
42+
)
43+
salePrice = salePrice + listing.getDetails().salePrice
44+
}
45+
46+
// Borrow a provider reference to the buyers vault
47+
let provider = universalDucPayer.storage.borrow<auth(FungibleToken.Withdraw) &DapperUtilityCoin.Vault>(from: /storage/dapperUtilityCoinVault)
48+
?? panic("Cannot borrow DUC vault from buyer account storage")
49+
50+
// withdraw the purchase tokens from the vault
51+
self.paymentVault <- provider.withdraw(amount: salePrice)
52+
53+
// Access the buyer's NFT collection to store the purchased NFT.
54+
self.{{.NFTProductName}}Collection = buyer.capabilities.borrow<&{NonFungibleToken.CollectionPublic}>({{.NFTProductName}}.CollectionPublicPath)
55+
?? panic("Could not borrow Storefront from provided address")
56+
57+
}
58+
59+
execute {
60+
// Purchase the NFTs
61+
for listing in self.listings {
62+
let listingSalePrice: UFix64 = listing.getDetails().salePrice
63+
let listingPaymentVault <-self.paymentVault.withdraw(amount: listingSalePrice)
64+
let item <- listing.purchase(
65+
payment: <-listingPaymentVault
66+
)
67+
68+
self.{{.NFTProductName}}Collection.deposit(token: <-item)
69+
}
70+
destroy self.paymentVault
71+
}
72+
}

utils/parse_cadence_template.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"regexp"
7+
)
8+
9+
func getQuotedAddressRegexExpressionReplacer(contractName string) string {
10+
const regexPattern = `"(?:(?:\./|(?:\.\./)+)?(?:[a-zA-Z0-9_\-]+/)*%s(\.cdc)?)"`
11+
return fmt.Sprintf(regexPattern, contractName)
12+
}
13+
14+
func getRegexExpressionReplacer(contractName string) string {
15+
const regexPattern = `%s`
16+
return fmt.Sprintf(regexPattern, contractName)
17+
}
18+
19+
// ParseCadenceTemplateV3 parses the Cadence template and replaces placeholders
20+
func ParseCadenceTemplateV3(template []byte, data ...interface{}) ([]byte, error) {
21+
if err := validateStruct(data); err != nil {
22+
return nil, err
23+
}
24+
updatedTemplate, err := replacePlaceholders(string(template), data)
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
return []byte(updatedTemplate), nil
30+
}
31+
32+
// validateStruct ensures the provided data is a struct
33+
func validateStruct(data []interface{}) error {
34+
for _, item := range data {
35+
if item == nil {
36+
continue
37+
}
38+
if reflect.ValueOf(item).Kind() != reflect.Struct {
39+
return fmt.Errorf("data must be a struct")
40+
}
41+
}
42+
return nil
43+
}
44+
45+
// replacePlaceholders replaces the placeholders in the template with actual values
46+
func replacePlaceholders(template string, data []interface{}) (string, error) {
47+
for index, item := range data {
48+
if item == nil {
49+
continue
50+
}
51+
val := reflect.ValueOf(item)
52+
for i := 0; i < val.NumField(); i++ {
53+
field := val.Type().Field(i)
54+
fieldName := field.Name
55+
fieldValue := val.Field(i).String()
56+
if index == 0 {
57+
replacer := regexp.MustCompile(getQuotedAddressRegexExpressionReplacer(fieldName))
58+
template = replacer.ReplaceAllString(template, "0x"+fieldValue)
59+
} else {
60+
replacer := regexp.MustCompile(getRegexExpressionReplacer(fieldName))
61+
template = replacer.ReplaceAllString(template, fieldValue)
62+
}
63+
}
64+
65+
}
66+
67+
return template, nil
68+
}

0 commit comments

Comments
 (0)