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
aptos init --profile <your-profile> --ledger
Then follow the terminal prompts like so:
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:
- If you see the error
Device Not Found
, make sure to unlock your Ledger then try this step again. - 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.
- Blind signing allows you to confirm a smart contract interaction you cannot verify through a human-readable language.
- 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:
- Plug in your ledger.
- Unlock it.
- Open the Aptos app.
- 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:
aptos move publish --profile <your-ledger-profile-name> --named-addresses hello_blockchain=<your-ledger-profile-name>
You should see a response like:
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:
jq --version
Start a localnet
You can run this command at any point to restart a fresh localnet:
mkdir -p localnet-data
aptos node run-localnet \
--assume-yes \
--test-dir localnet-data \
--force-restart
The localnet is ready when it prints out:
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:
aptos init --profile localnet-a
When prompted, choose local
as the network, then enter the following private key:
0x44bbe12dd5e120d94f65fb3cdae1dc7017156a8f7da1ebfa876cabf48fa6f5e2
Once the profile is setup, store the account address for later:
export ACCOUNT_A=0x$(
aptos account lookup-address --profile localnet-a \
| jq -r '.Result'
)
echo $ACCOUNT_A
This should output:
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:
aptos account list --profile localnet-a
Note that the authentication key is the same as the account address:
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:
0xaaaa29ce2bd42dec0bad87599d827c0bc565b2a83595489aa3dc3f5e43ebaaaa
Set up localnet Ledger profile
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:
export ACCOUNT_B=0x$(
aptos account lookup-address --profile localnet-b \
| jq -r '.Result'
)
echo $ACCOUNT_B
Check the authentication key for the account:
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
:
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:
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:
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:
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:
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
:
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:
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:
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:
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:
aptos move run \
--args \
address:$ACCOUNT_B \
u64:1 \
--profile localnet-a \
--function-id 0x1::aptos_account::transfer