This guide helps you migrate from the old aptos-sdk crate (located in the aptos-labs/aptos-core repository) to the new aptos-sdk crate published on crates.io.
Important: These changes require immediate attention when migrating.
Old SDK - Required git dependency with patches and rustflags:
# Cargo.toml
[dependencies]
aptos-sdk = { git = "https://github.com/aptos-labs/aptos-core", branch = "devnet" }
[patch.crates-io]
merlin = { git = "https://github.com/aptos-labs/merlin" }
x25519-dalek = { git = "https://github.com/aptos-labs/x25519-dalek", branch = "zeroize_v1" }# .cargo/config.toml (required)
[build]
rustflags = ["--cfg", "tokio_unstable"]New SDK - Clean crates.io dependency:
# Cargo.toml
[dependencies]
aptos-sdk = "0.6.0"No .cargo/config.toml or patches required.
| Old SDK | New SDK |
|---|---|
LocalAccount |
Ed25519Account |
The new SDK uses explicit account type names reflecting the signature scheme:
// Old
use aptos_sdk::types::LocalAccount;
let account = LocalAccount::generate(&mut rand::rngs::OsRng);
// New
use aptos_sdk::account::Ed25519Account;
let account = Ed25519Account::generate();Old SDK - Required explicit random number generator:
let mut rng = rand::rngs::OsRng;
let account = LocalAccount::generate(&mut rng);New SDK - RNG handled internally:
let account = Ed25519Account::generate();Old SDK - Required mutable reference for signing:
coin_client.transfer(&mut alice, bob.address(), 1_000, None).await?;New SDK - Immutable references (signing doesn't mutate account):
aptos.transfer_apt(&sender, recipient.address(), 1_000).await?;Old SDK - Multiple separate clients:
use aptos_sdk::{
coin_client::CoinClient,
rest_client::{Client, FaucetClient},
types::LocalAccount,
};
let rest_client = Client::new(node_url);
let faucet_client = FaucetClient::new(faucet_url, node_url);
let coin_client = CoinClient::new(&rest_client);New SDK - Single unified client:
use aptos_sdk::{Aptos, AptosConfig, account::Ed25519Account};
let aptos = Aptos::new(AptosConfig::testnet())?;
// All functionality available through `aptos`Old SDK:
use aptos_sdk::{
coin_client::CoinClient,
rest_client::{Client, FaucetClient},
types::LocalAccount,
};
use url::Url;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let node_url = Url::parse("https://fullnode.devnet.aptoslabs.com")?;
let faucet_url = Url::parse("https://faucet.devnet.aptoslabs.com")?;
let rest_client = Client::new(node_url.clone());
let faucet_client = FaucetClient::new(faucet_url, node_url);
let coin_client = CoinClient::new(&rest_client);
let mut alice = LocalAccount::generate(&mut rand::rngs::OsRng);
let bob = LocalAccount::generate(&mut rand::rngs::OsRng);
faucet_client.fund(alice.address(), 100_000_000).await?;
faucet_client.create_account(bob.address()).await?;
let txn_hash = coin_client
.transfer(&mut alice, bob.address(), 1_000, None)
.await?;
rest_client.wait_for_transaction(&txn_hash).await?;
let balance = coin_client.get_account_balance(&bob.address()).await?;
println!("Bob's balance: {}", balance);
Ok(())
}New SDK:
use aptos_sdk::{Aptos, AptosConfig, account::Ed25519Account};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let aptos = Aptos::new(AptosConfig::devnet())?;
let sender = Ed25519Account::generate();
let recipient = Ed25519Account::generate();
aptos.fund_account(sender.address(), 100_000_000).await?;
aptos.transfer_apt(&sender, recipient.address(), 1_000).await?;
let balance = aptos.get_balance(recipient.address()).await?;
println!("Recipient balance: {}", balance);
Ok(())
}| Old SDK | New SDK | Notes |
|---|---|---|
LocalAccount |
Ed25519Account |
Default Ed25519 account |
LocalAccount::generate(&mut rng) |
Ed25519Account::generate() |
No RNG needed |
LocalAccount::from_private_key() |
Ed25519Account::from_private_key_hex() |
Accepts hex string |
account.address() |
account.address() |
Same |
account.public_key() |
account.public_key() |
Same |
| Old SDK | New SDK | Notes |
|---|---|---|
Client::new(url) |
Aptos::new(AptosConfig::custom(url)?) |
Unified client |
Client::new(devnet_url) |
Aptos::devnet()? |
Preset for devnet |
Client::new(testnet_url) |
Aptos::testnet()? |
Preset for testnet |
Client::new(mainnet_url) |
Aptos::mainnet()? |
Preset for mainnet |
| N/A | Aptos::local()? |
New: local network preset |
| Old SDK | New SDK | Notes |
|---|---|---|
FaucetClient::new(faucet_url, node_url) |
Built into Aptos |
No separate client |
faucet_client.fund(address, amount) |
aptos.fund_account(address, amount) |
Waits for confirmation |
faucet_client.create_account(address) |
aptos.fund_account(address, 0) |
Fund with 0 to create |
| N/A | aptos.create_funded_account(amount) |
New: generate + fund |
| Old SDK | New SDK | Notes |
|---|---|---|
CoinClient::new(&rest_client) |
Built into Aptos |
No separate client |
coin_client.transfer(&mut account, ...) |
aptos.transfer_apt(&account, ...) |
Immutable reference |
coin_client.get_account_balance(&addr) |
aptos.get_balance(addr) |
Simpler API |
| N/A | aptos.transfer_coin(&account, addr, type, amt) |
New: generic coin transfer |
| Old SDK | New SDK | Notes |
|---|---|---|
rest_client.submit(txn) |
aptos.submit_transaction(&signed_txn) |
Same concept |
rest_client.wait_for_transaction(&hash) |
aptos.submit_and_wait(&signed_txn, timeout) |
Combined submit+wait |
rest_client.simulate(txn) |
aptos.simulate(&account, payload) |
Enhanced result parsing |
| N/A | aptos.simulate_and_submit(&account, payload) |
New: dry-run then submit |
| Old SDK | New SDK | Notes |
|---|---|---|
transaction_factory.payload(...) |
TransactionBuilder::new().payload(...) |
Fluent builder |
| Manual sequence number lookup | aptos.build_transaction(&account, payload) |
Auto-fetches seq num |
| Manual gas price lookup | aptos.build_transaction(&account, payload) |
Auto-fetches gas price |
| Old SDK | New SDK | Notes |
|---|---|---|
rest_client.get_account(addr) |
aptos.fullnode().get_account(addr) |
Via fullnode client |
rest_client.get_account_resources(addr) |
aptos.fullnode().get_account_resources(addr) |
Via fullnode client |
rest_client.get_account_resource(addr, type) |
aptos.fullnode().get_account_resource(addr, type) |
Via fullnode client |
// Old: LocalAccount with explicit RNG
use aptos_sdk::types::LocalAccount;
let account = LocalAccount::generate(&mut rand::rngs::OsRng);
// New: Ed25519Account (default), Secp256k1Account, or Secp256r1Account
use aptos_sdk::account::Ed25519Account;
let account = Ed25519Account::generate();
// From private key (old)
let account = LocalAccount::from_private_key(private_key, 0);
// From private key (new)
let account = Ed25519Account::from_private_key_hex("0x...")?;
// Or from bytes:
let account = Ed25519Account::from_private_key_bytes(&bytes)?;// Old: Manual URL construction
use url::Url;
let url = Url::parse("https://fullnode.testnet.aptoslabs.com")?;
let client = Client::new(url);
// New: Network presets
let aptos = Aptos::new(AptosConfig::testnet())?;
// Or shortcuts:
let aptos = Aptos::testnet()?;
let aptos = Aptos::mainnet()?;
let aptos = Aptos::devnet()?;
let aptos = Aptos::local()?;
// Custom URL (new)
let aptos = Aptos::new(AptosConfig::custom("https://my-node.example.com/v1")?)?;
// Access raw fullnode client when needed
let fullnode = aptos.fullnode();
let resources = fullnode.get_account_resources(address).await?;// Old: transaction_factory approach
let payload = aptos_sdk::transaction_builder::aptos_stdlib::aptos_coin_transfer(
recipient,
amount,
);
let raw_txn = transaction_factory
.payload(payload)
.sender(sender.address())
.sequence_number(seq_num)
.build();
// New: TransactionBuilder with helpers
use aptos_sdk::transaction::{EntryFunction, TransactionBuilder};
// Simple transfer (automatic building)
aptos.transfer_apt(&sender, recipient, amount).await?;
// Manual building with TransactionBuilder
let payload = EntryFunction::apt_transfer(recipient, amount)?;
let raw_txn = TransactionBuilder::new()
.sender(sender.address())
.sequence_number(seq_num)
.payload(payload.into())
.chain_id(aptos.chain_id())
.max_gas_amount(100_000)
.gas_unit_price(100)
.expiration_from_now(600)
.build()?;
// Or let the SDK handle sequence number and gas
let raw_txn = aptos.build_transaction(&sender, payload.into()).await?;Both SDKs use the same underlying BCS serialization:
// Both SDKs
use aptos_bcs;
let bytes = aptos_bcs::to_bytes(&value)?;
let value: MyType = aptos_bcs::from_bytes(&bytes)?;Move types are available in the new SDK:
use aptos_sdk::types::{MoveStructTag, MoveType, TypeTag};The new SDK includes features not available in the old SDK:
⚠️ Secp256r1 caveat:Secp256r1Accountis deprecated for on-chain transaction signing and is retained for off-chain use only (raw P-256 sign/verify, key interop). Baresecp256r1signatures are rejected by Aptos validators because the on-chainAnySignaturevariant 2 isWebAuthn, not bareSecp256r1Ecdsa. For WebAuthn/passkey (P-256) signing of Aptos transactions, use [WebAuthnAccount] instead — it reuses the same key material and emits the correct WebAuthn-envelope wire format.
use aptos_sdk::account::{Ed25519Account, Secp256k1Account, WebAuthnAccount};
// Ed25519 (default, most common)
let ed25519_account = Ed25519Account::generate();
// Secp256k1 (Ethereum-compatible)
let secp256k1_account = Secp256k1Account::generate();
// Secp256r1 / P-256 for on-chain signing: use WebAuthnAccount (the supported
// path). `Secp256r1Account` is deprecated for transactions (off-chain only).
let webauthn_account = WebAuthnAccount::generate();Create accounts that require M-of-N signatures from keys of different types:
use aptos_sdk::account::{AnyPrivateKey, MultiKeyAccount};
use aptos_sdk::crypto::{Ed25519PrivateKey, Secp256k1PrivateKey};
// Create keys of different types
let ed25519_key = Ed25519PrivateKey::generate();
let secp256k1_key = Secp256k1PrivateKey::generate();
// Create a 2-of-2 multi-key account
let multi_key = MultiKeyAccount::new(
vec![
AnyPrivateKey::ed25519(ed25519_key),
AnyPrivateKey::secp256k1(secp256k1_key),
],
2, // threshold
)?;
// Use like any other account
aptos.transfer_apt(&multi_key, recipient, amount).await?;Not currently available. Keyless (OIDC) account support was removed from the Rust SDK because the previous implementation was chain-incompatible (wrong auth-key scheme, wrong authenticator variant tag, non-AIP-61 address derivation, and a service-API mismatch). It will be re-added once implemented correctly. Track the design in
crates/aptos-sdk/feature-plans/09-keyless-accounts.md. If you need keyless today, use the TypeScript SDK.
Let a third party pay for transaction gas fees:
use aptos_sdk::transaction::{
EntryFunction, TransactionBuilder,
builder::sign_fee_payer_transaction,
types::FeePayerRawTransaction,
};
// Build the transaction
let payload = EntryFunction::apt_transfer(recipient, amount)?;
let raw_txn = TransactionBuilder::new()
.sender(sender.address())
.sequence_number(seq_num)
.payload(payload.into())
.chain_id(aptos.chain_id())
.expiration_from_now(600)
.build()?;
// Create fee payer transaction
let fee_payer_txn = FeePayerRawTransaction::new_simple(raw_txn, fee_payer.address());
// Sign with both sender and fee payer
let signed = sign_fee_payer_transaction(
&fee_payer_txn,
&sender, // Original sender
&[], // Secondary signers (if any)
&fee_payer, // Fee payer
)?;
aptos.submit_and_wait(&signed, None).await?;Test transactions before submission:
// Simulate a transaction
let result = aptos.simulate(&sender, payload.into()).await?;
if result.success() {
println!("Gas needed: {}", result.gas_used());
println!("Events: {:?}", result.events());
} else {
println!("Would fail: {}", result.error_message().unwrap_or_default());
}
// Get gas estimate with safety margin
let estimated_gas = aptos.estimate_gas(&sender, payload.into()).await?;
// Simulate then submit if successful
let result = aptos.simulate_and_submit(&sender, payload.into()).await?;Submit multiple transactions efficiently:
// Batch multiple transfers
let results = aptos.batch_transfer_apt(&sender, vec![
(addr1, 1_000_000),
(addr2, 2_000_000),
(addr3, 3_000_000),
]).await?;
// Or batch arbitrary payloads
let payloads = vec![payload1, payload2, payload3];
let results = aptos.submit_batch_and_wait(&sender, payloads, None).await?;Query indexed blockchain data via GraphQL:
// Requires `indexer` feature (default)
if let Some(indexer) = aptos.indexer() {
// `query::<T>(query: &str, variables: Option<serde_json::Value>)` returns
// the deserialized `data` payload. Pass a type annotation so `T` is known,
// borrow the query string, and supply optional GraphQL variables.
let my_query = r#"query { ledger_infos { chain_id } }"#;
let result: serde_json::Value = indexer.query(my_query, None).await?;
}Optimize for different workloads:
use aptos_sdk::config::PoolConfig;
// High-throughput applications
let config = AptosConfig::testnet()
.with_pool(PoolConfig::high_throughput());
// Low-latency applications
let config = AptosConfig::testnet()
.with_pool(PoolConfig::low_latency());
// Constrained environments
let config = AptosConfig::testnet()
.with_pool(PoolConfig::minimal());
// Custom configuration
let config = AptosConfig::testnet()
.with_pool(PoolConfig::builder()
.max_idle_per_host(16)
.max_idle_total(64)
.idle_timeout(Duration::from_secs(60))
.build());Handle transient failures automatically:
use aptos_sdk::retry::RetryConfig;
// Aggressive retries for development
let config = AptosConfig::testnet()
.with_retry(RetryConfig::aggressive());
// Conservative retries for production
let config = AptosConfig::mainnet()
.with_retry(RetryConfig::conservative());
// Disable retries
let config = AptosConfig::testnet()
.without_retry();The new SDK uses feature flags for modular compilation. Only include what you need:
[dependencies]
# Default features (recommended for most users)
aptos-sdk = "0.6.0"
# Minimal configuration (smaller binary)
aptos-sdk = { version = "0.6.0", default-features = false, features = ["ed25519"] }
# Full features
aptos-sdk = { version = "0.6.0", features = ["full"] }| Feature | Default | Description |
|---|---|---|
ed25519 |
Yes | Ed25519 signature scheme |
secp256k1 |
Yes | Secp256k1 ECDSA (Ethereum-compatible) |
secp256r1 |
Yes | Secp256r1/P-256 ECDSA (WebAuthn/passkey) |
mnemonic |
Yes | BIP-39 mnemonic phrase support |
indexer |
Yes | GraphQL indexer client |
faucet |
Yes | Testnet faucet integration |
bls |
No | BLS12-381 signatures |
macros |
No | Proc macros for type-safe contract bindings |
For minimal binary size, disable default features:
[dependencies]
aptos-sdk = { version = "0.6.0", default-features = false, features = ["ed25519", "faucet"] }"Cannot find type LocalAccount"
// Old
use aptos_sdk::types::LocalAccount;
// New
use aptos_sdk::account::Ed25519Account;"Method transfer not found"
// Old
coin_client.transfer(&mut alice, bob.address(), 1_000, None).await?;
// New
aptos.transfer_apt(&sender, recipient.address(), 1_000).await?;"Expected mutable reference"
Account references are now immutable. Remove mut:
// Old
let mut account = ...;
client.method(&mut account, ...);
// New
let account = ...;
aptos.method(&account, ...);"Feature xyz not enabled"
Enable the required feature in Cargo.toml:
[dependencies]
aptos-sdk = { version = "0.6.0", features = ["ed25519", "secp256k1", "faucet"] }"Cannot find CoinClient/FaucetClient"
These are now integrated into the main Aptos client:
// Old
let faucet_client = FaucetClient::new(...);
let coin_client = CoinClient::new(...);
// New - all methods on Aptos
let aptos = Aptos::testnet()?;
aptos.fund_account(...).await?; // Was faucet_client.fund()
aptos.transfer_apt(...).await?; // Was coin_client.transfer()
aptos.get_balance(...).await?; // Was coin_client.get_account_balance()"Faucet not available"
Faucet is only available on testnet/devnet/local:
// This works
let aptos = Aptos::testnet()?;
aptos.fund_account(address, amount).await?;
// This won't have a faucet
let aptos = Aptos::mainnet()?;
// aptos.fund_account(...) // Error: faucet not available"Account not found"
New accounts must be created/funded before use:
let account = Ed25519Account::generate();
// Fund the account first
aptos.fund_account(account.address(), 100_000_000).await?;
// Now you can use it
aptos.transfer_apt(&account, recipient, amount).await?;