main 441c2ba9be76 cached
10 files
44.4 KB
12.7k tokens
32 symbols
1 requests
Download .txt
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
Download .txt
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
Download .txt
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.

Copied to clipboard!