Skip to content
🎉 Welcome to the new Aptos Docs preview! Click here to submit feedback!
Build
Use Hardware Ledger

Use Hardware Ledger via the Aptos CLI

Using a hardware wallet like Ledger is the most secure way to sign transactions on mainnet as your private key never leaves your device.

⚠️

The Ledger Nano S has limited memory and may not be able to sign many transactions on Aptos. If you are trying to sign a transaction that is too big for your device to handle, you will get the error Wrong raw transaction length.

Initial Setup

You will need to do a few steps of configuration for the Aptos CLI and your Ledger device to sign transactions.

Ensure you have the Aptos CLI installed.

You can install the Aptos CLI by following these steps if you have not done so already.

Ensure you have done the basic setup for your Ledger device.

You can find those steps on Ledger’s website. For example, here are the set up instructions for the Ledger Nano X.

Plug your Ledger device into your computer.

Install the Aptos App on your Ledger device by following this guide.

Unlock your Ledger device and open the Aptos app.

ℹ️

Whenever you want to sign using your Ledger you will need to plug it in, unlock it, and open the Aptos app before running any CLI commands.

Create a new Ledger profile in the Aptos CLI

Terminal
aptos init --profile <your-profile> --ledger

Then follow the terminal prompts like so:

Terminal
Configuring for profile <your-profile>
Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet]
 
No network given, using devnet...
Please choose an index from the following 5 ledger accounts, or choose an arbitrary index that you want to use:
[0] Derivation path: m/44'/637'/0'/0'/0' (Address: 59836ba1dd0c845713bdab34346688d6f1dba290dbf677929f2fc20593ba0cfb)
[1] Derivation path: m/44'/637'/1'/0'/0' (Address: 21563230cf6d69ee72a51d21920430d844ee48235e708edbafbc69708075a86e)
[2] Derivation path: m/44'/637'/2'/0'/0' (Address: 667446181b3b980ef29f5145a7a2cc34d433fc3ee8c97fc044fd978435f2cb8d)
[3] Derivation path: m/44'/637'/3'/0'/0' (Address: 2dcf037a9f31d93e202c074229a1b69ea8ee4d2f2d63323476001c65b0ec4f31)
[4] Derivation path: m/44'/637'/4'/0'/0' (Address: 23c579a9bdde1a59f1c9d36d8d379aeefe7a5997b5b58bd5a5b0c12a4f170431)
 
0
Account 59836ba1dd0c845713bdab34346688d6f1dba290dbf677929f2fc20593ba0cfb has been already found on-chain
 
---
Aptos CLI is now set up for account 59836ba1dd0c845713bdab34346688d6f1dba290dbf677929f2fc20593ba0cfb as profile <your-profile>!  Run `aptos --help` for more information about commands
{
  "Result": "Success"
}

In the example, they chose to use the first ledger account by entering 0 after the aptos init command. You may choose whichever account you want.

Common errors:

  1. If you see the error Device Not Found, make sure to unlock your Ledger then try this step again.
  2. If you see the error Aptos ledger app is not opened, make sure to open the Aptos app on your Ledger, then try this step again.

Finally, you will need to enable blind signing on your Ledger device by following these steps.

  1. Blind signing allows you to confirm a smart contract interaction you cannot verify through a human-readable language.
  2. This is needed to execute transactions without limitation as some payloads are too big to display.

Signing Using Ledger

After doing the initial setup, you can sign transactions by following these steps:

  1. Plug in your ledger.
  2. Unlock it.
  3. Open the Aptos app.
  4. Run the Aptos CLI command which requires a signature.
ℹ️

This process works for any command that requires a signature, whether that’s to transfer coins, publish a Move contract, interact with a contract, etc.

For example, if you wanted to publish a Move package like the [hello_blockchain](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain) demo contract you could follow the above steps then run:

Terminal
aptos move publish --profile <your-ledger-profile-name> --named-addresses hello_blockchain=<your-ledger-profile-name>

You should see a response like:

Terminal
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING Examples
package size 1755 bytes
Do you want to submit a transaction for a range of [139600 - 209400] Octas at a gas unit price of 100 Octas? [yes/no] >
 
