-
Notifications
You must be signed in to change notification settings - Fork 87
Outdated create metadata instruction #47
Comments
do you have resolve it? @davidkakov111 |
No, in Python I don't, because I didn't find enough information about the correct instructions. However, I have solved the Solana NFT minting process in TypeScript. If you want, you can check it out here. It is currently working and much simpler then then it was in python (A few years ago :) ) : https://github.com/davidkakov111/SolanaMysteryBoxShop/tree/TS_NFT_API" |
Hi David, I have referred to your typescript code. In your function, Inputs contain private_key, It's difficult for me because I need to create an exchange of 1 NFT where sellers and buyers log in via Wallet and sellers need to create NFTs on my exchange and buyers will buy that NFT. So, how can users agree to give private_key to my website to make mint NFT transactions and NFT owner change transactions? I'm new to NFTs, please give me some guidance. Thank you
|
OK,Thank you very much |
I actually solved this last year for my company - essentially I did this: anchorpy client-gen ./idl.json ./src --program-id metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s This generated the client for me but did not fill out the correct function identifiers so I had to fix those manually: # example instruction
def create_metadata_account_v2(
args: CreateMetadataAccountV2Args, accounts: CreateMetadataAccountV2Accounts
) -> TransactionInstruction:
keys: list[AccountMeta] = [
AccountMeta(
pubkey=accounts["metadata_account"], is_signer=False, is_writable=True
),
AccountMeta(pubkey=accounts["mint"], is_signer=False, is_writable=False),
AccountMeta(
pubkey=accounts["mint_authority"], is_signer=True, is_writable=False
),
AccountMeta(pubkey=accounts["payer"], is_signer=True, is_writable=True),
AccountMeta(
pubkey=accounts["update_authority"], is_signer=True, is_writable=False
),
AccountMeta(
pubkey=accounts["system_program"], is_signer=False, is_writable=False
),
]
identifier = b"\x18I)\xed,\x8e\xc2\xfe"
encoded_args = layout.build(
{
"data": args["data"].to_encodable(),
"is_mutable": args["is_mutable"],
}
)
data = identifier + encoded_args
return TransactionInstruction(keys, PROGRAM_ID, data)
# Corrected Instruction
import borsh_construct as borsh
def get_identifier(discriminant: int) -> bytes:
layout = borsh.CStruct("number" / borsh.U8)
return layout.build({"number": discriminant})
def create_metadata_account_v2(
args: CreateMetadataAccountV2Args, accounts: CreateMetadataAccountV2Accounts
) -> TransactionInstruction:
keys: list[AccountMeta] = [
AccountMeta(
pubkey=accounts["metadata_account"], is_signer=False, is_writable=True
),
AccountMeta(pubkey=accounts["mint"], is_signer=False, is_writable=False),
AccountMeta(
pubkey=accounts["mint_authority"], is_signer=True, is_writable=False
),
AccountMeta(pubkey=accounts["payer"], is_signer=True, is_writable=True),
AccountMeta(
pubkey=accounts["update_authority"], is_signer=True, is_writable=False
),
AccountMeta(
pubkey=accounts["system_program"], is_signer=False, is_writable=False
),
]
identifier = get_identifier(16) # This number you get from the metaplex docs!
encoded_args = layout.build(
{
"data": args["data"].to_encodable(),
"is_mutable": args["is_mutable"],
}
)
data = identifier + encoded_args
return TransactionInstruction(keys, PROGRAM_ID, data) But to be honest I had to write many helpers and to make the whole flow work nicely. Would not recommend it. Will ask If I can open source the code. At then end I had a mint function like this one: import base64
from typing import List, Optional, Union
from solana.keypair import Keypair
from solana.publickey import PublicKey
from solana.rpc.async_api import AsyncClient
from solana.system_program import CreateAccountParams, create_account
from solana.transaction import Transaction
from spl.token._layouts import ACCOUNT_LAYOUT, MINT_LAYOUT
from spl.token.constants import TOKEN_PROGRAM_ID
from spl.token.instructions import (
InitializeMintParams,
MintToParams,
create_associated_token_account,
get_associated_token_address,
initialize_mint,
mint_to,
)
from app.internal.pymetaplex.TokenMetadata.instructions import (
CreateMasterEditionV3Accounts,
CreateMasterEditionV3Args,
CreateMetadataAccountV3Args,
UpdateMetadataAccountV2Accounts,
UpdateMetadataAccountV2Args,
VerifySizedCollectionItemAccounts,
create_master_edition_v3,
create_metadata_account_v3,
update_metadata_account_v2,
verify_sized_collection_item,
)
from app.internal.pymetaplex.TokenMetadata.types import (
Collection,
CollectionDetailsKind,
Creator,
DataV2,
Uses,
)
# from app.internal.pymetaplex.TokenMetadata.utils.edition import get_edition_account
from app.internal.pymetaplex.TokenMetadata.utils.execute import send_and_confirm
# from app.internal.pymetaplex.TokenMetadata.utils.metadata import get_metadata_account
#### These were in other files
from app.internal.pymetaplex.TokenMetadata.program_id import PROGRAM_ID
def get_metadata_account(mint_key: str) -> PublicKey:
mint_key = mint_key if isinstance(mint_key, PublicKey) else PublicKey(mint_key)
return PublicKey.find_program_address(
[b"metadata", bytes(PROGRAM_ID), bytes(mint_key)],
PROGRAM_ID,
)[0]
def get_edition_account(mint_key: str) -> PublicKey:
mint_key = mint_key if isinstance(mint_key, PublicKey) else PublicKey(mint_key)
return PublicKey.find_program_address(
[
b"metadata",
bytes(PROGRAM_ID),
bytes(PublicKey(mint_key)),
b"edition",
],
PROGRAM_ID,
)[0]
####
SYSTEM_PROGRAM_ID = PublicKey("11111111111111111111111111111111")
SYSVAR_RENT_PUBKEY = PublicKey("SysvarRent111111111111111111111111111111111")
async def mint(
client: AsyncClient,
source_account: Keypair,
destination_account: PublicKey,
name: str,
symbol: str,
uri: str,
seller_fee_basis_points: int,
creators: Optional[List[Creator]] = None,
uses: Optional[Uses] = None,
collection_details: Optional[CollectionDetailsKind] = None,
update_primary_sale_happened: bool = True,
collection_mint: Optional[PublicKey] = None,
verify_collection: Optional[bool] = True,
max_supply: Optional[int] = 0,
) -> Union[PublicKey, dict]:
"""This function is a helper function to mint nfts.
Args:
client (AsyncClient): AsyncClient from solana.rpc.async_api
source_account (Keypair): Payer, Update authority.
destination_account (PublicKey): Final NFT owner address.
name (str): name of nft on chain
symbol (str): symbol of nft on chain
uri (str): uri of nft on chain
seller_fee_basis_points (int): seller_fee_basis_points of nft on chain
creators (List[Creator]): List of on chain creators
uses (Uses, optional): Nft uses. For more info look at the metaplex docs. Defaults to None.
collection_details (CollectionDetailsKind, optional): Like CollectionDetailsKind(value=V1Value(size=0)). If provided it means this nft is the parent of a collection. Defaults to None.
collection_mint (PublicKey, optional): If provided the nft will point to collection_mint nft as his parent. Defaults to None.
update_primary_sale_happened (bool, optional): If false, 100% of the proceeds of a sale on the 2ndary market place will go to the creators. Defaults to True.
verify_collection (bool, optional): Only makes sense in combinatino with collection_mint. Defaults to True.
max_supply (int, optional): Number of editions that can be printed from Master Edition. Defaults to 0.
returns:
Tuple[str, str]
1. Pubic key of the mint account.
2. transaction
"""
# Preprocess Inputs
if collection_mint:
# collection can not be initialized as verified
collection = Collection(verified=False, key=collection_mint)
else:
collection = None
# init transaction
tx = Transaction()
# List non-derived accounts
mint_account = Keypair()
# List signers
signers = [source_account, mint_account]
### 1. Create Mint Account Instruction
# Get the minimum rent balance for a mint account
min_rent_reseponse = await client.get_minimum_balance_for_rent_exemption(
MINT_LAYOUT.sizeof()
)
lamports = min_rent_reseponse["result"]
# every token on solana needs an associated account
create_mint_account_ix = create_account(
CreateAccountParams(
from_pubkey=source_account.public_key,
new_account_pubkey=mint_account.public_key,
lamports=lamports,
space=MINT_LAYOUT.sizeof(),
program_id=TOKEN_PROGRAM_ID,
)
)
tx = tx.add(create_mint_account_ix)
### 2. here we create one spl token
initialize_mint_ix = initialize_mint(
InitializeMintParams(
decimals=0,
program_id=TOKEN_PROGRAM_ID,
mint=mint_account.public_key,
mint_authority=source_account.public_key,
freeze_authority=source_account.public_key,
)
)
tx = tx.add(initialize_mint_ix)
### 3. Get or Create Associated Token Account Instruction
# List signers
# signers = [source_account]
# Create Associated Token Account
associated_token_account = get_associated_token_address(
destination_account, mint_account.public_key
)
# Check if PDA is initialized. If not, create the account
associated_token_account_info = await client.get_account_info(
associated_token_account
)
account_info = associated_token_account_info["result"]["value"]
if account_info is not None:
account_state = ACCOUNT_LAYOUT.parse(
base64.b64decode(account_info["data"][0])
).state
else:
account_state = 0
if account_state == 0:
associated_token_account_ix = create_associated_token_account(
owner=destination_account,
payer=source_account.public_key, # signer
mint=mint_account.public_key,
)
tx = tx.add(associated_token_account_ix)
### 4. Create Metadata Account Instruction
data = DataV2(
name=name,
symbol=symbol,
uri=uri,
seller_fee_basis_points=seller_fee_basis_points,
creators=creators,
collection=collection,
uses=uses,
)
metadata_args = CreateMetadataAccountV3Args(
data=data, is_mutable=True, collection_details=collection_details
)
metadata_account = PublicKey(get_metadata_account(mint_account.public_key))
metadata_accounts = CreateMasterEditionV3Accounts(
metadata_account=metadata_account,
mint=mint_account.public_key,
mint_authority=source_account.public_key,
payer=source_account.public_key,
update_authority=source_account.public_key,
system_program=SYSTEM_PROGRAM_ID,
rent=SYSVAR_RENT_PUBKEY,
)
create_metadata_ix = create_metadata_account_v3(metadata_args, metadata_accounts)
tx = tx.add(create_metadata_ix)
### 5. Mint one token
# Mint NFT to the associated token account
mint_to_ix = mint_to(
MintToParams(
program_id=TOKEN_PROGRAM_ID,
mint=mint_account.public_key,
dest=associated_token_account,
mint_authority=source_account.public_key,
amount=1,
signers=[source_account.public_key],
)
)
tx = tx.add(mint_to_ix)
### 6. Set primary sale happened to True
if update_primary_sale_happened:
update_metadata_args = UpdateMetadataAccountV2Args(
data=None,
update_authority=None,
primary_sale_happened=True,
is_mutable=None,
)
update_metadata_accounts = UpdateMetadataAccountV2Accounts(
metadata_account=metadata_account,
update_authority=source_account.public_key,
)
update_metadata_tx = update_metadata_account_v2(
args=update_metadata_args, accounts=update_metadata_accounts
)
tx = tx.add(update_metadata_tx)
### 7. Create Master Edition Instruction
master_edition_args = CreateMasterEditionV3Args(max_supply=max_supply)
edition_account = get_edition_account(mint_account.public_key)
master_edition_accounts = CreateMasterEditionV3Accounts(
edition=edition_account,
mint=mint_account.public_key,
update_authority=source_account.public_key,
mint_authority=source_account.public_key,
payer=source_account.public_key,
metadata=metadata_account,
token_program=TOKEN_PROGRAM_ID,
system_program=SYSTEM_PROGRAM_ID,
rent=SYSVAR_RENT_PUBKEY,
)
create_master_edition_ix = create_master_edition_v3(
master_edition_args, master_edition_accounts
)
tx = tx.add(create_master_edition_ix)
### 8. Verify Collection
if collection is not None and verify_collection:
collection_metadata = get_metadata_account(collection_mint)
collection_master_edition_account = get_edition_account(collection_mint)
verify_collection_accounts = VerifySizedCollectionItemAccounts(
metadata=metadata_account,
collection_authority=source_account.public_key,
payer=source_account.public_key,
collection_mint=collection_mint,
collection=collection_metadata,
collection_master_edition_account=collection_master_edition_account,
collection_authority_record=source_account.public_key,
)
verify_collection_ix = verify_sized_collection_item(
accounts=verify_collection_accounts
)
tx = tx.add(verify_collection_ix)
### 9. Execute Transaction
result = await send_and_confirm(client, tx, signers)
return mint_account.public_key, result["result"] |
Hi @cuongnguyengit, In your task, it seems you need to follow the Solana documentation to use WalletConnect for user management and so on. (This is a very good and very fast solution for NFT minting with Solana Pay and WalletConnect, which may be what you need: https://github.com/solana-developers/solana-pay-nft-minter) I didn't use WalletConnect in my webshop because my goal was to simplify the payment process with a unique method and avoid any data transfer between the webshop and the user to prevent common phishing risks. For this function, I pass my private key from my main Django webshop; this Next.js project is just a "branch" of the web3 webshop. (Your users should never give their private key to you; it is illegal.) |
Hi @davidkakov111, I have gained more knowledge about Solana and NFTs. |
I also don't have deep knowledge in this field, but I'll try to provide an answer: |
Problem:
The Metaplex Python API experiences a deployment issue where an outdated instruction, specifically the create_metadata_instruction within the transaction.py file, leads to a contract key with value = None during Solana smart contract deployment.
Details:
File: transaction.py
Specific Issue: create_metadata_instruction sends outdated instructions to the Token Metadata Program.
Steps to Reproduce:
Deploy a Solana smart contract with the Metaplex Python API.
Observe that the outdated instruction results in a contract key without a value.
Expected Behavior:
The Metaplex Python API should send current instructions to the Token Metadata Program to ensure successful contract deployment.
The text was updated successfully, but these errors were encountered: