Movement 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.