AnchorPy

repository·main·Indexed 43 days ago

https://github.com/kevinheavey/anchorpy

A Python library for interacting with Anchor programs on Solana. Version 0.21.0 provides static and dynamic client capabilities, a CLI for initializing tests and interactive shells, and a client generator to create typed Python clients from Anchor IDLs.

Tokens
16.1K
Snippets
54
Records
60
Agent score
51%

What's inside AnchorPy

  1. Overview of AnchorPy features

    main

    AnchorPy is a Python gateway for interacting with Anchor programs. It provides several key tools for developers:

    • Static client generator (clientgen): Generates clients based on program definitions.
    • Dynamic client (dynamic_client): Provides a runtime client experience similar to anchor-ts.
    • Pytest plugin: Enables testing Anchor programs within the Pytest framework.
    • CLI: A command-line interface with various utilities for Anchor Python development.
  2. Naming conventions: snake_case vs camelCase

    main

    AnchorPy follows Pythonic snake_case conventions to match Rust, whereas the TypeScript client uses camelCase. When porting logic from TypeScript to AnchorPy, ensure you convert the following to snake_case:

    • Workspaces: workspace["puppet_master"] (not puppetMaster).
    • Instructions: program.rpc["my_func"] (not myFunc).
    • Context account keys: {"my_account": my_account} (not myAccount).
    • User-defined type fields: program.type["TransactionAccount"](is_writable=True) (not isWritable).
    # Example of snake_case in user-defined types
    program.type["TransactionAccount"](is_writable=True)
  3. Using the explicit `Context` object for RPC calls

    main

    When calling RPC functions in AnchorPy, you must pass account information using an explicit Context dataclass via the ctx keyword argument. This differs from the TypeScript client, which allows passing a plain object directly as an argument.

    Pattern: program.rpc["my_func"](ctx=Context({"account_name": account_instance}))

    # Instead of program.rpc["my_func"]({"myAccount": my_account})
    program.rpc["my_func"](ctx=Context({"my_account": my_account}))
  4. How AnchorPy handles naming conventions

    main

    AnchorPy follows the naming conventions used in Rust. When accessing programs in a workspace or methods via the RPC interface, use snake_case (e.g., basic_0 instead of Basic0).

    If you are unsure of how a name has been converted, you can inspect program.idl, which shows how AnchorPy has parsed and transformed the IDL names.

  5. Instantiate IDL Enums and Structs with `program.type`

    main

    The Program object provides a .type namespace that allows you to instantiate user-defined types (Enums and Structs) defined in the program's IDL. This is useful for constructing arguments for RPC calls.

    Enums

    To use an enum variant, access it via program.type["EnumName"].VariantName().

    Structs

    To build a struct, call the struct name from the type namespace with the required fields: program.type["StructName"](field1=val1, field2=val2).

    # Example: Using an Enum
    await program.rpc["my_func"](program.type["Side"].Buy())
    
    # Example: Using a Struct
    program.type["TransactionAccount"](
        pubkey=multisig.pubkey(),
        is_writable=True,
        is_signer=False,
    )
  6. Accessing program members using dictionary syntax

    main

    Unlike the Anchor TypeScript client which uses dot notation (e.g., program.rpc.initialize()), AnchorPy uses dictionary-style access with square brackets for workspaces, RPC instructions, and instruction builders. This is because AnchorPy uses dictionaries for these lookups.

    Examples:

    • Workspaces: Use workspace["name"] instead of workspace.name.
    • RPC calls: Use program.rpc["instruction_name"]() instead of program.rpc.instructionName().
    • Instructions: Use program.instruction["instruction_name"] instead of program.instruction.instructionName.
    # Instead of workspace.basic_1
    workspace["basic_1"]
    
    # Instead of program.rpc.initialize()
    program.rpc["initialize"]()
  7. Install AnchorPy

    main

    AnchorPy requires Python >= 3.9. You can install it via pip using one of the following methods:

    • Full installation (includes CLI and Pytest plugin): pip install anchorpy[cli, pytest]
    • Minimal installation (core library only): pip install anchorpy
    pip install anchorpy[cli, pytest]
  8. Generate a Python client with `anchorpy client-gen`

    main

    Use the anchorpy client-gen command to generate a fully typed Python client from an Anchor IDL. This is recommended over the dynamic client because it provides proper type hints, error classes, and utility functions for serialization.

    The generator produces a package containing submodules for accounts, instructions, types, and errors.

    $ anchorpy client-gen path/to/idl.json ./my_client
  9. Use the AnchorPy CLI

    main

    The AnchorPy CLI provides utilities to streamline working with Anchor programs in Python. You can use it to initialize test files or launch an interactive IPython shell with a pre-configured workspace.

    To see all available commands and options, run:

    anchorpy --help
    $ anchorpy --help
    Usage: anchorpy [OPTIONS] COMMAND [ARGS]...
    
      AnchorPy CLI.
    
    Options:
      --install-completion [bash|zsh|fish|powershell|pwsh]
                                      Install completion for the specified shell.
      --show-completion [bash|zsh|fish|powershell|pwsh]
                                      Show completion for the specified shell, to
                                      copy it or customize the installation.
      --help                          Show this message and exit.
    
    Commands:
      init   Create a basic Python test file for an Anchor program.
      shell  Start IPython shell with AnchorPy workspace object initialized.
  10. Use Workspaces in AnchorPy

    main

    AnchorPy supports workspaces to easily access deployed programs. Unlike JavaScript/TypeScript where workspaces are often globally available, in AnchorPy you must explicitly create and close them.

    Key differences:

    • Use create_workspace() to instantiate a workspace.
    • Access programs using snake_case (e.g., workspace["basic_0"]), matching the naming convention used in Rust/lib.rs.
    • Use await close_workspace(workspace) to close all HTTP clients within the workspace and prevent warnings.
    • For testing, workspace_fixture is available as a factory.
    import asyncio
    from anchorpy import create_workspace, close_workspace
    
    async def main():
        # Read the deployed program from the workspace.
        workspace = create_workspace()
        program = workspace["basic_0"]
        
        # Execute the RPC.
        await program.rpc["initialize"]()
        
        # Close all HTTP clients in the workspace.
        await close_workspace(workspace)
    
    asyncio.run(main())
  11. Generate a Program Client from an IDL

    main

    To interact with a smart contract, you can generate a Program client by providing its Interface Definition Language (IDL) and its deployed program ID.

    It is recommended to use a generated client (which provides proper type hints) instead of the dynamic client where possible. When using the dynamic client, you must ensure the underlying HTTP client is closed to avoid warnings. This can be done using an async with context manager or by explicitly calling await program.close().

    from pathlib import Path
    import asyncio
    from solders.pubkey import Pubkey
    from anchorpy import Idl, Program
    
    async def main():
        # Read the generated IDL.
        with Path("target/idl/basic_0.json").open() as f:
            raw_idl = f.read()
        idl = Idl.from_json(raw_idl)
        
        # Address of the deployed program.
        program_id = Pubkey.from_string("<YOUR-PROGRAM-ID>")
        
        # Generate the program client from IDL using a context manager.
        async with Program(idl, program_id) as program:
            # Execute the RPC using bracket notation for the method name.
            await program.rpc["initialize"]()
    
    asyncio.run(main())
  12. Call RPC functions with Arguments and Accounts

    main

    When calling an RPC function that requires specific accounts or signers, AnchorPy uses an explicit Context object. This is a departure from the TypeScript/JavaScript implementation where accounts are passed in a plain options object.

    To call a function with arguments and context:

    1. Define an accounts dictionary containing the required public keys.
    2. Wrap the accounts and signers list in a Context object.
    3. Pass the arguments followed by the ctx keyword argument to the RPC method.
    import asyncio
    from solders.keypair import Keypair
    from solders.system_program import ID as SYS_PROGRAM_ID
    from anchorpy import create_workspace, close_workspace, Context
    
    async def main():
        workspace = create_workspace()
        program = workspace["basic_1"]
        my_account = Keypair()
        
        # Define the accounts required by the instruction
        accounts = {
            "my_account": my_account.pubkey(),
            "user": program.provider.wallet.public_key,
            "system_program": SYS_PROGRAM_ID
        }
        
        # Execute the RPC with arguments and an explicit Context object
        await program.rpc["initialize"](
            1234, 
            ctx=Context(accounts=accounts, signers=[my_account])
        )
        
        await close_workspace(workspace)
    
    asyncio.run(main())