Skip to content

Commit

Permalink
add createNftOnChain and refactor files and folders (#53)
Browse files Browse the repository at this point in the history
* add createNftOnChain and refactor files and folders

* use NetworkingRouter implementation for SolanaRouter

* Max supply for Master Edition test

* fix namespace issue and only retry on nilStatus

* update swift packages

Co-authored-by: Arturo Jamaica <me@arturojamaica.com>
  • Loading branch information
mike-metaplex and ajamaica authored Sep 13, 2022
1 parent 42686c8 commit fded5a8
Show file tree
Hide file tree
Showing 27 changed files with 1,055 additions and 198 deletions.
2 changes: 1 addition & 1 deletion Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"location" : "https://github.com/metaplex-foundation/Solana.Swift.git",
"state" : {
"branch" : "master",
"revision" : "75df095c2f4e2e75e5e8e6f6b0fcc9ee7388c68d"
"revision" : "9342eb1e96a0a8b78f4a38f255f1fcbfe0797383"
}
},
{
Expand Down
29 changes: 29 additions & 0 deletions Sources/Metaplex/Modules/NFTS/NftClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,33 @@ public class NftClient {
public func findNftsByOwner(publicKey: PublicKey, onComplete: @escaping (Result<[NFT?], OperationError>) -> Void) {
findAllByOwner(publicKey: publicKey, onComplete: onComplete)
}

public func createNft(
name: String,
symbol: String?,
uri: String,
sellerFeeBasisPoints: UInt16,
hasCreators: Bool,
addressCount: UInt32,
creators: [MetaplexCreator],
isMutable: Bool,
mintAccountState: AccountState,
account: Account,
onComplete: @escaping (Result<NFT, OperationError>) -> Void
) {
let input = CreateNftInput(
mintAccountState: mintAccountState,
account: account,
name: name,
symbol: symbol,
uri: uri,
sellerFeeBasisPoints: sellerFeeBasisPoints,
hasCreators: hasCreators,
addressCount: addressCount,
creators: creators,
isMutable: isMutable
)
let operation = CreateNftOnChainOperationHandler(metaplex: self.metaplex)
operation.handle(operation: CreateNftOperation.pure(.success(input))).run { onComplete($0) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// CreateNftOnChainOperationHandler.swift
//
//
// Created by Michael J. Huber Jr. on 9/2/22.
//

import Foundation
import Solana

public enum AccountState {
case new(Account)
case existing(Account)

var account: Account {
switch self {
case .new(let account):
return account
case .existing(let account):
return account
}
}
}

struct CreateNftInput {
let mintAccountState: AccountState
let account: Account
let name: String
let symbol: String?
let uri: String
let sellerFeeBasisPoints: UInt16
let hasCreators: Bool
let addressCount: UInt32
let creators: [MetaplexCreator]
let collection: MetaplexCollection? = nil
let uses: MetaplexUses? = nil
let isMutable: Bool
}

typealias CreateNftOperation = OperationResult<CreateNftInput, OperationError>

class CreateNftOnChainOperationHandler: OperationHandler {
var metaplex: Metaplex

typealias I = CreateNftInput
typealias O = NFT

init(metaplex: Metaplex) {
self.metaplex = metaplex
}

func handle(operation: CreateNftOperation) -> OperationResult<NFT, OperationError> {
let builder = InstructionBuilder(metaplex: metaplex)
return operation.flatMap { input in
OperationResult<[TransactionInstruction], Error>.init { callback in
builder.createNftInstructions(input: input) {
callback($0)
}
}.mapError {
OperationError.buildInstructionsError($0)
}.flatMap { instructions in
OperationResult<String, Error>.init { callback in
self.metaplex.connection.serializeTransaction(instructions: instructions, recentBlockhash: nil, signers: [input.account, input.mintAccountState.account]) {
callback($0)
}
}
.mapError { OperationError.serializeTransactionError($0) }
}.flatMap { serializedTransaction in
OperationResult<TransactionID, IdentityDriverError>.init { callback in
self.metaplex.sendTransaction(serializedTransaction: serializedTransaction) {
callback($0)
}
}
.mapError { OperationError.sendTransactionError($0) }
}.flatMap { signature in
let operation: () -> OperationResult<SignatureStatus, Retry<Error>> = {
OperationResult<SignatureStatus, Error>.init { callback in
// We are sleeping here in order to wait for the transaction to finalize on the chain.
#warning("This needs to be refactored into something more elegant.")
sleep(3)
self.metaplex.confirmTransaction(
signature: signature,
configs: nil
) { result in
switch result {
case .success(let signature):
guard let signature = signature, let status = signature.confirmationStatus, status == .finalized else {
callback(.failure(OperationError.nilSignatureStatus))
return
}
callback(.success(signature))
case .failure(let error):
callback(.failure(error))
}
}
}
.mapError { error in
if case OperationError.nilSignatureStatus = error {
return Retry.retry(error)
}
return Retry.doNotRetry(error)
}
}
let retry = OperationResult<SignatureStatus, Error>.retry(attempts: 5, operation: operation)
.mapError { OperationError.confirmTransactionError($0) }
return retry
}.flatMap { (status: SignatureStatus) -> OperationResult<NFT, OperationError> in
let findNft = FindNftByMintOnChainOperationHandler(metaplex: self.metaplex)
return findNft.handle(operation: FindNftByMintOperation.pure(.success(input.mintAccountState.account.publicKey)))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,63 +51,6 @@ public enum MetadataKey {
}
}

public class MasterEditionV1: BufferLayout {
public let supply: UInt64?
public let maxSupply: UInt64?
public let printingMint: PublicKey
public let oneTimePrintingAuthorizationMint: PublicKey

public static var BUFFER_LENGTH: UInt64 = 282

public init(supply: UInt64?, maxSupply: UInt64?, printingMint: PublicKey, oneTimePrintingAuthorizationMint: PublicKey) {
self.supply = supply
self.maxSupply = maxSupply
self.printingMint = printingMint
self.oneTimePrintingAuthorizationMint = oneTimePrintingAuthorizationMint
}

required public init(from reader: inout BinaryReader) throws {
self.supply = try? .init(from: &reader)
self.maxSupply = try? .init(from: &reader)
self.printingMint = try .init(from: &reader)
self.oneTimePrintingAuthorizationMint = try .init(from: &reader)
}

public func serialize(to writer: inout Data) throws {
try supply?.serialize(to: &writer)
try maxSupply?.serialize(to: &writer)
try printingMint.serialize(to: &writer)
try oneTimePrintingAuthorizationMint.serialize(to: &writer)
}
}

public class MasterEditionV2: BufferLayout {
public let supply: UInt64
public let maxSupply: UInt64?

public static var BUFFER_LENGTH: UInt64 = 282

public init(supply: UInt64, maxSupply: UInt64?) {
self.supply = supply
self.maxSupply = maxSupply
}

required public init(from reader: inout BinaryReader) throws {
self.supply = try .init(from: &reader)
self.maxSupply = try? .init(from: &reader)
}

public func serialize(to writer: inout Data) throws {
try supply.serialize(to: &writer)
try maxSupply?.serialize(to: &writer)
}
}

public enum MasterEditionVersion: Codable {
case masterEditionV1(MasterEditionV1)
case masterEditionV2(MasterEditionV2)
}

public class MasterEditionAccount: BufferLayout {

static func pda(mintKey: PublicKey) -> Result<PublicKey, Error> {
Expand Down
135 changes: 0 additions & 135 deletions Sources/Metaplex/Programs/TokenMetadata/Accounts/MetadataAccount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,7 @@
import Foundation
import Solana

extension PublicKey {

static let vaultProgramId = PublicKey(string: "vau1zxA2LbssAUEF7Gpw91zMM1LvXrvpzJtmZ58rPsn")!

static let auctionProgramId = PublicKey(string: "auctxRXPeJoc4817jDhf4HbjnhEcr1cCXenosMhK5R8")!

static let metaplexProgramId = PublicKey(string: "p1exdMJcjVao65QdewkaZRUnU6VPSXhus9n2GzWfh98")!

static let systemProgramID = PublicKey(string: "11111111111111111111111111111111")!
}

extension String {
static let metadataPrefix = "metadata"
}

public enum MetaplexTokenStandard: UInt8, Codable {
case NonFungible = 0, FungibleAsset = 1, Fungible = 2, NonFungibleEdition = 3
}

public struct MetadataAccount: BufferLayout {

static func pda(mintKey: PublicKey) -> Result<PublicKey, Error> {
let seedMetadata = [String.metadataPrefix.bytes,
TokenMetadataProgram.publicKey.bytes,
Expand Down Expand Up @@ -124,118 +104,3 @@ public struct MetadataAccount: BufferLayout {
}
}
}

public struct MetaplexCollection: BorshCodable, BufferLayout {
public static var BUFFER_LENGTH: UInt64 = 10

public let verified: Bool
public let key: PublicKey

public init(verified: Bool, key: PublicKey){
self.verified = verified
self.key = key
}

public init(from reader: inout BinaryReader) throws {
self.verified = try .init(from: &reader)
self.key = try .init(from: &reader)
}

public func serialize(to writer: inout Data) throws {
try verified.serialize(to: &writer)
try key.serialize(to: &writer)
}
}

public struct MetaplexData: BorshCodable, BufferLayout {
public static var BUFFER_LENGTH: UInt64 = 10

public init(name: String, symbol: String, uri: String, sellerFeeBasisPoints: UInt16, hasCreators: Bool, addressCount: UInt32, creators: [MetaplexCreator]) {
self._name = name
self._symbol = symbol
self._uri = uri
self.sellerFeeBasisPoints = sellerFeeBasisPoints
self.hasCreators = hasCreators
self.addressCount = addressCount
self.creators = creators
}

private let _name: String
private let _symbol: String
private let _uri: String

public var name: String {
_name.trimmingCharacters(in: CharacterSet(charactersIn: "\0").union(.whitespacesAndNewlines))
}
public var symbol: String {
_symbol.trimmingCharacters(in: CharacterSet(charactersIn: "\0").union(.whitespacesAndNewlines))
}
public var uri: String {
_uri.trimmingCharacters(in: CharacterSet(charactersIn: "\0").union(.whitespacesAndNewlines))
}

public let sellerFeeBasisPoints: UInt16
public let hasCreators: Bool
public let addressCount: UInt32
public let creators: [MetaplexCreator]

public init(from reader: inout BinaryReader) throws {
self._name = try .init(from: &reader)
self._symbol = try .init(from: &reader)
self._uri = try .init(from: &reader)
self.sellerFeeBasisPoints = try .init(from: &reader)
self.hasCreators = try .init(from: &reader)
var creatorsArray: [MetaplexCreator] = []
if self.hasCreators {
let addressCount: UInt32 = try .init(from: &reader)
for _ in 0..<addressCount {
let creator: MetaplexCreator = try .init(from: &reader)
creatorsArray.append(creator)
}

}
self.addressCount = UInt32(creatorsArray.count)
self.creators = creatorsArray
}

public func serialize(to writer: inout Data) throws {
try _name.serialize(to: &writer)
try _symbol.serialize(to: &writer)
try _uri.serialize(to: &writer)
try sellerFeeBasisPoints.serialize(to: &writer)
try hasCreators.serialize(to: &writer)
if hasCreators {
try UInt32(creators.count).serialize(to: &writer)
for creator in creators {
try creator.serialize(to: &writer)
}
}
}
}

public struct MetaplexCreator: BorshCodable, BufferLayout {
public static var BUFFER_LENGTH: UInt64 = 32 + 8 + 8

public init(address: PublicKey, verified: UInt8, share: UInt8) {
self.address = address
self.verified = verified
self.share = share
}

public let address: PublicKey
public let verified: UInt8

public let share: UInt8

public init(from reader: inout BinaryReader) throws {
self.address = try .init(from: &reader)
self.verified = try .init(from: &reader)
self.share = try .init(from: &reader)
}

public func serialize(to writer: inout Data) throws {
try address.serialize(to: &writer)
try verified.serialize(to: &writer)
try share.serialize(to: &writer)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// MetaplexCollection.swift
//
//
// Created by Michael J. Huber Jr. on 9/12/22.
//

import Foundation
import Solana

public struct MetaplexCollection: BorshCodable, BufferLayout {
public static var BUFFER_LENGTH: UInt64 = 10

public let verified: Bool
public let key: PublicKey

public init(verified: Bool, key: PublicKey){
self.verified = verified
self.key = key
}

public init(from reader: inout BinaryReader) throws {
self.verified = try .init(from: &reader)
self.key = try .init(from: &reader)
}

public func serialize(to writer: inout Data) throws {
try verified.serialize(to: &writer)
try key.serialize(to: &writer)
}
}
Loading

0 comments on commit fded5a8

Please sign in to comment.