yes
 
{
  "Result": {
    "transaction_hash": "0xd5a12594f85284cfd5518d547d084030b178ee926fa3d8cbf699cc0596eff538",
    "gas_used": 1396,
    "gas_unit_price": 100,
    "sender": "59836ba1dd0c845713bdab34346688d6f1dba290dbf677929f2fc20593ba0cfb",
    "sequence_number": 0,
    "success": true,
    "timestamp_us": 1689887104333038,
    "version": 126445,
    "vm_status": "Executed successfully"
  }
}
 

After you have approved publishing this package you will be prompted to sign the transaction on your Ledger device. Once signed, the package will be published to the network!

One error you might run into is Error: Wrong raw transaction length. This means that the transaction or package size was too big for your device to sign. Currently the Aptos Ledger app can only support transactions that are smaller than 20kb. The Ledger Nano S device has less memory than that, which is why it is more likely to produce this error.

Authentication key rotation

If you have an active account that is not secured using a hardware wallet, then you may wish to rotate the account’s authentication key so that it is linked with your Ledger.

Alternatively, if you have an account linked with a Ledger hardware wallet that you wish to publish a large package from, you might want to temporarily rotate the account’s authentication key to a hot key.

This tutorial will walk you through both scenarios.

🧠

Before you start this tutorial make sure you have jq, a lightweight and flexible command-line JSON processor:

Terminal
jq --version

Start a localnet

You can run this command at any point to restart a fresh localnet:

Terminal
mkdir -p localnet-data
aptos node run-localnet \
    --assume-yes \
    --test-dir localnet-data \
    --force-restart

The localnet is ready when it prints out:

Terminal
Applying post startup steps...
 
Setup is complete, you can now use the localnet!

Move localnet to a background process

Open up a new terminal window to continue with the rest of the tutorial and leave the localnet running in the background.

💡

When you finally want to shut down the localnet, you can press Ctrl+C in the terminal window where you started the localnet.

Set up localnet hot wallet profile

In a fresh terminal window, set up a localnet profile for an account with a compromised private key:

Terminal
aptos init --profile localnet-a

When prompted, choose local as the network, then enter the following private key:

Terminal
0x44bbe12dd5e120d94f65fb3cdae1dc7017156a8f7da1ebfa876cabf48fa6f5e2

Once the profile is setup, store the account address for later:

Terminal
export ACCOUNT_A=0x$(
    aptos account lookup-address --profile localnet-a \
    | jq -r '.Result'
)
echo $ACCOUNT_A

This should output:

Terminal
0xaaaa29ce2bd42dec0bad87599d827c0bc565b2a83595489aa3dc3f5e43ebaaaa
🧠

For ease of identification this account has vanity address 0xaaaa...aaaa, which was generated via optivanity.

Inspect the account

Inspect the account’s Account resource:

Terminal
aptos account list --profile localnet-a

Note that the authentication key is the same as the account address:

Terminal
export AUTH_KEY_A=$(
    aptos account list --profile localnet-a | jq -r \
        '.Result[0]["0x1::account::Account"]["authentication_key"]'
)
echo $AUTH_KEY_A

This should also output:

Terminal
0xaaaa29ce2bd42dec0bad87599d827c0bc565b2a83595489aa3dc3f5e43ebaaaa

Set up localnet Ledger profile

Terminal
aptos init --profile localnet-b --ledger

When prompted, choose local as the network, then derivation path index 0.

Once the profile is setup, store the account address for later:

Terminal
export ACCOUNT_B=0x$(
    aptos account lookup-address --profile localnet-b \
    | jq -r '.Result'
)
echo $ACCOUNT_B

Check the authentication key for the account:

Terminal
export AUTH_KEY_B=$(
    aptos account list --profile localnet-b | jq -r \
        '.Result[0]["0x1::account::Account"]["authentication_key"]'
)
echo $AUTH_KEY_B

Rotate the authentication key of the hot wallet

Rotate the authentication key of the hot wallet via aptos_framework::account::rotate_authentication_key_call:

Terminal
aptos move run \
    --args "hex:$AUTH_KEY_B" \
    --profile localnet-a \
    --function-id 0x1::account::rotate_authentication_key_call

