Repository: metaplex-foundation/python-api
Branch: main
Commit: 441c2ba9be76
Files: 10
Total size: 44.4 KB
Directory structure:
gitextract_hzgxmx41/
├── README.md
├── api/
│ ├── __init__.py
│ └── metaplex_api.py
├── api.py
├── metaplex/
│ ├── __init__.py
│ ├── metadata.py
│ └── transactions.py
├── requirements.txt
├── test/
│ └── test_api.py
└── utils/
└── execution_engine.py
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
# Metaplex Python API
This modules allows you to create, mint, transfer and burn NFTs on the Solana blockchain using Python.
## Setup
First, clone down the repository
Create a virtual environment and install the dependencies in `requirements.txt`. Be sure to use a version of Python >= 3.6
```bash
python3 -m virtualenv venv
source venv/bin/activate
pip install -r requirements.txt
```
At this point, you should be good to go.
## Usage
To create a `MetaplexAPI` object, you need to pass a dicitonary to the constructor with the following keys:
```python
cfg = {
"PRIVATE_KEY": YOUR_PRIVATE_KEY,
"PUBLIC_KEY": YOUR_PUBLIC_KEY,
"DECRYPTION_KEY": SERVER_DECRYPTION_KEY
}
api = MetaplexAPI(cfg)
```
The keypair that is passed into the `MetaplexAPI` serves as the fee payer for all network transactions (creating new wallets, minting tokens, transferring tokens, etc). Both keys are base58 encoded.
The decryption key ensures that messages sent to a server that utilizes this API will not be receiving unencrypted private keys over the wire. These private keys are necessary for signing transactions. The client can encrypt their data by using the same decryption key as the server. This is the syntax:
```python
from cryptography.fernet import Fernet
cipher = Fernet(DECRYPTION_KEY) # This is the same key that the server has
encrypted_key = cipher.encrypt(SECRET)
# Send `encrypted_key` to the downstream server to process
```
## Methods
This section will go through the following story (if you look at the code snippets) and invoke each of the methods in the API along the way:
1) Account `A` is created and airdropped 10 SOL
2) `A` creates an NFT `N`
3) Account `B` is created
4) `A` pays `B`'s rent fee
5) `N` is minted to `B` associated token account
6) Account `C` is created
7) `A` pays `C`'s rent fee
8) `B` transfers `N` to `C`
9) `C` destroys `N`
Let's get started:
```bash
$ python3 -i -m api.metaplex_api
>>>
```
### deploy
`deploy` will create a new NFT token by
1) Creating a new account from a randomly generated address (invokes `CreateAccount` from the System Program)
2) Invoking `InitializeMint` on the new account
3) Initializing the metadata for this account by invoking the `CreateMetatdata` instruction from the Metaplex protocol
Args:
`api_endpoint`: (str) The RPC endpoint to connect the network. ([devnet](https://api.devnet.solana.com/), [mainnet](https://api.mainnet-beta.solana.com/))
`name`: (str) Name of the NFT Contract (32 bytes max)
`symbol`: (str) Symbol of the NFT Contract (10 bytes max)
`skip_confirmation=True`: A flag that tells you to wait for the transaction to be confirmed if set to `False` (only used for testing, because it requires synchronized/sequential calls)
Example:
```python
>>> account = KeyPair()
>>> cfg = {"PRIVATE_KEY": base58.b58encode(account.seed).decode("ascii"), "PUBLIC_KEY": str(account.public_key), "DECRYPTION_KEY": Fernet.generate_key().decode("ascii")}
>>> api_endpoint = "https://api.devnet.solana.com/"
>>> Client(api_endpoint).request_airdrop(account.public_key, int(1e10))
{'jsonrpc': '2.0', 'result': '4ojKmAAesmKtqJkNLRtEjdgg4CkmowuTAjRSpp3K36UvQQvEXwhirV85E8cvWYAD42c3UyFdCtzydMgWokH2mbM', 'id': 1}
>>> metaplex_api = MetaplexAPI(cfg)
>>> seller_basis_fees = 0 # value in 10000
>>> metaplex_api.deploy(api_endpoint, "A"*32, "A"*10, seller_basis_fees)
'{"status": 200, "contract": "7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG", "msg": "Successfully created mint 7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG", "tx": "2qmiWoVi2PNeAjppe2cNbY32zZCJLXMYgdS1zRVFiKJUHE41T5b1WfaZtR2QdFJUXadrqrjbkpwRN5aG2J3KQrQx"}'
>>>
```
Note that when sending SOL to the newly generated account, that account will serve as the fee payer. You can check out [this transaction on the Solana Block Exporer]( https://explorer.solana.com/tx/2qmiWoVi2PNeAjppe2cNbY32zZCJLXMYgdS1zRVFiKJUHE41T5b1WfaZtR2QdFJUXadrqrjbkpwRN5aG2J3KQrQx?cluster=devnet).
### wallet
`wallet` creates a new random public/private keypair
```python
>>> metaplex_api.wallet()
'{"address": "VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh", "private_key": [95, 46, 174, 145, 248, 101, 108, 111, 128, 44, 41, 212, 118, 145, 42, 242, 84, 6, 31, 115, 18, 126, 47, 230, 103, 202, 46, 7, 194, 149, 42, 213]}'
>>>
```
No network calls are made here
### topup
`topup` sends a small amount of SOL to the destination account by invoking `Transfer` from the System Program
Args:
`api_endpoint`: (str) The RPC endpoint to connect the network. ([devnet](https://api.devnet.solana.com/), [mainnet](https://api.mainnet-beta.solana.com/))
`to`: (str) The base58 encoded public key of the destination address
`amount`: (Union[int, None]) This is the number of lamports to send to the destination address. If `None` (default), then the minimum rent exemption balance is transferred.
```python
>>> metaplex_api.topup(api_endpoint, "VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh")
'{"status": 200, "msg": "Successfully sent 0.00203928 SOL to VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh", "tx": "32Dk647Fb6aKJyErVfxgtSfC4xbssoJprcB7BEmEAdYTFK96M5VEQ1z62QxCCC7tAPF1g9TNvMehoGNudLNaKTWE"}'
>>>
```
[tx link](https://explorer.solana.com/tx/32Dk647Fb6aKJyErVfxgtSfC4xbssoJprcB7BEmEAdYTFK96M5VEQ1z62QxCCC7tAPF1g9TNvMehoGNudLNaKTWE?cluster=devnet)
### mint
`mint` will mint a token to a designated user account by
1) Fetching or creating an AssociatedTokenAccount from a Program Derived Address
2) Invoking `MintTo` with the AssociatedTokenAccount as the destination
3) Invoking the `UpdateMetadata` instruction from the Metaplex protocol to update the `uri` of the contract (containing the actual content)
Args:
`api_endpoint`: (str) The RPC endpoint to connect the network. ([devnet](https://api.devnet.solana.com/), [mainnet](https://api.mainnet-beta.solana.com/))
`contract_key`: (str) The base58 encoded public key of the mint address
`dest_key`: (str) The base58 encoded public key of the destinaion address (where the contract will be minted)
`link`: (str) The link to the content of the the NFT
```
>>> metaplex_api.mint(api_endpoint, "7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG", "VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh", "https://arweave.net/1eH7bZS-6HZH4YOc8T_tGp2Rq25dlhclXJkoa6U55mM/")
'{"status": 200, "msg": "Successfully minted 1 token to DkrGGuqn183rNyYHQNo9NSDYKZB8FVsaPBGn3F6nG7iH", "tx": "5r4qY1LudNg49FXyduadoAm83cJDWVeypUX6dsGs91RJqSxzU5qTt9WXfXs3Lzs5ZGQsTDTRpDyiXorv1wCzrzsJ"}'
>>>
```
[tx link](https://explorer.solana.com/tx/5r4qY1LudNg49FXyduadoAm83cJDWVeypUX6dsGs91RJqSxzU5qTt9WXfXs3Lzs5ZGQsTDTRpDyiXorv1wCzrzsJ?cluster=devnet)
### send
`send` will send a token from one user account to another user account
1) Fetching the AssociatedTokenAccount from a Program Derived Address for the sender
2) Fetching or creatign the AssociatedTokenAccount from a Program Derived Address for the receiver
3) Invoking `Transfer` (from the Token Program) with the receiver's AssociatedTokenAccount as the destination
Args:
`api_endpoint`: (str) The RPC endpoint to connect the network. ([devnet](https://api.devnet.solana.com/), [mainnet](https://api.mainnet-beta.solana.com/))
`contract_key`: (str) The base58 encoded public key of the mint address\
`sender_key`: (str) The base58 encoded public key of the source address (from which the contract will be transferred)
`dest_key`: (str) The base58 encoded public key of the destinaion address (to where the contract will be transferred)
`encrypted_private_key`: (bytes) The encrypted private key of the sender
```python
>>> metaplex_api.wallet()
'{"address": "EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8", "private_key": [172, 155, 209, 75, 226, 68, 91, 22, 199, 75, 148, 197, 143, 10, 211, 67, 5, 160, 101, 15, 139, 33, 208, 65, 59, 198, 5, 41, 167, 206, 85, 83]}'
>>> metaplex_api.topup(api_endpoint, "EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8")
'{"status": 200, "msg": "Successfully sent 0.00203928 SOL to EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8", "tx": "4erc1aPC8fSNV1kb41mgUSgMKMHhd8FdDd4gqFPQ9TmmS48QcaAi9zpNjzMG3UNr1dDw1mBxThZCgJyUPchiV3Jz"}'
>>> encrypted_key = metaplex_api.cipher.encrypt(bytes([95, 46, 174, 145, 248, 101, 108, 111, 128, 44, 41, 212, 118, 145, 42, 242, 84, 6, 31, 115, 18, 126, 47, 230, 103, 202, 46, 7, 194, 149, 42, 213]))
>>> metaplex_api.send(api_endpoint, "7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG", "VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh", "EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8", encrypted_key)
'{"status": 200, "msg": "Successfully transfered token from VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh to EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8", "tx": "3ZsGcCfjUXviToSB4U6Wg1W1W4rm8bMT7wF8zfauTciK6PdszpLqcvmmYqqrz8mRGK8pQPABVewCk8EdsvNVhzp6"}'
```
[tx link](https://explorer.solana.com/tx/3ZsGcCfjUXviToSB4U6Wg1W1W4rm8bMT7wF8zfauTciK6PdszpLqcvmmYqqrz8mRGK8pQPABVewCk8EdsvNVhzp6?cluster=devnet)
### burn
`burn` will remove a token from the blockchain
1) Fetching the AssociatedTokenAccount from a Program Derived Address for the owner
3) Invoking `Burn` (from the Token Program) with the owner's AssociatedTokenAccount as the destination
Args:
`api_endpoint`: (str) The RPC endpoint to connect the network. (devnet: https://api.devnet.solana.com/, mainnet: https://api.mainnet-beta.solana.com/)
`contract_key`: (str) The base58 encoded public key of the mint address
`owner_key`: (str) The base58 encoded public key of the owner address
`encrypted_private_key`: (bytes) The encrypted private key of the owner
```
>>> encrypted_key = metaplex_api.cipher.encrypt(bytes([172, 155, 209, 75, 226, 68, 91, 22, 199, 75, 148, 197, 143, 10, 211, 67, 5, 160, 101, 15, 139, 33, 208, 65, 59, 198, 5, 41, 167, 206, 85, 83]))
>>> metaplex_api.burn(api_endpoint, "7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG", "EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8", encrypted_key)
'{"status": 200, "msg": "Successfully burned token 7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG on EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8", "tx": "5kd5g4mNBSjoTVYwAasWZx6iB8ijaELfBukKrNYBeDvLomK7iTqFH1R29yniEGcfajakDxsqmYCDgDvukihRyZeZ"}'
>>>
```
https://explorer.solana.com/tx/5kd5g4mNBSjoTVYwAasWZx6iB8ijaELfBukKrNYBeDvLomK7iTqFH1R29yniEGcfajakDxsqmYCDgDvukihRyZeZ?cluster=devnet
### Full Example Code:
This is the sequential code from the previous section. These accounts will need to change if you want to do your own test.
```
account = KeyPair()
cfg = {"PRIVATE_KEY": base58.b58encode(account.seed).decode("ascii"), "PUBLIC_KEY": str(account.public_key), "DECRYPTION_KEY": Fernet.generate_key().decode("ascii")}
api_endpoint = "https://api.devnet.solana.com/"
Client(api_endpoint).request_airdrop(account.public_key, int(1e10))
# Create API
metaplex_api = MetaplexAPI(cfg)
# Deploy
metaplex_api.deploy(api_endpoint, "A"*32, "A"*10, 0)
# Topup VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh
metaplex_api.topup(api_endpoint, "VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh")
# Mint
metaplex_api.mint(api_endpoint, "7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG", "VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh", "https://arweave.net/1eH7bZS-6HZH4YOc8T_tGp2Rq25dlhclXJkoa6U55mM/")
# Topup EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8
metaplex_api.topup(api_endpoint, "EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8")
# Send
encrypted_key = metaplex_api.cipher.encrypt(bytes([95, 46, 174, 145, 248, 101, 108, 111, 128, 44, 41, 212, 118, 145, 42, 242, 84, 6, 31, 115, 18, 126, 47, 230, 103, 202, 46, 7, 194, 149, 42, 213]))
metaplex_api.send(api_endpoint, "7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG", "VtdBygLSt1EJF5M3nUk5CRxuNNTyZFUsKJ4yUVcC6hh", "EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8", encrypted_key)
# Burn
encrypted_key = metaplex_api.cipher.encrypt(bytes([172, 155, 209, 75, 226, 68, 91, 22, 199, 75, 148, 197, 143, 10, 211, 67, 5, 160, 101, 15, 139, 33, 208, 65, 59, 198, 5, 41, 167, 206, 85, 83]))
metaplex_api.burn(api_endpoint, "7bxe7t1aGdum8o97bkuFeeBTcbARaBn9Gbv5sBd9DZPG", "EnMb6ZntX43PFeX2NLcV4dtLhqsxF9hUr3tgF1Cwpwu8", encrypted_key)
```
================================================
FILE: api/__init__.py
================================================
================================================
FILE: api/metaplex_api.py
================================================
import json
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 utils.execution_engine import execute
class MetaplexAPI():
def __init__(self, cfg):
self.private_key = list(base58.b58decode(cfg["PRIVATE_KEY"]))[:32]
self.public_key = cfg["PUBLIC_KEY"]
self.keypair = Keypair(self.private_key)
self.cipher = Fernet(cfg["DECRYPTION_KEY"])
def wallet(self):
""" Generate a wallet and return the address and private key. """
keypair = Keypair()
pub_key = keypair.public_key
private_key = list(keypair.seed)
return json.dumps(
{
'address': str(pub_key),
'private_key': private_key
}
)
def deploy(self, api_endpoint, name, symbol, fees, max_retries=3, skip_confirmation=False, max_timeout=60, target=20, finalized=True):
"""
Deploy a contract to the blockchain (on network that support contracts). Takes the network ID and contract name, plus initialisers of name and symbol. Process may vary significantly between blockchains.
Returns status code of success or fail, the contract address, and the native transaction data.
"""
try:
tx, signers, contract = deploy(api_endpoint, self.keypair, name, symbol, fees)
print(contract)
resp = execute(
api_endpoint,
tx,
signers,
max_retries=max_retries,
skip_confirmation=skip_confirmation,
max_timeout=max_timeout,
target=target,
finalized=finalized,
)
resp["contract"] = contract
resp["status"] = 200
return json.dumps(resp)
except:
return json.dumps({"status": 400})
def topup(self, api_endpoint, to, amount=None, max_retries=3, skip_confirmation=False, max_timeout=60, target=20, finalized=True):
"""
Send a small amount of native currency to the specified wallet to handle gas fees. Return a status flag of success or fail and the native transaction data.
"""
try:
tx, signers = topup(api_endpoint, self.keypair, to, amount=amount)
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 mint(self, api_endpoint, contract_key, dest_key, link, max_retries=3, skip_confirmation=False, max_timeout=60, target=20, finalized=True, supply=1 ):
"""
Mints an NFT to an account, updates the metadata and creates a master edition
"""
tx, signers = mint(api_endpoint, self.keypair, contract_key, dest_key, link, supply=supply)
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_token_metadata(self, api_endpoint, mint_token_id, link, data, creators_addresses, creators_verified, creators_share,fee, max_retries=3, skip_confirmation=False, max_timeout=60, target=20, finalized=True, supply=1 ):
"""
Updates the json metadata for a given mint token id.
"""
tx, signers = update_token_metadata(api_endpoint, self.keypair, mint_token_id, link, data, fee, creators_addresses, creators_verified, creators_share)
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)
def send(self, api_endpoint, contract_key, sender_key, dest_key, encrypted_private_key, max_retries=3, skip_confirmation=False, max_timeout=60, target=20, finalized=True):
"""
Transfer a token on a given network and contract from the sender to the recipient.
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 = send(api_endpoint, self.keypair, contract_key, sender_key, dest_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.
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 = burn(api_endpoint, contract_key, owner_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})
================================================
FILE: api.py
================================================
from api.metaplex_api import MetaplexAPI
================================================
FILE: metaplex/__init__.py
================================================
================================================
FILE: metaplex/metadata.py
================================================
from typing import Union
import struct
from enum import IntEnum
from construct import Bytes, Flag, Int8ul
from construct import Struct as cStruct # type: ignore
from solana.publickey import PublicKey
from solana.transaction import AccountMeta, TransactionInstruction
import base58
import base64
MAX_NAME_LENGTH = 32
MAX_SYMBOL_LENGTH = 10
MAX_URI_LENGTH = 200
MAX_CREATOR_LENGTH = 34
MAX_CREATOR_LIMIT = 5
class InstructionType(IntEnum):
CREATE_METADATA = 0
UPDATE_METADATA = 1
METADATA_PROGRAM_ID = PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s')
SYSTEM_PROGRAM_ID = PublicKey('11111111111111111111111111111111')
SYSVAR_RENT_PUBKEY = PublicKey('SysvarRent111111111111111111111111111111111')
ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL')
TOKEN_PROGRAM_ID = PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
def get_metadata_account(mint_key):
return PublicKey.find_program_address(
[b'metadata', bytes(METADATA_PROGRAM_ID), bytes(PublicKey(mint_key))],
METADATA_PROGRAM_ID
)[0]
def get_edition(mint_key):
return PublicKey.find_program_address(
[b'metadata', bytes(METADATA_PROGRAM_ID), bytes(PublicKey(mint_key)), b"edition"],
METADATA_PROGRAM_ID
)[0]
def create_associated_token_account_instruction(associated_token_account, payer, wallet_address, token_mint_address):
keys = [
AccountMeta(pubkey=payer, is_signer=True, is_writable=True),
AccountMeta(pubkey=associated_token_account, is_signer=False, is_writable=True),
AccountMeta(pubkey=wallet_address, is_signer=False, is_writable=False),
AccountMeta(pubkey=token_mint_address, is_signer=False, is_writable=False),
AccountMeta(pubkey=SYSTEM_PROGRAM_ID, is_signer=False, is_writable=False),
AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False),
AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False),
]
return TransactionInstruction(keys=keys, program_id=ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID)
def _get_data_buffer(name, symbol, uri, fee, creators, verified=None, share=None):
if isinstance(share, list):
assert(len(share) == len(creators))
if isinstance(verified, list):
assert(len(verified) == len(creators))
args = [
len(name),
*list(name.encode()),
len(symbol),
*list(symbol.encode()),
len(uri),
*list(uri.encode()),
fee,
]
byte_fmt = "<"
byte_fmt += "I" + "B"*len(name)
byte_fmt += "I" + "B"*len(symbol)
byte_fmt += "I" + "B"*len(uri)
byte_fmt += "h"
byte_fmt += "B"
if creators:
args.append(1)
byte_fmt += "I"
args.append(len(creators))
for i, creator in enumerate(creators):
byte_fmt += "B"*32 + "B" + "B"
args.extend(list(base58.b58decode(creator)))
if isinstance(verified, list):
args.append(verified[i])
else:
args.append(1)
if isinstance(share, list):
args.append(share[i])
else:
args.append(100)
else:
args.append(0)
buffer = struct.pack(byte_fmt, *args)
return buffer
def create_metadata_instruction_data(name, symbol, fee, creators):
_data = _get_data_buffer(name, symbol, " "*64, fee, creators)
metadata_args_layout = cStruct(
"data" / Bytes(len(_data)),
"is_mutable" / Flag,
)
_create_metadata_args = dict(data=_data, is_mutable=True)
instruction_layout = cStruct(
"instruction_type" / Int8ul,
"args" / metadata_args_layout,
)
return instruction_layout.build(
dict(
instruction_type=InstructionType.CREATE_METADATA,
args=_create_metadata_args,
)
)
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),
AccountMeta(pubkey=mint_authority_key, is_signer=True, is_writable=False),
AccountMeta(pubkey=payer, is_signer=True, is_writable=False),
AccountMeta(pubkey=update_authority, is_signer=False, is_writable=False),
AccountMeta(pubkey=SYSTEM_PROGRAM_ID, is_signer=False, is_writable=False),
AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False),
]
return TransactionInstruction(keys=keys, program_id=METADATA_PROGRAM_ID, data=data)
def unpack_metadata_account(data):
assert(data[0] == 4)
i = 1
source_account = base58.b58encode(bytes(struct.unpack('<' + "B"*32, data[i:i+32])))
i += 32
mint_account = base58.b58encode(bytes(struct.unpack('<' + "B"*32, data[i:i+32])))
i += 32
name_len = struct.unpack('<I', data[i:i+4])[0]
i += 4
name = struct.unpack('<' + "B"*name_len, data[i:i+name_len])
i += name_len
symbol_len = struct.unpack('<I', data[i:i+4])[0]
i += 4
symbol = struct.unpack('<' + "B"*symbol_len, data[i:i+symbol_len])
i += symbol_len
uri_len = struct.unpack('<I', data[i:i+4])[0]
i += 4
uri = struct.unpack('<' + "B"*uri_len, data[i:i+uri_len])
i += uri_len
fee = struct.unpack('<h', data[i:i+2])[0]
i += 2
has_creator = data[i]
i += 1
creators = []
verified = []
share = []
if has_creator:
creator_len = struct.unpack('<I', data[i:i+4])[0]
i += 4
for _ in range(creator_len):
creator = base58.b58encode(bytes(struct.unpack('<' + "B"*32, data[i:i+32])))
creators.append(creator)
i += 32
verified.append(data[i])
i += 1
share.append(data[i])
i += 1
primary_sale_happened = bool(data[i])
i += 1
is_mutable = bool(data[i])
metadata = {
"update_authority": source_account,
"mint": mint_account,
"data": {
"name": bytes(name).decode("utf-8").strip("\x00"),
"symbol": bytes(symbol).decode("utf-8").strip("\x00"),
"uri": bytes(uri).decode("utf-8").strip("\x00"),
"seller_fee_basis_points": fee,
"creators": creators,
"verified": verified,
"share": share,
},
"primary_sale_happened": primary_sale_happened,
"is_mutable": is_mutable,
}
return metadata
def get_metadata(client, mint_key):
metadata_account = get_metadata_account(mint_key)
data = base64.b64decode(client.get_account_info(metadata_account)['result']['value']['data'][0])
metadata = unpack_metadata_account(data)
return metadata
def update_metadata_instruction_data(name, symbol, uri, fee, creators, verified, share):
_data = bytes([1]) + _get_data_buffer(name, symbol, uri, fee, creators, verified, share) + bytes([0, 0])
instruction_layout = cStruct(
"instruction_type" / Int8ul,
"args" / Bytes(len(_data)),
)
return instruction_layout.build(
dict(
instruction_type=InstructionType.UPDATE_METADATA,
args=_data,
)
)
def update_metadata_instruction(data, update_authority, mint_key):
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),
]
return TransactionInstruction(keys=keys, program_id=METADATA_PROGRAM_ID, data=data)
def create_master_edition_instruction(
mint: PublicKey,
update_authority: PublicKey,
mint_authority: PublicKey,
payer: PublicKey,
supply: Union[int, None],
):
edition_account = get_edition(mint)
metadata_account = get_metadata_account(mint)
if supply is None:
data = struct.pack("<BB", 10, 0)
else:
data = struct.pack("<BBQ", 10, 1, supply)
keys = [
AccountMeta(pubkey=edition_account, is_signer=False, is_writable=True),
AccountMeta(pubkey=mint, is_signer=False, is_writable=True),
AccountMeta(pubkey=update_authority, is_signer=True, is_writable=False),
AccountMeta(pubkey=mint_authority, is_signer=True, is_writable=False),
AccountMeta(pubkey=payer, is_signer=True, is_writable=False),
AccountMeta(pubkey=metadata_account, is_signer=False, is_writable=False),
AccountMeta(pubkey=PublicKey(TOKEN_PROGRAM_ID), is_signer=False, is_writable=False),
AccountMeta(pubkey=PublicKey(SYSTEM_PROGRAM_ID), is_signer=False, is_writable=False),
AccountMeta(pubkey=PublicKey(SYSVAR_RENT_PUBKEY), is_signer=False, is_writable=False),
]
return TransactionInstruction(
keys=keys,
program_id=METADATA_PROGRAM_ID,
data=data,
)
================================================
FILE: metaplex/transactions.py
================================================
import json
import base64
from solana.publickey import PublicKey
from solana.transaction import Transaction
from solana.keypair import Keypair
from solana.rpc.api import Client
from solana.system_program import transfer, TransferParams, create_account, CreateAccountParams
from spl.token._layouts import MINT_LAYOUT, ACCOUNT_LAYOUT
from spl.token.instructions import (
get_associated_token_address, mint_to, MintToParams,
transfer as spl_transfer, TransferParams as SPLTransferParams,
burn as spl_burn, BurnParams,
initialize_mint, InitializeMintParams,
)
from metaplex.metadata import (
create_associated_token_account_instruction,
create_master_edition_instruction,
create_metadata_instruction_data,
create_metadata_instruction,
get_metadata,
update_metadata_instruction_data,
update_metadata_instruction,
ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
TOKEN_PROGRAM_ID,
)
def deploy(api_endpoint, source_account, name, symbol, fees):
# Initalize Client
client = Client(api_endpoint)
# List non-derived accounts
mint_account = Keypair()
token_account = TOKEN_PROGRAM_ID
# List signers
signers = [source_account, mint_account]
# Start transaction
tx = Transaction()
# Get the minimum rent balance for a mint account
min_rent_reseponse = client.get_minimum_balance_for_rent_exemption(MINT_LAYOUT.sizeof()) # type: ignore
lamports = min_rent_reseponse["result"]
# Generate Mint
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_account,
)
)
tx = tx.add(create_mint_account_ix)
initialize_mint_ix = initialize_mint(
InitializeMintParams(
decimals=0,
program_id=token_account,
mint=mint_account.public_key,
mint_authority=source_account.public_key,
freeze_authority=source_account.public_key,
)
)
tx = tx.add(initialize_mint_ix)
# Create Token Metadata
create_metadata_ix = create_metadata_instruction(
data=create_metadata_instruction_data(name, symbol, fees, [str(source_account.public_key)]),
update_authority=source_account.public_key,
mint_key=mint_account.public_key,
mint_authority_key=source_account.public_key,
payer=source_account.public_key,
)
tx = tx.add(create_metadata_ix)
return tx, signers, str(mint_account.public_key)
def wallet():
""" Generate a wallet and return the address and private key. """
account = Keypair()
pub_key = account.public_key
private_key = list(account.seed)
return json.dumps(
{
'address': str(pub_key),
'private_key': private_key
}
)
def topup(api_endpoint, sender_account, to, amount=None):
"""
Send a small amount of native currency to the specified wallet to handle gas fees. Return a status flag of success or fail and the native transaction data.
"""
# Connect to the api_endpoint
client = Client(api_endpoint)
# List accounts
dest_account = PublicKey(to)
# List signers
signers = [sender_account]
# Start transaction
tx = Transaction()
# Determine the amount to send
if amount is None:
min_rent_reseponse = client.get_minimum_balance_for_rent_exemption(ACCOUNT_LAYOUT.sizeof())
lamports = min_rent_reseponse["result"]
else:
lamports = int(amount)
# Generate transaction
transfer_ix = transfer(TransferParams(from_pubkey=sender_account.public_key, to_pubkey=dest_account, lamports=lamports))
tx = tx.add(transfer_ix)
return tx, signers
def update_token_metadata(api_endpoint, source_account, mint_token_id, link, data, fee, creators_addresses, creators_verified, creators_share):
"""
Updates the json metadata for a given mint token id.
"""
mint_account = PublicKey(mint_token_id)
signers = [source_account]
tx = Transaction()
update_metadata_data = update_metadata_instruction_data(
data['name'],
data['symbol'],
link,
fee,
creators_addresses,
creators_verified,
creators_share,
)
update_metadata_ix = update_metadata_instruction(
update_metadata_data,
source_account.public_key,
mint_account,
)
tx = tx.add(update_metadata_ix)
return tx, signers
def mint(api_endpoint, source_account, contract_key, dest_key, link, supply=1):
"""
Mint a token on the specified network and contract, into the wallet specified by address.
Required parameters: batch, sequence, limit
These are all 32-bit unsigned ints and are assembled into a 96-bit integer ID on Ethereum and compatible blockchains.
Where this is not possible we'll look for an alternate mapping.
Additional character fields: name, description, link, created
These are text fields intended to be written directly to the blockchain. created is an ISO standard timestamp string (UTC)
content is an optional JSON string for customer-specific data.
Return a status flag of success or fail and the native transaction data.
"""
# Initialize Client
client = Client(api_endpoint)
# List non-derived accounts
mint_account = PublicKey(contract_key)
user_account = PublicKey(dest_key)
token_account = TOKEN_PROGRAM_ID
# List signers
signers = [source_account]
# Start transaction
tx = Transaction()
# Create Associated Token Account
associated_token_account = get_associated_token_address(user_account, mint_account)
associated_token_account_info = client.get_account_info(associated_token_account)
# Check if PDA is initialized. If not, create the 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_instruction(
associated_token_account=associated_token_account,
payer=source_account.public_key, # signer
wallet_address=user_account,
token_mint_address=mint_account,
)
tx = tx.add(associated_token_account_ix)
# Mint NFT to the newly create associated token account
mint_to_ix = mint_to(
MintToParams(
program_id=TOKEN_PROGRAM_ID,
mint=mint_account,
dest=associated_token_account,
mint_authority=source_account.public_key,
amount=1,
signers=[source_account.public_key],
)
)
tx = tx.add(mint_to_ix)
metadata = get_metadata(client, mint_account)
update_metadata_data = update_metadata_instruction_data(
metadata['data']['name'],
metadata['data']['symbol'],
link,
metadata['data']['seller_fee_basis_points'],
metadata['data']['creators'],
metadata['data']['verified'],
metadata['data']['share'],
)
update_metadata_ix = update_metadata_instruction(
update_metadata_data,
source_account.public_key,
mint_account,
)
tx = tx.add(update_metadata_ix)
create_master_edition_ix = create_master_edition_instruction(
mint=mint_account,
update_authority=source_account.public_key,
mint_authority=source_account.public_key,
payer=source_account.public_key,
supply=supply,
)
tx = tx.add(create_master_edition_ix)
return tx, signers
def send(api_endpoint, source_account, contract_key, sender_key, dest_key, private_key):
"""
Transfer a token on a given network and contract from the sender to the recipient.
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.
"""
# Initialize Client
client = Client(api_endpoint)
# List non-derived accounts
owner_account = Keypair(private_key) # Owner of contract
sender_account = PublicKey(sender_key) # Public key of `owner_account`
token_account = TOKEN_PROGRAM_ID
mint_account = PublicKey(contract_key)
dest_account = PublicKey(dest_key)
# This is a very rare care, but in the off chance that the source wallet is the recipient of a transfer we don't need a list of 2 keys
signers = [source_account, owner_account]
# Start transaction
tx = Transaction()
# Find PDA for sender
token_pda_address = get_associated_token_address(sender_account, mint_account)
if client.get_account_info(token_pda_address)['result']['value'] is None:
raise Exception
# Check if PDA is initialized for receiver. If not, create the account
associated_token_account = get_associated_token_address(dest_account, mint_account)
associated_token_account_info = 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_instruction(
associated_token_account=associated_token_account,
payer=source_account.public_key, # signer
wallet_address=dest_account,
token_mint_address=mint_account,
)
tx = tx.add(associated_token_account_ix)
# Transfer the Token from the sender account to the associated token account
spl_transfer_ix = spl_transfer(
SPLTransferParams(
program_id=token_account,
source=token_pda_address,
dest=associated_token_account,
owner=sender_account,
signers=[],
amount=1,
)
)
tx = tx.add(spl_transfer_ix)
return tx, signers
def burn(api_endpoint, contract_key, owner_key, private_key):
"""
Burn a token, permanently removing it from the blockchain.
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.
"""
# Initialize Client
client = Client(api_endpoint)
# List accounts
owner_account = PublicKey(owner_key)
token_account = TOKEN_PROGRAM_ID
mint_account = PublicKey(contract_key)
# List signers
signers = [Keypair(private_key)]
# Start transaction
tx = Transaction()
# Find PDA for sender
token_pda_address = get_associated_token_address(owner_account, mint_account)
if client.get_account_info(token_pda_address)['result']['value'] is None:
raise Exception
# Burn token
burn_ix = spl_burn(
BurnParams(
program_id=token_account,
account=token_pda_address,
mint=mint_account,
owner=owner_account,
amount=1,
signers=[],
)
)
tx = tx.add(burn_ix)
return tx, signers
================================================
FILE: requirements.txt
================================================
anyio==3.3.4
attrs==21.2.0
base58==2.1.1
cachetools==4.2.4
certifi==2021.10.8
cffi==1.15.0
chardet==4.0.0
charset-normalizer==2.0.7
cheroot==8.5.2
CherryPy==18.6.1
construct==2.10.67
cryptography==35.0.0
ed25519==1.5
h11==0.12.0
httpcore==0.13.7
httpx==0.20.0
idna==3.3
iniconfig==1.1.1
jaraco.classes==3.2.1
jaraco.collections==3.4.0
jaraco.functools==3.3.0
jaraco.text==3.5.1
more-itertools==8.10.0
packaging==21.2
pluggy==1.0.0
portend==3.0.0
py==1.10.0
pycparser==2.20
PyNaCl==1.4.0
pyparsing==2.4.7
pytest==6.2.5
pytz==2021.3
requests==2.26.0
rfc3986==1.5.0
six==1.16.0
sniffio==1.2.0
solana==0.18.1
tempora==4.1.2
toml==0.10.2
typing-extensions==3.10.0.2
urllib3==1.26.7
websockets==10.0
zc.lockfile==2.0
================================================
FILE: test/test_api.py
================================================
import argparse
import string
import random
import json
import time
import base58
from solana.keypair import Keypair
from solana.rpc.api import Client
from metaplex.metadata import get_metadata
from cryptography.fernet import Fernet
from api.metaplex_api import MetaplexAPI
def await_full_confirmation(client, txn, max_timeout=60):
if txn is None:
return
elapsed = 0
while elapsed < max_timeout:
sleep_time = 1
time.sleep(sleep_time)
elapsed += sleep_time
resp = client.get_confirmed_transaction(txn)
while 'result' not in resp:
resp = client.get_confirmed_transaction(txn)
if resp["result"]:
print(f"Took {elapsed} seconds to confirm transaction {txn}")
break
def test(api_endpoint="https://api.devnet.solana.com/"):
keypair = Keypair()
cfg = {
"PRIVATE_KEY": base58.b58encode(keypair.seed).decode("ascii"),
"PUBLIC_KEY": str(keypair.public_key),
"DECRYPTION_KEY": Fernet.generate_key().decode("ascii"),
}
api = MetaplexAPI(cfg)
client = Client(api_endpoint)
resp = {}
while 'result' not in resp:
resp = client.request_airdrop(keypair.public_key, int(1e9))
print("Request Airdrop:", resp)
txn = resp['result']
await_full_confirmation(client, txn)
letters = string.ascii_uppercase
name = ''.join([random.choice(letters) for i in range(32)])
symbol = ''.join([random.choice(letters) for i in range(10)])
print("Name:", name)
print("Symbol:", symbol)
# added seller_basis_fee_points
deploy_response = json.loads(api.deploy(api_endpoint, name, symbol, 0))
print("Deploy:", deploy_response)
assert deploy_response["status"] == 200
contract = deploy_response.get("contract")
print(get_metadata(client, contract))
wallet = json.loads(api.wallet())
address1 = wallet.get('address')
encrypted_pk1 = api.cipher.encrypt(bytes(wallet.get('private_key')))
topup_response = json.loads(api.topup(api_endpoint, address1))
print(f"Topup {address1}:", topup_response)
assert topup_response["status"] == 200
mint_to_response = json.loads(api.mint(api_endpoint, contract, address1, "https://arweave.net/1eH7bZS-6HZH4YOc8T_tGp2Rq25dlhclXJkoa6U55mM/"))
print("Mint:", mint_to_response)
# await_confirmation(client, mint_to_response['tx'])
assert mint_to_response["status"] == 200
print(get_metadata(client, contract))
wallet2 = json.loads(api.wallet())
address2 = wallet2.get('address')
encrypted_pk2 = api.cipher.encrypt(bytes(wallet2.get('private_key')))
print(client.request_airdrop(api.public_key, int(1e10)))
topup_response2 = json.loads(api.topup(api_endpoint, address2))
print(f"Topup {address2}:", topup_response2)
# await_confirmation(client, topup_response2['tx'])
assert topup_response2["status"] == 200
send_response = json.loads(api.send(api_endpoint, contract, address1, address2, encrypted_pk1))
assert send_response["status"] == 200
# await_confirmation(client, send_response['tx'])
burn_response = json.loads(api.burn(api_endpoint, contract, address2, encrypted_pk2))
print("Burn:", burn_response)
# await_confirmation(client, burn_response['tx'])
assert burn_response["status"] == 200
print("Success!")
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--network", default=None)
args = ap.parse_args()
if args.network == None or args.network == 'devnet':
test()
elif args.network == 'testnet':
test(api_endpoint="https://api.testnet.solana.com/")
elif args.network == 'mainnet':
test(api_endpoint="https://api.mainnet-beta.solana.com/")
else:
print("Invalid network argument supplied")
================================================
FILE: utils/execution_engine.py
================================================
import time
from solana.keypair import Keypair
from solana.rpc.api import Client
from solana.rpc.types import TxOpts
def execute(api_endpoint, tx, signers, max_retries=3, skip_confirmation=True, max_timeout=60, target=20, finalized=True):
client = Client(api_endpoint)
signers = list(map(Keypair, set(map(lambda s: s.seed, signers))))
for attempt in range(max_retries):
try:
result = client.send_transaction(tx, *signers, opts=TxOpts(skip_preflight=True))
print(result)
signatures = [x.signature for x in tx.signatures]
if not skip_confirmation:
await_confirmation(client, signatures, max_timeout, target, finalized)
return result
except Exception as e:
print(f"Failed attempt {attempt}: {e}")
continue
raise e
def await_confirmation(client, signatures, max_timeout=60, target=20, finalized=True):
elapsed = 0
while elapsed < max_timeout:
sleep_time = 1
time.sleep(sleep_time)
elapsed += sleep_time
resp = client.get_signature_statuses(signatures)
if resp["result"]["value"][0] is not None:
confirmations = resp["result"]["value"][0]["confirmations"]
is_finalized = resp["result"]["value"][0]["confirmationStatus"] == "finalized"
else:
continue
if not finalized:
if confirmations >= target or is_finalized:
print(f"Took {elapsed} seconds to confirm transaction")
return
elif is_finalized:
print(f"Took {elapsed} seconds to confirm transaction")
return
gitextract_hzgxmx41/
├── README.md
├── api/
│ ├── __init__.py
│ └── metaplex_api.py
├── api.py
├── metaplex/
│ ├── __init__.py
│ ├── metadata.py
│ └── transactions.py
├── requirements.txt
├── test/
│ └── test_api.py
└── utils/
└── execution_engine.py
SYMBOL INDEX (32 symbols across 5 files)
FILE: api/metaplex_api.py
class MetaplexAPI (line 8) | class MetaplexAPI():
method __init__ (line 10) | def __init__(self, cfg):
method wallet (line 16) | def wallet(self):
method deploy (line 28) | def deploy(self, api_endpoint, name, symbol, fees, max_retries=3, skip...
method topup (line 52) | def topup(self, api_endpoint, to, amount=None, max_retries=3, skip_con...
method mint (line 73) | def mint(self, api_endpoint, contract_key, dest_key, link, max_retries...
method update_token_metadata (line 93) | def update_token_metadata(self, api_endpoint, mint_token_id, link, da...
method send (line 112) | def send(self, api_endpoint, contract_key, sender_key, dest_key, encry...
method burn (line 136) | def burn(self, api_endpoint, contract_key, owner_key, encrypted_privat...
FILE: metaplex/metadata.py
class InstructionType (line 16) | class InstructionType(IntEnum):
function get_metadata_account (line 26) | def get_metadata_account(mint_key):
function get_edition (line 32) | def get_edition(mint_key):
function create_associated_token_account_instruction (line 38) | def create_associated_token_account_instruction(associated_token_account...
function _get_data_buffer (line 50) | def _get_data_buffer(name, symbol, uri, fee, creators, verified=None, sh...
function create_metadata_instruction_data (line 91) | def create_metadata_instruction_data(name, symbol, fee, creators):
function create_metadata_instruction (line 109) | def create_metadata_instruction(data, update_authority, mint_key, mint_a...
function unpack_metadata_account (line 123) | def unpack_metadata_account(data):
function get_metadata (line 180) | def get_metadata(client, mint_key):
function update_metadata_instruction_data (line 186) | def update_metadata_instruction_data(name, symbol, uri, fee, creators, v...
function update_metadata_instruction (line 199) | def update_metadata_instruction(data, update_authority, mint_key):
function create_master_edition_instruction (line 207) | def create_master_edition_instruction(
FILE: metaplex/transactions.py
function deploy (line 28) | def deploy(api_endpoint, source_account, name, symbol, fees):
function wallet (line 74) | def wallet():
function topup (line 87) | def topup(api_endpoint, sender_account, to, amount=None):
function update_token_metadata (line 110) | def update_token_metadata(api_endpoint, source_account, mint_token_id, l...
function mint (line 136) | def mint(api_endpoint, source_account, contract_key, dest_key, link, sup...
function send (line 213) | def send(api_endpoint, source_account, contract_key, sender_key, dest_ke...
function burn (line 266) | def burn(api_endpoint, contract_key, owner_key, private_key):
FILE: test/test_api.py
function await_full_confirmation (line 13) | def await_full_confirmation(client, txn, max_timeout=60):
function test (line 28) | def test(api_endpoint="https://api.devnet.solana.com/"):
FILE: utils/execution_engine.py
function execute (line 6) | def execute(api_endpoint, tx, signers, max_retries=3, skip_confirmation=...
function await_confirmation (line 22) | def await_confirmation(client, signatures, max_timeout=60, target=20, fi...
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (47K chars).
[
{
"path": "README.md",
"chars": 12018,
"preview": "# Metaplex Python API\n\nThis modules allows you to create, mint, transfer and burn NFTs on the Solana blockchain using Py"
},
{
"path": "api/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "api/metaplex_api.py",
"chars": 6737,
"preview": "import json\nfrom cryptography.fernet import Fernet\nimport base58\nfrom solana.keypair import Keypair \nfrom metaplex.trans"
},
{
"path": "api.py",
"chars": 41,
"preview": "from api.metaplex_api import MetaplexAPI\n"
},
{
"path": "metaplex/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "metaplex/metadata.py",
"chars": 8996,
"preview": "from typing import Union\nimport struct\nfrom enum import IntEnum\nfrom construct import Bytes, Flag, Int8ul\nfrom construct"
},
{
"path": "metaplex/transactions.py",
"chars": 11457,
"preview": "import json\nimport base64\nfrom solana.publickey import PublicKey \nfrom solana.transaction import Transaction\nfrom solana"
},
{
"path": "requirements.txt",
"chars": 711,
"preview": "anyio==3.3.4\nattrs==21.2.0\nbase58==2.1.1\ncachetools==4.2.4\ncertifi==2021.10.8\ncffi==1.15.0\nchardet==4.0.0\ncharset-normal"
},
{
"path": "test/test_api.py",
"chars": 3799,
"preview": "import argparse\nimport string\nimport random\nimport json\nimport time\nimport base58\nfrom solana.keypair import Keypair\nfro"
},
{
"path": "utils/execution_engine.py",
"chars": 1663,
"preview": "import time\nfrom solana.keypair import Keypair \nfrom solana.rpc.api import Client\nfrom solana.rpc.types import TxOpts \n\n"
}
]
About this extraction
This page contains the full source code of the metaplex-foundation/python-api GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (44.4 KB), approximately 12.7k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.