This guide covers migrating from the v1 API (aptos_sdk) to the v2 API (aptos_sdk.v2).
Both APIs are available in the same package — you can migrate incrementally. The v1 SDK remains fully functional and unchanged; v2 is shipped as a subpackage with its own independent implementation.
- Python 3.12+ (was 3.10+ in v1)
- New v2 dependencies:
coincurve,bip-utils,aiohttp - v1 retains its own dependencies (
cryptography,httpx) — no changes needed
# ── v1 ──
from aptos_sdk.async_client import RestClient
from aptos_sdk.account import Account
from aptos_sdk.account_address import AccountAddress
client = RestClient("https://fullnode.devnet.aptoslabs.com/v1")
alice = Account.generate()
balance = await client.account_balance(alice.address())
await client.close()
# ── v2 ──
from aptos_sdk.v2 import Aptos, AptosConfig, Network, Account
async with Aptos(AptosConfig(network=Network.DEVNET)) as aptos:
alice = Account.generate()
balance = await aptos.account.get_balance(alice.address)| v1 | v2 |
|---|---|
from aptos_sdk.async_client import RestClient |
from aptos_sdk.v2 import Aptos, AptosConfig |
from aptos_sdk.account import Account |
from aptos_sdk.v2 import Account |
from aptos_sdk.account_address import AccountAddress |
from aptos_sdk.v2 import AccountAddress |
from aptos_sdk.ed25519 import PrivateKey |
from aptos_sdk.v2.crypto import Ed25519PrivateKey |
from aptos_sdk.secp256k1_ecdsa import PrivateKey |
from aptos_sdk.v2.crypto import Secp256k1PrivateKey |
from aptos_sdk.bcs import Serializer, Deserializer |
from aptos_sdk.v2.bcs import Serializer, Deserializer |
from aptos_sdk.transactions import EntryFunction, ... |
from aptos_sdk.v2.transactions import EntryFunction, ... |
from aptos_sdk.type_tag import TypeTag, StructTag |
from aptos_sdk.v2.types import TypeTag, StructTag |
v1 — URL string + optional ClientConfig:
config = ClientConfig()
config.max_gas_amount = 200_000
client = RestClient("https://fullnode.devnet.aptoslabs.com/v1", config)
# ...
await client.close()v2 — AptosConfig with Network enum + async context manager:
config = AptosConfig(
network=Network.DEVNET, # or MAINNET, TESTNET, LOCAL, CUSTOM
max_gas_amount=200_000,
gas_unit_price=100,
expiration_ttl=600,
transaction_wait_secs=20,
max_retries=3,
api_key=None,
)
async with Aptos(config) as aptos:
# ...The async with pattern ensures the HTTP session is closed. You can also call await aptos.close() manually.
| Operation | v1 | v2 |
|---|---|---|
| Generate Ed25519 | Account.generate() |
Account.generate() |
| Generate Secp256k1 | Account.generate_secp256k1_ecdsa() |
Account.generate_secp256k1() |
| From private key hex | Account.load_key("0x...") |
Account.from_private_key(Ed25519PrivateKey.from_str("0x...")) |
| From JSON file | Account.load(path) |
(not built-in — deserialize manually) |
| From mnemonic | (not available) | Account.from_mnemonic("word1 word2 ...") |
| Get address | account.address() (method) |
account.address (property) |
| Get public key | account.public_key() (method) |
account.public_key (property) |
| Get private key | account.private_key (attribute) |
account.private_key (property) |
| v1 | v2 |
|---|---|
ed25519.PrivateKey |
Ed25519PrivateKey |
ed25519.PublicKey |
Ed25519PublicKey |
ed25519.Signature |
Ed25519Signature |
ed25519.PrivateKey.random() |
Ed25519PrivateKey.generate() |
secp256k1_ecdsa.PrivateKey |
Secp256k1PrivateKey |
secp256k1_ecdsa.PublicKey |
Secp256k1PublicKey |
secp256k1_ecdsa.Signature |
Secp256k1Signature |
secp256k1_ecdsa.PrivateKey.random() |
Secp256k1PrivateKey.generate() |
Both v1 and v2 support AIP-80 formatting (key.aip80()) and parsing (from_str("ed25519-priv-0x...")).
v1 — all methods on RestClient:
info = await client.account(address)
balance = await client.account_balance(address)
seq = await client.account_sequence_number(address)
resource = await client.account_resource(address, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>")
resources = await client.account_resources(address)
modules = await client.account_modules(address)
chain_id = await client.chain_id()
ledger = await client.info()
block = await client.blocks_by_height(100, with_transactions=True)
table_item = await client.get_table_item(handle, key_type, value_type, key)v2 — domain-specific API accessors on Aptos:
info = await aptos.account.get_info(address)
balance = await aptos.account.get_balance(address)
seq = await aptos.account.get_sequence_number(address)
resource = await aptos.account.get_resource(address, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>")
resources = await aptos.account.get_resources(address)
modules = await aptos.account.get_modules(address)
chain_id = await aptos.general.get_chain_id()
ledger = await aptos.general.get_ledger_info()
block = await aptos.general.get_block_by_height(100, with_transactions=True)
table_item = await aptos.general.get_table_item(handle, key_type, value_type, key)Note: v2 uses the
/accounts/{address}/balance/{asset_type}endpoint for balances instead of the deprecatedCoinStoreresource lookup. This works with both legacy coins and fungible assets.
v1 — manual pipeline:
payload = EntryFunction.natural(
"0x1::coin", "transfer",
[TypeTag(StructTag.from_str("0x1::aptos_coin::AptosCoin"))],
[TransactionArgument(recipient, Serializer.struct),
TransactionArgument(amount, Serializer.u64)],
)
raw_txn = RawTransaction(
sender.address(), seq_num, TransactionPayload(payload),
max_gas, gas_price, expiration, chain_id,
)
authenticator = sender.sign_transaction(raw_txn)
signed_txn = SignedTransaction(raw_txn, authenticator)
txn_hash = await client.submit_bcs_transaction(signed_txn)
await client.wait_for_transaction(txn_hash)v2 — high-level helpers:
# One-liner for coin transfers:
txn_hash = await aptos.coin.transfer(sender, recipient.address, amount)
await aptos.transaction.wait_for_transaction(txn_hash)
# Or manual pipeline with automatic sequence number / chain ID:
payload = EntryFunction.natural(
"0x1::aptos_account", "transfer_coins",
[TypeTag(StructTag.from_str("0x1::aptos_coin::AptosCoin"))],
[TransactionArgument(recipient.address, Serializer.struct),
TransactionArgument(amount, Serializer.u64)],
)
raw_txn = await aptos.transaction.build(
sender=sender.address,
payload=TransactionPayload(payload),
)
txn_hash = await aptos.transaction.sign_and_submit(raw_txn, sender)
await aptos.transaction.wait_for_transaction(txn_hash)Note: v2 uses
aptos_account::transfer_coinsinstead ofcoin::transfer. This automatically creates the recipient'sCoinStoreif it doesn't exist, avoidingECOIN_STORE_NOT_PUBLISHEDerrors.
v2 provides a dedicated FungibleAssetApi for working with the Fungible Asset (FA) standard:
# Transfer a fungible asset
txn_hash = await aptos.fungible_asset.transfer(
sender=alice,
metadata_address=fa_metadata_addr, # address of the FA metadata object
recipient=bob.address,
amount=1_000,
)
# Check balance
balance = await aptos.fungible_asset.balance(bob.address, fa_metadata_addr)v1:
result = await client.simulate_transaction(raw_txn, sender)v2:
result = await aptos.transaction.simulate(raw_txn, sender.public_key)v1:
result = await client.view(
"0x1::coin::balance",
["0x1::aptos_coin::AptosCoin"],
[str(address)],
)v2 — JSON arguments:
result = await aptos.general.view(
"0x1::coin", "balance",
["0x1::aptos_coin::AptosCoin"],
[str(address)],
)v2 — BCS-encoded arguments (for complex types like vectors, options):
from aptos_sdk.v2.bcs import Serializer
ser = Serializer()
ser.sequence([1, 2, 3], Serializer.u64)
bcs_args = ser.output()
result_bytes = await aptos.general.view_bcs(
"0x1::some_module", "some_function",
[], # type arguments
bcs_args, # BCS-encoded arguments
)v1:
from aptos_sdk.async_client import FaucetClient
faucet = FaucetClient("https://faucet.devnet.aptoslabs.com", client)
await faucet.fund_account(address, 100_000_000)v2:
await aptos.faucet.fund_account(address, 100_000_000)| v1 | v2 |
|---|---|
aptos_sdk.async_client.ApiError |
aptos_sdk.v2.errors.ApiError |
aptos_sdk.async_client.AccountNotFound |
aptos_sdk.v2.errors.AccountNotFoundError |
aptos_sdk.async_client.ResourceNotFound |
aptos_sdk.v2.errors.ResourceNotFoundError |
aptos_sdk.async_client.TransactionTimeout |
aptos_sdk.v2.errors.TransactionTimeoutError |
aptos_sdk.async_client.TransactionFailed |
aptos_sdk.v2.errors.TransactionFailedError |
aptos_sdk.errors.DeserializationError |
aptos_sdk.v2.errors.BcsDeserializationError |
aptos_sdk.errors.SerializationError |
aptos_sdk.v2.errors.BcsSerializationError |
aptos_sdk.errors.InvalidKeyError |
aptos_sdk.v2.errors.InvalidKeyError |
| (no base class) | aptos_sdk.v2.errors.AptosError (catches all) |
v2 has a structured error hierarchy — catch AptosError to catch everything, or be specific:
from aptos_sdk.v2.errors import (
AptosError, # base for all SDK errors
ApiError, # HTTP errors (has .status_code)
AccountNotFoundError, # 404 for account
ResourceNotFoundError, # 404 for resource
TransactionTimeoutError, # wait exceeded timeout
TransactionFailedError, # committed but VM failed (has .vm_status)
BcsSerializationError, # BCS encoding error
BcsDeserializationError, # BCS decoding error
InvalidKeyError, # bad key format
InvalidSignatureError, # bad signature
InvalidMnemonicError, # bad mnemonic phrase
InvalidAddressError, # malformed address
InvalidTypeTagError, # unparseable type tag
)v1:
from aptos_sdk.account_address import AccountAddress
address = AccountAddress.from_key(public_key)
resource_addr = AccountAddress.for_resource_account(creator, seed)
object_addr = AccountAddress.for_named_object(creator, seed)v2:
from aptos_sdk.v2.crypto import AuthenticationKey
from aptos_sdk.v2.types import AccountAddress
auth_key = AuthenticationKey.from_public_key(public_key)
address = auth_key.account_address()
resource_addr = AccountAddress.for_resource_account(creator, seed)
object_addr = AccountAddress.for_named_object(creator, seed)Note:
AuthenticationKey.from_public_keyaccepts Ed25519, Secp256k1, and AnyPublicKey types. Secp256k1 keys are automatically wrapped inAnyPublicKeyfor single-key authentication.
from aptos_sdk.v2.crypto import (
generate_mnemonic,
validate_mnemonic,
derive_ed25519_private_key,
derive_secp256k1_private_key,
)
from aptos_sdk.v2 import Account
# Generate a mnemonic
phrase = generate_mnemonic() # 12 words
phrase = generate_mnemonic(24) # 24 words
assert validate_mnemonic(phrase)
# Derive account from mnemonic (default: Ed25519)
account = Account.from_mnemonic(phrase)
# Derive Secp256k1 account
account = Account.from_mnemonic(phrase, secp256k1=True)
# Derive raw keys with custom BIP-44 path
key = derive_ed25519_private_key(phrase, "m/44'/637'/0'/0'/0'")
key = derive_ed25519_private_key(phrase, "m/44'/637'/1'/0'/0'") # account index 1The Serializer and Deserializer APIs are mostly identical between v1 and v2. Update the import path:
# v1
from aptos_sdk.bcs import Serializer, Deserializer
# v2
from aptos_sdk.v2.bcs import Serializer, DeserializerNew in v2 — signed integer support:
ser = Serializer()
ser.i8(-1)
ser.i16(-256)
ser.i32(-100_000)
ser.i64(-1_000_000_000)
ser.i128(-1)
ser.i256(-1)
deser = Deserializer(ser.output())
assert deser.i8() == -1
assert deser.i16() == -256
assert deser.i32() == -100_000
assert deser.i64() == -1_000_000_000
assert deser.i128() == -1
assert deser.i256() == -1v1 uses the cryptography library for Secp256k1. v2 uses coincurve (a Python binding for libsecp256k1). This is faster and produces identical signatures.
# v1 (cryptography)
from cryptography.hazmat.primitives.asymmetric import ec
key = ec.generate_private_key(ec.SECP256K1())
pk = secp256k1_ecdsa.PrivateKey(key)
# v2 (coincurve)
from aptos_sdk.v2.crypto import Secp256k1PrivateKey
pk = Secp256k1PrivateKey.generate()
# Or from hex/AIP-80 (v2):
pk = Secp256k1PrivateKey.from_str("0xdead...")
pk = Secp256k1PrivateKey.from_str("secp256k1-priv-0xdead...")AptosConfig+Networkenum for configurationAptosasync context manager with lazy API initializationCoinApiandFungibleAssetApihigh-level helpersFaucetApibuilt into theAptosfacade- BIP-39 mnemonic generation and key derivation
coincurvefor faster Secp256k1- Structured error hierarchy with
AptosErrorbase - HTTP retry with exponential backoff (configurable
max_retries) - BCS-encoded view functions (
view_bcs) for complex parameter types - Signed integer BCS support (
i8–i256) - Orderless transactions (
TransactionInnerPayloadwith replay-protection nonce)
IndexerClient(GraphQL)FaucetClient(standalone)- Token clients (
aptos_token_client,aptos_tokenv1_client) TransactionWorker(batch submission)AccountSequenceNumber(automatic sequence number management)PackagePublisher- CLI wrappers (
aptos_cli_wrapper,cli) MultiPublicKey/MultiSignature(multi-ed25519)ledger_versionparameter on query methodsAccount.load()/Account.store()(JSON file persistence)
You don't need to migrate everything at once. Both APIs coexist in the same package:
# Mix v1 and v2 in the same codebase
from aptos_sdk.v2 import Aptos, AptosConfig, Network, Account as V2Account
from aptos_sdk.aptos_token_client import AptosTokenClient # v1-only feature
async with Aptos(AptosConfig(network=Network.DEVNET)) as aptos:
alice = V2Account.generate()
await aptos.faucet.fund_account(alice.address, 100_000_000)
# Use v1 token client for NFT operations not yet in v2
token_client = AptosTokenClient(rest_client)
# ...v1 and v2 are fully independent — they do not share runtime state, so there are no cross-version side effects.