Check the new authentication key of the account:

Terminal
export NEW_AUTH_KEY_A=$(
    aptos account list --profile localnet-a | jq -r \
        '.Result[0]["0x1::account::Account"]["authentication_key"]'
)
echo $NEW_AUTH_KEY_A

This should match the authentication key of the localnet-b profile:

Terminal
echo $AUTH_KEY_B

Update the CLI profile

Since the vanity address account (localnet-a) now has the same authentication key as the Ledger account (localnet-b), you can sign transactions for vanity address account using the Ledger.

To make this process easier, you’ll want to update the corresponding profile in your CLI config.yaml file, which is typically stored at ~/.aptos/config.yaml.

The localnet-a profile should look something like this:

CLI Config
  localnet-a:
    private_key: "0x44bbe12dd5e120d94f65fb3cdae1dc7017156a8f7da1ebfa876cabf48fa6f5e2"
    public_key: "0xc4e7b2ff0d57d67a35c1605f1f3f2e4213723b1ff2a8426b9ca111f0fc51254d"
    account: aaaa29ce2bd42dec0bad87599d827c0bc565b2a83595489aa3dc3f5e43ebaaaa
    rest_url: "http://localhost:8080"
    faucet_url: "http://localhost:8081"

Identify the public_key and derivation_path for the localnet-b profile, then use them to update the localnet-a profile:

CLI Config
  localnet-a:
    private_key_before_rotation: "0x44bbe12dd5e120d94f65fb3cdae1dc7017156a8f7da1ebfa876cabf48fa6f5e2"
    public_key_before_rotation: "0xc4e7b2ff0d57d67a35c1605f1f3f2e4213723b1ff2a8426b9ca111f0fc51254d"
    public_key: <SAME_AS_FOR_LOCALNET_B>
    account: aaaa29ce2bd42dec0bad87599d827c0bc565b2a83595489aa3dc3f5e43ebaaaa
    rest_url: "http://localhost:8080"
    faucet_url: "http://localhost:8081"
    derivation_path: <SAME_AS_FOR_LOCALNET_B>
🧠

You’ll want to save the private key and public key from before the rotation for when you rotate back later. This is what private_key_before_rotation and public_key_before_rotation are for.

Sign a transfer transaction via Ledger

Send 1 octa from localnet-a to localnet-b:

Terminal
aptos move run \
    --args \
        address:$ACCOUNT_B \
        u64:1 \
    --profile localnet-a \
    --function-id 0x1::aptos_account::transfer

You’ll need to approve the transaction on your Ledger.

Rotate the authentication key back

Rotate the authentication key of the localnet-a account back to the original authentication key, thereby rendering it a hot wallet again:

Terminal
aptos move run \
    --args "hex:$AUTH_KEY_A" \
    --profile localnet-a \
    --function-id 0x1::account::rotate_authentication_key_call

You’ll need to approve the transaction on your Ledger.

Check the final authentication key of the account:

Terminal
export FINAL_AUTH_KEY_A=$(
    aptos account list --profile localnet-a | jq -r \
        '.Result[0]["0x1::account::Account"]["authentication_key"]'
)
echo $FINAL_AUTH_KEY_A

Revert the CLI profile to reflect the final authentication key and corresponding public key:

CLI Config
  localnet-a:
    private_key: "0x44bbe12dd5e120d94f65fb3cdae1dc7017156a8f7da1ebfa876cabf48fa6f5e2"
    public_key: "0xc4e7b2ff0d57d67a35c1605f1f3f2e4213723b1ff2a8426b9ca111f0fc51254d"
    account: aaaa29ce2bd42dec0bad87599d827c0bc565b2a83595489aa3dc3f5e43ebaaaa
    rest_url: "http://localhost:8080"
    faucet_url: "http://localhost:8081"

Sign a transfer transaction via hot wallet

Send another 1 octa transfer from localnet-a to localnet-b, which should authenticate via the private key stored in your CLI config:

Terminal
aptos move run \
    --args \
        address:$ACCOUNT_B \
        u64:1 \
    --profile localnet-a \
    --function-id 0x1::aptos_account::transfer