Movement Labs LogoMovement Docs
Interact on Chain

Python SDK

Learn how to interact with Movement using the Aptos Python SDK.

The Movement network is compatible with the Aptos Python SDK, which provides a comprehensive interface for blockchain interactions. The SDK is available on PyPi with source code in the aptos-python-sdk GitHub repository. This guide demonstrates how to use the Python SDK to interact with smart contracts deployed on the Movement network.

Installation

Install with pip

pip3 install aptos-sdk

Install from source code

git clone https://github.com/aptos-labs/aptos-python-sdk
pip3 install . --user

Install by embedding

cd /path/to/python/project
cp -r /path/to/aptos-python-sdk aptos-sdk

Getting Started

This tutorial demonstrates how to use the Python SDK to interact with a smart contract deployed on the Movement network. We'll build a complete example that shows account management, balance checking, and smart contract interactions.

Create Project Directory

mkdir python-sdk-example
cd python-sdk-example

Create Environment Configuration

Create a .env file to store your configuration:

touch .env

Add the following configuration to your .env file:

PRIVATE_KEY=YOUR_PRIVATE_KEY
RPC_URL = "https://testnet.movementnetwork.xyz/v1"
FAUCET_URL = "https://faucet.testnet.movementnetwork.xyz/"
MODULE_ADDRESS=YOUR_MODULE_ADDRESS

Note: Replace the placeholder values with your actual private key and module address. You can generate a private key using the Movement CLI or other wallet tools.

Required Imports

First, let's import all the necessary modules for our Movement SDK client:

import asyncio
import os
from typing import Optional, Any, Dict

from aptos_sdk.ed25519 import PrivateKey
from aptos_sdk.async_client import FaucetClient, RestClient
from aptos_sdk.account import Account, AccountAddress
from aptos_sdk.transactions import (
    EntryFunction, 
    TransactionPayload, 
    TransactionArgument
)
from aptos_sdk.bcs import Serializer
from dotenv import load_dotenv

Create the Movement Client Class

We'll create a comprehensive client class that handles all interactions with Movement smart contracts:

class MovementSmartContractClient:
    """A client for interacting with Movement smart contracts, specifically the message contract."""

Initialize the Client

The constructor initializes the client with network configuration and sets up the necessary components:

  • RestClient: Handles RPC calls to the Movement network
  • FaucetClient: Manages account funding for testing
  • Account: Manages the user's account and private key
     def __init__(self, private_key: str, rpc_url: str, faucet_url: str, module_address: str = ""):
        """
        Initialize the Movement client with network configuration.
        
        Args:
            private_key: The private key for the account (with or without 0x prefix)
            rpc_url: The RPC URL for the Movement network
            faucet_url: The faucet URL for funding accounts
            module_address: The address of the deployed smart contract module
        """
        # Initialize clients
        self.rest_client = RestClient(rpc_url)
        self.faucet_client = FaucetClient(faucet_url, self.rest_client)
        
        # Setup account
        if private_key.startswith("0x"):
            private_key = private_key[2:]
        
        self.private_key = PrivateKey.from_hex(private_key)
        self.account_address = AccountAddress.from_key(self.private_key.public_key())
        self.account = Account(account_address=self.account_address, private_key=self.private_key)
        
        # Contract configuration
        self.module_address = module_address
        self.set_message_function = f"message::set_message"
        self.get_message_function = f"message::get_message"
        
        print(f"Account address: {self.account.address()}")

Account Management Methods

Fund Account

The fund_account method uses the faucet client to add test tokens to your account:

    async def fund_account(self, amount: int = 100000000) -> None:
        """
        Fund the account using the faucet.
        
        Args:
            amount: Amount to fund in Octas (default: 1 APT = 100000000 Octas)
        """
        try:
            await self.faucet_client.fund_account(self.account.address(), amount)
            print(f"Successfully funded account with {amount} Octas")
        except Exception as e:
            print(f"Error funding account: {e}")

Check Balance

The get_balance method retrieves the current MOVE token balance in Octas (1 MOVE = 100,000,000 Octas):

    async def get_balance(self) -> int:
        """
        Get the current account balance.
        
        Returns:
            Account balance in Octas
        """
        try:
            balance = await self.rest_client.account_balance(self.account.address())
            return balance
        except Exception as e:
            print(f"Error getting balance: {e}")
            return 0

Smart Contract Interaction Methods

Read Contract State

The get_message method reads data from the smart contract without modifying the blockchain state:

async def get_message(self, account_address: Optional[str] = None) -> Optional[Dict[str, Any]]:
        """
        Retrieve the resource message::MessageHolder::message
        
        Args:
            account_address: The account address to read message for (defaults to current account)
            
        Returns:
            The message holder resource or None if not found
        """
        if not self.module_address:
            raise ValueError("Module address not set. Please provide module_address in constructor.")
        
        target_address = AccountAddress.from_str(account_address) if account_address else self.account.address()
        
        try:
            
            # Get the get_message resource from the account
            resource = await self.rest_client.view(
                f"{self.module_address}::{self.get_message_function}",
                [],
                [str(target_address)]
            )
            print(f"Message resource: {resource}")
            return resource
            
        except Exception as e:
            print(f"Error reading message: {e}")
            return None

