diff --git a/api/metaplex_api.py b/api/metaplex_api.py index 2bee099..d1fa616 100644 --- a/api/metaplex_api.py +++ b/api/metaplex_api.py @@ -2,7 +2,16 @@ from cryptography.fernet import Fernet import base58 from solana.keypair import Keypair -from metaplex.transactions import deploy, topup, mint, send, burn, update_token_metadata +from metaplex.transactions import ( + deploy, + topup, + mint, + send, + burn, + update_token_metadata, + creators_sign, + update_primary_sale_happened +) from utils.execution_engine import execute class MetaplexAPI(): @@ -132,7 +141,55 @@ def send(self, api_endpoint, contract_key, sender_key, dest_key, encrypted_priva return json.dumps(resp) except: return json.dumps({"status": 400}) - + + def creators_sign(self, api_endpoint, contract_key, encrypted_private_key, max_retries=3, skip_confirmation=False, max_timeout=60, target=20, finalized=True): + """ + Unverified creators to sign minted tokens. requires the unverfied creators private keys. + May require a private key, if so this will be provided encrypted using Fernet: https://cryptography.io/en/latest/fernet/ + Return a status flag of success or fail and the native transaction data. + """ + try: + private_key = list(self.cipher.decrypt(encrypted_private_key)) + tx, signers = creators_sign(api_endpoint, contract_key, private_key) + resp = execute( + api_endpoint, + tx, + signers, + max_retries=max_retries, + skip_confirmation=skip_confirmation, + max_timeout=max_timeout, + target=target, + finalized=finalized, + ) + resp["status"] = 200 + return json.dumps(resp) + except: + return json.dumps({"status": 400}) + + def update_primary_sale_happened(self, api_endpoint, contract_key, encrypted_private_key, max_retries=3, skip_confirmation=False, max_timeout=60, target=20, finalized=True): + """ + Updates primary sale happend to secondary sale. requires the owner's private keys + May require a private key, if so this will be provided encrypted using Fernet: https://cryptography.io/en/latest/fernet/ + Return a status flag of success or fail and the native transaction data. + """ + try: + private_key = list(self.cipher.decrypt(encrypted_private_key)) + tx, signers = update_primary_sale_happened(api_endpoint, contract_key, private_key) + resp = execute( + api_endpoint, + tx, + signers, + max_retries=max_retries, + skip_confirmation=skip_confirmation, + max_timeout=max_timeout, + target=target, + finalized=finalized, + ) + resp["status"] = 200 + return json.dumps(resp) + except: + return json.dumps({"status": 400}) + def burn(self, api_endpoint, contract_key, owner_key, encrypted_private_key, max_retries=3, skip_confirmation=False, max_timeout=60, target=20, finalized=True): """ Burn a token, permanently removing it from the blockchain. diff --git a/metaplex/metadata.py b/metaplex/metadata.py index 5187dc6..f4e4271 100644 --- a/metaplex/metadata.py +++ b/metaplex/metadata.py @@ -13,9 +13,12 @@ MAX_URI_LENGTH = 200 MAX_CREATOR_LENGTH = 34 MAX_CREATOR_LIMIT = 5 + class InstructionType(IntEnum): CREATE_METADATA = 0 UPDATE_METADATA = 1 + PRIMARY_SALE_HAPPENED = 4 + SIGN_METADATA = 7 METADATA_PROGRAM_ID = PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s') SYSTEM_PROGRAM_ID = PublicKey('11111111111111111111111111111111') @@ -108,7 +111,6 @@ def create_metadata_instruction_data(name, symbol, fee, creators): def create_metadata_instruction(data, update_authority, mint_key, mint_authority_key, payer): metadata_account = get_metadata_account(mint_key) - print(metadata_account) keys = [ AccountMeta(pubkey=metadata_account, is_signer=False, is_writable=True), AccountMeta(pubkey=mint_key, is_signer=False, is_writable=False), @@ -204,6 +206,40 @@ def update_metadata_instruction(data, update_authority, mint_key): ] return TransactionInstruction(keys=keys, program_id=METADATA_PROGRAM_ID, data=data) +def update_primary_sale_happened_instruction(update_authority, mint_key, token): + data_struct = cStruct( + "instruction_type" / Int8ul + ) + data = data_struct.build( + dict( + instruction_type=InstructionType.PRIMARY_SALE_HAPPENED + ) + ) + metadata_account = get_metadata_account(mint_key) + keys = [ + AccountMeta(pubkey=metadata_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=update_authority, is_signer=True, is_writable=False), + AccountMeta(pubkey=token, is_signer=False, is_writable=False), + ] + return TransactionInstruction(keys=keys, program_id=METADATA_PROGRAM_ID, data=data) + +def creators_sign_metadata_instruction(mint_key, creator): + data_struct = cStruct( + "instruction_type" / Int8ul + ) + data = data_struct.build( + dict( + instruction_type=InstructionType.SIGN_METADATA + ) + ) + metadata_account = get_metadata_account(mint_key) + keys = [ + AccountMeta(pubkey=metadata_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=creator, is_signer=True, is_writable=False), + ] + return TransactionInstruction(keys=keys, program_id=METADATA_PROGRAM_ID, data=data) + + def create_master_edition_instruction( mint: PublicKey, update_authority: PublicKey, diff --git a/metaplex/transactions.py b/metaplex/transactions.py index 4718a16..136f4a6 100644 --- a/metaplex/transactions.py +++ b/metaplex/transactions.py @@ -22,6 +22,8 @@ update_metadata_instruction, ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID, + update_primary_sale_happened_instruction, + creators_sign_metadata_instruction, ) @@ -263,6 +265,58 @@ def send(api_endpoint, source_account, contract_key, sender_key, dest_key, priva return tx, signers +def creators_sign(api_endpoint, contract_key, creator_private_key): + """ + Sign the token by unverified creators + Requires to be run by they keypair of the creator that are unverified + Return a status flag of success or fail and the native transaction data. + """ + # Initialize Client + client = Client(api_endpoint) + # List signers + creator_wallet_key = Keypair(creator_private_key) + signers = [creator_wallet_key] + # List accounts + creator_account = PublicKey(creator_wallet_key.public_address) + mint_account = PublicKey(contract_key) + # Start transaction + tx = Transaction() + # Create instruction and add + creators_sign_metadata_ix = creators_sign_metadata_instruction( + mint_key=mint_pubkey, + creator=creator_pubkey + ) + txn = txn.add(creators_sign_metadata_ix) + + return tx, signers + +def update_primary_sale_happened(api_endpoint, contract_key, owner_private_key): + """ + Updates primary sale happened + Return a status flag of success or fail and the native transaction data. + """ + # Initialize Client + client = Client(api_endpoint) + # List signers + owner_wallet_key = Keypair(owner_private_key) + signers = [owner_wallet_key] + # List accounts + owner_account = PublicKey(owner_private_key.public_address) + mint_account = PublicKey(contract_key) + + # Create instruction and add + associated_token_account = get_associated_token_address(owner_account, mint_account) + + update_primary_sale_happened_ix = update_primary_sale_happened_instruction( + update_authority=owner_account, + mint_key=mint_account, + token=associated_token_account + ) + + tx = tx.add(update_primary_sale_happened_ix) + + return tx, signers + def burn(api_endpoint, contract_key, owner_key, private_key): """ Burn a token, permanently removing it from the blockchain.