Write to Contract

The set_message method submits a transaction to modify the smart contract state:

    async def set_message(self, message: str) -> Optional[str]:
        """
        Potentially initialize and set the resource message::MessageHolder::message
        
        Args:
            message: The message to store
            
        Returns:
            Transaction hash if successful, None otherwise
        """
        if not self.module_address:
            raise ValueError("Module address not set. Please provide module_address in constructor.")
        
        try:
            print("\n=== Submitting Transaction ===")
            
            # Create the transaction payload using the correct format
            payload = EntryFunction.natural(
                f"{self.module_address}::{self.set_message_function}",
                "set_message",
                [],
                [TransactionArgument(message, Serializer.str)]
            )
            
            # Create and submit the BCS signed transaction
            signed_transaction = await self.rest_client.create_bcs_signed_transaction(
                self.account, 
                TransactionPayload(payload)
            )
            
            # Submit the transaction
            txn_hash = await self.rest_client.submit_bcs_transaction(signed_transaction)
            print(f"Submitted transaction: {txn_hash}")
            
            # Wait for transaction confirmation
            await self.rest_client.wait_for_transaction(txn_hash)
            print(f"Transaction confirmed: {txn_hash}")
            
            return txn_hash
            
        except Exception as e:
            print(f"Error setting message: {e}")
            return None

Complete Example Usage

Here's a complete example that demonstrates all the functionality:

async def main():
    """Main function to demonstrate the smart contract interaction."""
    # Load environment variables
    load_dotenv()
    
    private_key = os.getenv("PRIVATE_KEY")
    rpc_url = os.getenv("RPC_URL")
    faucet_url = os.getenv("FAUCET_URL")
    module_address = os.getenv("MODULE_ADDRESS", "")  # Add this to your .env file
    
    if not all([private_key, rpc_url, faucet_url]):
        print("Please set PRIVATE_KEY, RPC_URL, and FAUCET_URL in your .env file")
        return
    
    # Initialize the client
    client = MovementSmartContractClient(
        private_key=private_key,
        rpc_url=rpc_url,
        faucet_url=faucet_url,
        module_address=module_address
    )
    
    # Fund the account
    await client.fund_account()
    
    # Get balance
    await client.get_balance()
    
    # Wait a bit for funding to complete
    await asyncio.sleep(5)
    
    # Check current balance
    balance = await client.get_balance()
    print(f"Current balance: {balance} Octas")
    
    # If module address is set, demonstrate smart contract interaction
    if module_address:
        # Try to read existing message
        await client.get_message()
        
        # Set a new message
        message = "Gmove to those that still Gmove"
        txn_hash = await client.set_message(message)
        
        if txn_hash:
            # Read the message after setting it
            await client.get_message()
    else:
        print("\nTo interact with smart contracts, please set MODULE_ADDRESS in your .env file")


if __name__ == "__main__":
    asyncio.run(main())

Expected Output

When you run the example, you should see output similar to:

Account address: 0xACCOUNT_ADDRESS
Successfully funded account with 100000000 Octas
Current balance: 100000000 Octas
Message resource: b'["gmove"]'

=== Submitting Transaction ===
Submitted transaction: 0xTRANSACTION_HASH
Transaction confirmed: 0xTRANSACTION_HASH
Message resource: b'["Gmove to those that still Gmove"]'

Full Example Code

import asyncio
import os
from typing import Optional, Any, Dict

from aptos_sdk.ed25519 import PrivateKey
from aptos_sdk.async_client import FaucetClient, RestClient
from aptos_sdk.account import Account, AccountAddress
from aptos_sdk.transactions import (
    EntryFunction, 
    TransactionPayload, 
    TransactionArgument
)
from aptos_sdk.bcs import Serializer
from dotenv import load_dotenv


class MovementSmartContractClient:
    """A client for interacting with Movement smart contracts, specifically the message contract."""
    
    def __init__(self, private_key: str, rpc_url: str, faucet_url: str, module_address: str = ""):
        """
        Initialize the Movement client with network configuration.
        
        Args:
            private_key: The private key for the account (with or without 0x prefix)
            rpc_url: The RPC URL for the Movement network
            faucet_url: The faucet URL for funding accounts
            module_address: The address of the deployed smart contract module
        """
        # Initialize clients
        self.rest_client = RestClient(rpc_url)
        self.faucet_client = FaucetClient(faucet_url, self.rest_client)
        
        # Setup account
        if private_key.startswith("0x"):
            private_key = private_key[2:]
        
        self.private_key = PrivateKey.from_hex(private_key)
        self.account_address = AccountAddress.from_key(self.private_key.public_key())
        self.account = Account(account_address=self.account_address, private_key=self.private_key)
        
        # Contract configuration
        self.module_address = module_address
        self.set_message_function = f"message::set_message"
        self.get_message_function = f"message::get_message"
        
        print(f"Account address: {self.account.address()}")
    
    async def fund_account(self, amount: int = 100000000) -> None:
        """
        Fund the account using the faucet.
        
        Args:
            amount: Amount to fund in Octas (default: 1 APT = 100000000 Octas)
        """
        try:
            await self.faucet_client.fund_account(self.account.address(), amount)
            print(f"Successfully funded account with {amount} Octas")
        except Exception as e:
            print(f"Error funding account: {e}")
    
    async def get_balance(self) -> int:
        """
        Get the current account balance.
        
        Returns:
            Account balance in Octas
        """
        try:
            balance = await self.rest_client.account_balance(self.account.address())
            return balance
        except Exception as e:
            print(f"Error getting balance: {e}")
            return 0
    
    async def get_message(self, account_address: Optional[str] = None) -> Optional[Dict[str, Any]]:
        """
        Retrieve the resource message::MessageHolder::message
        
        Args:
            account_address: The account address to read message for (defaults to current account)
            
        Returns:
            The message holder resource or None if not found
        """
        if not self.module_address:
            raise ValueError("Module address not set. Please provide module_address in constructor.")
        
        target_address = AccountAddress.from_str(account_address) if account_address else self.account.address()
        
        try:
            # Get the get_message resource from the account
            resource = await self.rest_client.view(
                f"{self.module_address}::{self.get_message_function}",
                [],
                [str(target_address)]
            )
            print(f"Message resource: {resource}")
            return resource
            
        except Exception as e:
            print(f"Error reading message: {e}")
            return None
    
    async def set_message(self, message: str) -> Optional[str]:
        """
        Potentially initialize and set the resource message::MessageHolder::message
        
        Args:
            message: The message to store
            
        Returns:
            Transaction hash if successful, None otherwise
        """
        if not self.module_address:
            raise ValueError("Module address not set. Please provide module_address in constructor.")
        
        try:
            print("\n=== Submitting Transaction ===")
            
            # Create the transaction payload using the correct format
            payload = EntryFunction.natural(
                f"{self.module_address}::{self.set_message_function}",
                "set_message",
                [],
                [TransactionArgument(message, Serializer.str)]
            )
            
            # Create and submit the BCS signed transaction
            signed_transaction = await self.rest_client.create_bcs_signed_transaction(
                self.account, 
                TransactionPayload(payload)
            )
            
            # Submit the transaction
            txn_hash = await self.rest_client.submit_bcs_transaction(signed_transaction)
            print(f"Submitted transaction: {txn_hash}")
            
            # Wait for transaction confirmation
            await self.rest_client.wait_for_transaction(txn_hash)
            print(f"Transaction confirmed: {txn_hash}")
            
            return txn_hash
            
        except Exception as e:
            print(f"Error setting message: {e}")
            return None
    
    async def get_account_info(self) -> Any:
        """
        Get detailed account information.
        
        Returns:
            Account information from the blockchain
        """
        try:
            account_info = await self.rest_client.account(self.account.address())
            print(f"Account info: {account_info}")
            return account_info
        except Exception as e:
            print(f"Error getting account info: {e}")
            return None


async def main():
    """Main function to demonstrate the smart contract interaction."""
    # Load environment variables
    load_dotenv()
    
    private_key = os.getenv("PRIVATE_KEY")
    rpc_url = os.getenv("RPC_URL")
    faucet_url = os.getenv("FAUCET_URL")
    module_address = os.getenv("MODULE_ADDRESS", "")  # Add this to your .env file
    
    if not all([private_key, rpc_url, faucet_url]):
        print("Please set PRIVATE_KEY, RPC_URL, and FAUCET_URL in your .env file")
        return
    
    # Initialize the client
    client = MovementSmartContractClient(
        private_key=private_key,
        rpc_url=rpc_url,
        faucet_url=faucet_url,
        module_address=module_address
    )
    
    # Fund the account
    await client.fund_account()
    
    # Get balance
    await client.get_balance()
    
    # Wait a bit for funding to complete
    await asyncio.sleep(5)
    
    # Get account info
    await client.get_account_info()
    
    # If module address is set, demonstrate smart contract interaction
    if module_address:
        # Try to read existing message
        await client.get_message()
        
        # Set a new message
        message = "Gmove to those that still Gmove"
        txn_hash = await client.set_message(message)
        
        if txn_hash:
            # Read the message after setting it
            await client.get_message()
    else:
        print("\nTo interact with smart contracts, please set MODULE_ADDRESS in your .env file")


if __name__ == "__main__":
    asyncio.run(main())

Next Steps

Now that you understand the basics of using the Python SDK with Movement:

  • Explore More Functions: The SDK supports many more operations like multi-signature transactions, token transfers, and complex smart contract interactions
  • Error Handling: Implement robust error handling for production applications
  • Testing: Write comprehensive tests for your smart contract interactions
  • Security: Never hardcode private keys in production code - use secure key management solutions

Additional Resources

This completes the Python SDK tutorial for interacting with the Movement network. The SDK provides a powerful and flexible way to build Python applications that interact with Movement smart contracts.