This is the abridged developer documentation for Aptos Documentation # Build the Future of Web3 on Aptos > Everything you need to build a best-in-class Web3 experience. import { Card, CardGrid, Tabs, TabItem } from '@astrojs/starlight/components'; import ListCard from '~/components/ListCard.astro'; - [Deploy Your First Move Smart Contract](/build/guides/first-move-module) Compile & publish Move modules to devnet in minutes. - [Your First Transaction](/build/guides/first-transaction) Write and read on-chain data using the TypeScript SDK. - [NEW! Code with AI (MCP)](/build/ai/aptos-mcp) Make your AI Agent (Cursor or Claude Code) smarter by using our open-source MCP server. - [NEW! AskAptos AI Chatbot](/build/ai) Query docs, plan or refine requirements with AI (top right!). - [Testnet Faucet](/network/faucet) Fund your testnet account with APT to start building. - [Official SDKs](/build/sdks) TypeScript, Go, Java, Python, Rust, C++, Unity, and more. - [Aptos CLI](/build/cli) Compile, test, publish contracts; accounts & keys; localnet. - [NEW! Move on Aptos (VS Code Extension)](/build/smart-contracts/move-vscode-extension) Aptos Labs' official extension for Move development. - [Objects](/build/smart-contracts/objects) Composable on-chain primitives for flexible asset ownership, addressing, & programmability. - [The Move Book](/build/smart-contracts/book) Understand Move syntax, types, resources, & best practices. - [Vibe Code a full-stack dApp on Learn](https://learn.aptoslabs.com/en/hackathon/vibe-coder-to-aptos-guide/introduction) Interactive AI workshop to quickly build a full-stack dApp. - [Sponsored Transactions](/build/guides/sponsored-transactions) Pay for users' gas so they can use your dApp with zero APT. - [Keyless Accounts](/build/guides/aptos-keyless) Onboard users and sign without wallets or seed phrases. - [NEW! Orderless Transactions](/build/guides/orderless-transactions) High-volume apps can be safer by sending transactions out of order with replay-protection nonce. - [On-chain Randomness](/build/smart-contracts/randomness) Verifiable random number = fair games, lotteries, & drops. - [NEW! LLMs.txt Integration](/llms-txt) AI-optimized documentation format, paste it in your favorite large context AI and get moving! - [Query, Index, or Stream On-Chain Data](/build/indexer) Query Indexer API, index contracts, stream raw transactions. - [Apply for a Grant](https://aptosnetwork.com/grants) - [GitHub Developer Discussions](https://github.com/aptos-labs/aptos-developer-discussions/discussions) - [Ecosystem Directory](https://aptosnetwork.com/ecosystem/directory) - [Discord](https://discord.gg/aptosnetwork) - [Telegram](https://t.me/aptos) # Aptos Glossary > Comprehensive glossary of terms and concepts used throughout the Aptos ecosystem and documentation ## A ### Accumulator Root Hash - An **accumulator root hash** is the root hash of a [Merkle accumulator](https://eprint.iacr.org/2009/625.pdf). ### Account - An **account** in the Aptos blockchain is a container for an arbitrary number of [Move modules](#move-module) and [Move resources](#move-resources). - The state of each account is composed of both code and data. - The account is identified by [account address](#account-address). See [Accounts](/network/blockchain/accounts) for more information. ### Account Address - An **account address** is the address of an Aptos account. - Account address refers to a specific destination on the Aptos network. The address dictates the destination and source of a specific amount of assets exchanged by two parties on the blockchain. - Aptos addresses are 64-character hex string (32 bytes). Often times these strings are prefixed with `0x` and for first 16 addresses, the leading 0s are excluded (ex. `0x1`) See [Accounts](/network/blockchain/accounts) for more information. ### API - An **Application Programming Interface (API)** is a set of protocols and tools that allow users to interact with Aptos blockchain nodes and client networks via external applications. Aptos offers a REST API to communicate with our nodes. - See [documentation](/build/apis) for more details. ### APT **Aptos token (APT)** is the Aptos blockchain native token used for paying network and transaction fees. ### Aptos **Aptos** is a Layer 1 blockchain for everyone. It uses the Move programming language and launched its mainnet on 2022-10-17 to redefine the web3 user experience. The Aptos blockchain is dedicated to creating better user experiences through increased speed, security, scalability, reliability and usability with low transaction costs. The word "Aptos" means "The People" in the Ohlone language. Learn more about the Aptos blockchain on the [official Aptos website](https://aptosnetwork.com). ### AptosBFT - **AptosBFT** is the Aptos protocol's BFT consensus algorithm. - AptosBFT is based on Jolteon. ### Aptos Blockchain - The **Aptos blockchain** is a ledger of immutable transactions agreed upon by the validators on the Aptos network (the network of validators). ### Aptos Name Service (ANS) - The **Aptos Name Service (ANS)** is a decentralized naming address service for the Aptos blockchain. An Aptos name is a human-readable _.apt_ domain name that is used in place of a public key, for example _love.apt_. - This service also allows users to register subdomain names in addition to the registered domain. Find out more at: [Aptosnames.com](https://www.aptosnames.com/) ### Aptos Core **Aptos-core** is the [open-source repository](https://github.com/aptos-labs/aptos-core/) containing the code for Aptos Network software. Aptos-core contains software for - the Aptos blockchain itself, which generates and stores the immutable ledger of confirmed transactions and - the validation process, which implements the consensus algorithm to validate transactions and add them to the Aptos blockchain immutable ledger. ### Aptos Ecosystem - **Aptos ecosystem** refers to various components of the Aptos blockchain network and their interactions. The Aptos ecosystem includes the community, [community-driven projects](https://aptosnetwork.com/ecosystem/directory), and [events](https://aptosnetwork.com/events). ### Aptos Explorer - The **[Aptos Explorer](https://explorer.aptoslabs.com/)** is an interface that helps users examine details of the Aptos blockchain, including account information, validators, and transactions. - The Aptos Explorer help users validate their work in Aptos wallets and other tools in the blockchain. ### Aptos Framework The **Aptos Framework** defines the public API for blockchain updates and the structure of on-chain data. It defines the business logic and access control for the three key pillars of Aptos functionality: payments, treasury, and on-chain governance. It is implemented as a set of modules written in the Move programming language and stored on-chain as Move bytecode. ### Aptos Node An **Aptos node** is a peer entity of the Aptos network that tracks the state of the Aptos blockchain. There are two types of Aptos nodes, [validators](#validator) and [fullnodes](#fullnodes). ### Aptos Protocol - **Aptos protocol** is the specification of how transactions are submitted, ordered, executed, and recorded within the Aptos network. ### AptosAccount - A **`AptosAccount`** is a Move resource that holds all the administrative data associated with an account, such as sequence number, balance, and authentication key. - A **`AptosAccount`** is the only resource that every account is guaranteed to contain. ### AptosAccount module - **The AptosAccount module** is a Move module that contains the code for manipulating the administrative data held in a particular `AptosAccount.T` resource. - Code for checking or incrementing sequence numbers, withdrawing or depositing currency, and extracting gas deposits is included in the AptosAccount module. ### Aptos Devnet - See [devnet](#devnet). ## B ### Blocks - On Aptos, blocks are a batch of [transactions](#transaction) committed at the same time. - Block number is analogous to "block height" in blockchain literature. - Transactions are referenced by ledger version rather than by block. ### BlockSTM - **BlockSTM** is the state-of-the-art dynamic parallelism execution engine developed by the Aptos Labs team. - Dynamic parallelism allows developers to flexibly write applications without facing design constraints of statically declaring write sets of transactions. - It has been adopted across the industry by multiple blockchains. - More details can be found in [the blog post](https://medium.com/aptoslabs/block-stm-how-we-execute-over-160k-transactions-per-second-on-the-aptos-blockchain-3b003657e4ba) and [presentation at a16z crypto](https://www.youtube.com/watch?v=2SE5tqPzhyw) ### Byzantine (Validator) - A **validator** that does not follow the specification of the consensus protocol, and wishes to compromise the correct execution of the protocol. - BFT algorithms traditionally support up to one-third of the algorithm's voting power being held by Byzantine validators. ### Byzantine Fault Tolerance (BFT) - **Byzantine Fault Tolerance** (BFT) is the ability of a distributed system to provide safety and liveness guarantees in the presence of faulty, or "[Byzantine](https://en.wikipedia.org/wiki/Byzantine_fault)," validators below a certain threshold. - The Aptos blockchain uses AptosBFT, a consensus protocol based on [Jolteon](#jolteon). - BFT algorithms typically operate with a number of entities, collectively holding $N$ votes (which are called "validators" in the Aptos network’s application of the system). - $N$ is chosen to withstand some number of validators holding $f$ votes, which might be malicious. - In this configuration, $N$ is typically set to $3f + 1$. Validators holding up to $f$ votes will be allowed to be faulty β€” offline, malicious, slow, etc. As long as $2f + 1$ votes are held by [honest](#honest-validator) validators, they will be able to reach consensus on consistent decisions. - This implies that BFT consensus protocols can function correctly, even if up to one-third of the voting power is held by validators that are compromised or fail. ## C ### CLI - **Command line interface** refers to the Aptos CLI used for developing on the Aptos blockchain, operating nodes, and debugging issues. Find out more at [the Aptos CLI page](/build/cli). ### Client - **Client** is software that receives information from the blockchain and manages transactions. Clients interact with the blockchain through the Aptos nodes. ### Code Labs - **Code labs and tutorials** depict various workflows - such as the use of the Aptos CLI in minting non-fungible tokens (NFTs) - in order for users to understand how the process works and employ related functions in their code. If users have the necessary funds in their accounts, they can follow the same code lab and tutorial steps used in devnet, testnet and mainnet networks. ### Consensus - **Consensus** is a component of a validator. - The consensus component is responsible for coordination and agreement amongst all validators on the block of transactions to be executed, their order, and the execution results. - The Aptos blockchain is formed with these agreed-upon transactions and their corresponding execution results. - The consensus component is accountable for achieving security, trust, and agreement among all validators on the Aptos blockchain. ### Consensus Protocol - A **consensus protocol** is collectively executed by n validators to accept or reject a transaction and to agree on the ordering of transactions and execution results. - See [BFT](#byzantine-fault-tolerance-bft). ## D ### Dapps - **Decentralized applications (dapps)** are programs or digital applications that run on the Aptos blockchain autonomously. Smart contracts are commonly used to achieve this function. ### Devnet - The **Aptos devnet** is a publicly deployed instance of the Aptos network that runs using a set of validator test nodes. - The devnet is a demonstration of the Aptos network that is built for experimenting with new ideas - The devnet simulates a digital payment system and the coins on the devnet have _no real world value_. - The devnet is the network by which developers are given the opportunity to test given protocols. It is similar to testnet as it operates independently of the mainnet yet is reset weekly. ## E ### Ed25519 - **Ed25519** is our supported digital signature scheme. - More specifically, the Aptos network uses the PureEdDSA scheme over the Ed25519 curve, as defined in RFC 8032. ### Epoch - An **epoch** is the period of time between reconfigurations of the validator set and other administrative actions by the blockchain. On Aptos mainnet currently, it is every 2 hours. ### Event - An **event** is the user-facing representation of the effects of executing a transaction. - A transaction may be designed to emit any number of events as a list. For example, a `Coin` transfer emits a `WithdrawEvent` for the sender account and a `DepositEvent` for the recipient account. - In the Aptos protocol, events provide evidence that the successful execution of a transaction resulted in a specific effect. The `DepositEvent` (in the above example) allows the recipient to confirm that a payment was received into their account. - Events are persisted on the blockchain and are used to answer queries by [clients](#client). ### Execution - **Execution** in the Aptos blockchain is an Aptos node component that manages the block of transactions. The execution component stores successful transactions. ### Expiration Time A transaction ceases to be valid after its **expiration time**. If it is assumed that: - $Time\_C$ is the current time that is agreed upon between validators ($Time\_C$ is not the local time of the client); - $Time\_E$ is the expiration time of a transaction $T\_N$; and - $Time\_C > Time\_E$ and transaction $T\_N$ has not been included in the blockchain, then there is a guarantee that $T\_N$ will never be included in the blockchain. ## F ### Faucet - The **faucet** is a service that mints APT on devnet. For testnet see the [mint page](/network/faucet). - APT on devnet and testnet has no real world value, it is only for development purposes. - To use a faucet, see [Faucet API](/build/apis/faucet-api). ### Fullnodes - **Fullnodes** are clients that ensure data are stored up-to-date on the network. They replicate blockchain state and transactions from other fullnodes and validator nodes. ### Fungible Asset - A **fungible asset** is an asset, such as a currency, share, in-game resource, etc., that is interchangeable with another identical asset without any loss in its value. For example, APT is a fungible asset because you can exchange one APT for another. - Follow the [Asset Standards](/build/smart-contracts/aptos-standards#asset-standards) to create fungible assets on the Aptos blockchain. - Next generation of the Coin standard that addresses shortcomings of `aptos_framework::coin` such as lack of guaranteed enforcement of freeze and burn and advanced functionalities such as programmable transfers, e.g., approve in ERC-20. ### Fungible Token - For the legacy Aptos Token Standard (aptos\_token::token), a **fungible token** is a token that is interchangeable with other identical tokens (i.e., tokens that share the same `TokenId`). This means the tokens have the same `creator address`, `collection name`, `token name`, and `property version`. - For the Aptos Digital Asset Standard (aptos\_token\_objects::token), a \* _fungible token_\* is a fungible asset with metadata object that includes a Digital Asset resource. ### Fungible Unit - A **fungible unit** is an individual unit of a fungible asset. These units are identical and interchangeable without any loss in value. For example, each [Octa](#octa) (the smallest unit of APT) is a fungible unit. ## G ### Gas - **Gas** is a way to pay for computation and storage on a blockchain network. All transactions on the Aptos network cost a certain amount of gas. - The gas required for a transaction depends on the size of the transaction, the computational cost of executing the transaction, and the amount of additional global state created by the transaction (e.g., if new accounts are created). - The purpose of gas is regulating demand for the limited computational and storage resources of the validators, including preventing denial of service ( DoS) attacks. See [Gas and Storage Fees](/network/blockchain/gas-txn-fee) for more information. ### Gas Unit Price - Each transaction specifies the **gas unit price** the sender is willing to pay per unit of gas. - The price of gas required for a transaction depends on the current demand for usage of the network. - Gas price is expressed in [Octas](#octa). See [Gas and Storage Fees](/network/blockchain/gas-txn-fee) for more information. ## H ### Honest (Validator) - **Honesty** means a validator that faithfully executes the consensus protocol and is not Byzantine. ## I ### Indexer - **[Indexer](/build/indexer)** is the component of Aptos that retrieves, processes, and efficiently stores raw data in the database to provide speedy access to the Aptos blockchain state. - At a high level, indexer gets data from a gRPC stream and runs processors to transform raw blockchain data and serve transformed data via GraphQL endpoint. ## J ### Jolteon - **Jolteon** is a recent proposal for a [BFT](#byzantine-fault-tolerance-bft) consensus protocol. - AptosBFT, the Aptos network's consensus algorithm, is based on Jolteon. - It simplifies the reasoning about safety, and it addresses some performance limitations of previous consensus protocols. In particular, it reduces latency by 33% compared to HotStuff. ## L ### Leader - A **leader** is a validator that proposes a block of transactions for the consensus protocol. - In leader-based protocols, nodes must agree on a leader to make progress. - Leaders are selected by a function that takes the current [round number](#round-number) as input. ## M ### Mainnet - **Mainnet** refers to a working, fully-operational blockchain. A mainnet network has been fully deployed and performs the functionality of transferring digital currency from a sender to a recipient. ### Maximum Gas Amount - The **Maximum Gas Amount** of a transaction is the maximum gas amount in gas units that the sender is ready to pay for the transaction. - The transaction can be successfully executed only if the gas used does not exceed the maximum gas amount. - The gas charged is equal to the gas price multiplied by units of gas required to process this transaction. - If the transaction runs out of gas while it is being executed or the account runs out of balance during execution, then the sender will be charged for gas used and the transaction will fail. See [Gas and Storage Fees](/network/blockchain/gas-txn-fee) for more information. ### Mempool - **Mempool** is one of the components of the validator. It holds an in-memory buffer of transactions that have been submitted but not yet agreed upon and executed. Mempool receives transactions from other [full nodes](#fullnodes). - Transactions in the mempool of a validator are added from the JSON-RPC Service of the current node and from the mempool of other Aptos nodes. - When the current validator is the leader, its consensus component pulls the transactions from its mempool and proposes the order of the transactions that form a block. The validator quorum then votes on the proposal. ### Merkle Trees - **Merkle tree** is a type of authenticated data structure that allows for efficient verification of data integrity and updates. - The Aptos network treats the entire blockchain as a single data structure that records the history of transactions and states over time. - The [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree) implementation simplifies the work of apps accessing the blockchain. It allows apps to: - Read any data from any point in time. - Verify the integrity of the data using a unified framework. ### Merkle Accumulator - The **[Merkle Accumulator](https://www.usenix.org/legacy/event/sec09/tech/full_papers/crosby.pdf)** is an _append-only_ Merkle tree that the Aptos blockchain uses to store the ledger. - A Merkle accumulator can provide proofs that a transaction was included in the chain ("proof of inclusion"). - They are also called "history trees" in literature. ### Module - A **module** in the Move programming language may either be a program or library that can create, transfer, or store assets. ### Move - **Move** is a new programming language that implements all the transactions on the Aptos blockchain. - It has two different kinds of code β€” [Move scripts](#move-script) and [Move modules](#move-module). - Move is a safe and secure programming language for web3 that emphasizes access control and scarcity. It is the programming language used to build the Aptos blockchain. You can read more about it in [Move on Aptos](/network/blockchain/move). ### Move Bytecode - Move programs are compiled into **Move bytecode**. - Move bytecode is used to express Move scripts and Move modules. ### Move Module - A **Move module** defines the rules for updating the global state of the Aptos blockchain. - In the Aptos protocol, a Move module is a **smart contract**. - Each user-submitted transaction includes a Move script. The Move script invokes procedures of one or more Move modules to update the global state of the blockchain according to the rules. ### Move Resources - **Move resources** contain data that can be accessed according to the \* _procedures_\* declared in a Move **module.** - Move resources can never be copied, reused, or lost. This protects Move programmers from accidentally or intentionally losing track of a resource. ### Move Script - Each transaction submitted by a user includes a **Move script**. - These transactions, also known as Move scripts, represent the operations a client submits to a validator. - The operation could be a request to move coins from user A to user B, or it could involve interactions with published [Move modules](#move-module) (smart contracts). - The Move script is an arbitrary program that interacts with resources published in the global storage of the Aptos blockchain by calling the procedures of a module. It encodes the logic for a transaction. - A single Move script can send funds to multiple recipients and invoke procedures from several different modules. - A Move script **is not** stored in the global state and cannot be invoked by other Move scripts. It is a single-use program. To see example uses of Move scripts, follow [Move scripts](/build/smart-contracts/scripts/script-tutorial). ### Move Virtual Machine (MVM) - The **Move virtual machine** executes Move scripts written in [Move bytecode](#move-bytecode) to produce an execution result. This result is used to update the blockchain **state**. - The virtual machine is part of a [validator](#validator). - The Move virtual machine (MoveVM) processes each validator node that translates transactions along with the current blockchain ledger state to produce a changeset as input or storage delta as output. ## N ### Node - A **node** is a peer entity of the Aptos network that tracks the state of the Aptos blockchain. - An Aptos node consists of logical components. [Mempool](#mempool), [consensus](#consensus), and the [Move virtual machine](#move-virtual-machine-mvm) are examples of node components. ### Nonce - **Nonce** is a number only used once, a random or semi-random number that is generated for a specific use for authentication protocols and cryptographic hash functions. ## O ### Octa - An **Octa** is the smallest unit of [APT](#apt). 1 APT = 108 Octas. ### Open-Source Community - **Open-source community** is a term used for a group of developers who work on open-source software. If you're reading this glossary, then you are part of the Aptos project's developer community. ## P ### Proof - A **proof** is a way to verify the accuracy of data in the blockchain. - Every operation in the Aptos blockchain can be verified cryptographically that it is indeed correct and that data has not been omitted. - For example, if a user queries the information within a particular executed transaction, they will be provided with a cryptographic proof that the data returned to them is correct. ### Proof-of-Stake (PoS) **Proof-of-Stake (PoS)** is a security mechanism that serves in confirming the uniqueness and legitimacy of blockchain transactions. The PoS consensus mechanism is leveraged by the Aptos blockchain powered by a network of validators, which in turn update the system and process transactions. ## Q ### Quorum Store - **Quorum Store** is the component that disseminates transactions (in batches) within the validator set. - It significantly improves consensus throughput by removing the leader bottleneck. - It decouples data dissemination from metadata ordering, allowing validators to disseminate data asynchronously in parallel. - More details can be found in the [blog post](https://medium.com/aptoslabs/quorum-store-how-consensus-horizontally-scales-on-the-aptos-blockchain-988866f6d5b0) ## R ### Randapp - A [dapp](#dapps) that uses randomness for its functionality. ### Resource Account - A **resource account** is used to manage resources independent of an account managed by a user. For example, a developer may use a resource account to manage an account for module publishing, say managing a contract. - The contract itself does not require a signer post initialization. A resource account gives you the means for the module to provide a signer to other modules and sign transactions on behalf of the module. See [Resource accounts](/build/smart-contracts/resource-accounts) for instructions on use. ### REST API Service - The **REST API Service** component is the external interface of an Aptos node. Any incoming client request, such as submitted transactions or queries, must first go through the REST Service. A client needs to go through the REST Service component to access storage or any other component in the system. This filters requests and protects the system. - Whenever a client submits a new transaction, the REST Service passes it to [mempool](#mempool). ### Round - A **round** consists of achieving consensus on a block of transactions and their execution results. ### Round Number - A **round number** is a shared counter used to select leaders during an [epoch](#epoch) of the consensus protocol. ## S ### SDKs - Aptos **software development kits (SDKs)** are sets of tools that enable a developer to quickly create a custom app on the Aptos platform. Find out more at [Use the Aptos SDKs](/build/sdks). ### Sequence Number - The **sequence number** for an account indicates the number of transactions that have been submitted and committed on chain from that account. It is incremented every time a transaction sent from that account is executed or aborted and stored in the blockchain. - A transaction is executed only if it matches the current sequence number for the sender account. This helps sequence multiple transactions from the same sender and prevents replay attacks. - If the current sequence number of an account A is X, then a transaction T on account A will only be executed if T's sequence number is X. - These transactions will be held in mempool until they are the next sequence number for that account (or until they expire). - When the transaction is applied, the sequence number of the account will become X+1. The account has a strictly increasing sequence number. ### Sender - _Alternate name_: Sender address. - **Sender** is the address that originates the transaction. A transaction must be signed by the sender but can have more than one signer. ### Shoal - Method for decreasing latency for BFT protocols. See the [Shoal paper](https://arxiv.org/pdf/2306.03058.pdf) ### Smart Contract - **Smart contract** refers to a computer program that automatically and directly carries out the contract's terms. - See [Move Module](#move-module) for related details. ### State - A **state** in the Aptos protocol is a snapshot of the distributed database. - A transaction modifies the database and produces a new and updated state. ### State Root Hash - **State root hash** is a [Merkle hash](https://en.wikipedia.org/wiki/Merkle_tree) over all keys and values the state of the Aptos blockchain at a given version. ## T ### Table - A [**table**](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/doc/table.md) implements the Table type and in Aptos is used to store information as key-value data within an account at large scale. See [`table.move`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/table.move) for the associated Aptos source file. ### Testnet - **Testnet** describes the Aptos network that is not fully functional yet more stable than devnet; it is an alternative network to mainnet to be used for testing. ### Tokens - **Tokens** are digital units of value issued on a blockchain. They can be redeemed for assets or value held. Tokens can be of the types: Fungible Token (FT), Non-Fungible Token (NFT), and Semi-Fungible Token (SFT). ### Transaction - A raw **transaction** contains the following fields: - [Sender (account address)](#account-address) - [Move script](#move-script) - [Gas price](#gas-unit-price) - [Maximum gas amount](#maximum-gas-amount) - [Sequence number](#sequence-number) - [Expiration time](#expiration-time) - A signed transaction is a raw transaction with the digital signature. - An executed transaction changes the state of the Aptos blockchain. ### Transaction Script - See [Move script](#move-script) ## V ### Validator - _Alternate name_: Validators. - A **validator** is an entity of the Aptos ecosystem that validates on the Aptos blockchain. It receives requests from clients and runs consensus, execution, and storage. - A validator maintains the history of all the transactions on the blockchain. - Internally, a validator needs to keep the current state, to execute transactions, and to calculate the next state. - Aptos validators are in charge of verifying transactions. ### Validator Nodes - **Validator nodes** are a unique class of fullnodes that take part in consensus, specifically a Byzantine Fault Tolerance (BFT) consensus protocol in Aptos. Validators agree upon transactions to be added to the Aptos blockchain as well as the order in which they are added. ### Version - A **version** is a sequentially increasing number that increments for every [transaction](#transaction). - On aptos, transactions are globally ordered and every transaction has a version (often called "height" in blockchain literature.) - Transaction version 0 is the first transaction (genesis transaction), and a transaction version 100 is the 101st transaction in the blockchain. ## W ### Well-Formed Transaction An Aptos transaction is **well-formed** if each of the following conditions are true for the transaction: - The transaction has a valid signature. - An account exists at the sender address. - It includes a public key, and the hash of the public key matches the sender account's authentication key. - The sequence number of the transaction matches the sender account's sequence number. - The sender account's balance is greater than the [maximum gas amount](#maximum-gas-amount). - The expiration time of the transaction has not passed. # Smart Contracts > Learn to write secure, efficient smart contracts on Aptos using the Move programming language with examples, tutorials, and developer resources import { CardGrid, LinkCard } from '@astrojs/starlight/components'; import { RemoteCodeblock } from '~/components/RemoteCodeblock'; Aptos contracts are written using Move, a next generation language for secure, sandboxed, and formally verified programming which is used for multiple chains. Move allows developers to write programs that flexibly manage and transfer assets while providing security and protections against attacks on those assets. ## πŸ“– Learn Move ## πŸ‘¨β€πŸ’» Move Examples Here is a `hello_blockchain` example of move ## βš’οΈ Developer Resources ### FAQ and Discussions - [Aptos Dev Discussions](https://github.com/aptos-labs/aptos-developer-discussions/discussions) for Q\&A about Move. ### Move IDE plugins - Move on Aptos extension for [VSCode](https://marketplace.visualstudio.com/items?itemName=AptosLabs.move-on-aptos) and [OpenVSX](https://open-vsx.org/extension/aptoslabs/move-on-aptos). - [Move language plugin for JetBrains IDEs](https://plugins.jetbrains.com/plugin/14721-move-language) ### External Resources - [Aptos Move by Example](https://move-developers-dao.gitbook.io/aptos-move-by-example) - [Teach yourself Move on Aptos](https://github.com/econia-labs/teach-yourself-move). - [Formal Verification, the Move Language, and the Move Prover](https://www.certik.com/resources/blog/2wSOZ3mC55AB6CYol6Q2rP-formal-verification-the-move-language-and-the-move-prover) - [Collection of nestable Move resources](https://github.com/taoheorg/taohe) We have a new Move on Aptos compiler that supports Move 2. See [this page](/build/smart-contracts/compiler_v2) for more information. # Aptos Blockchain > Core concepts of the Aptos blockchain including architecture, consensus, and fundamental mechanisms import { CardGrid, LinkCard } from '@astrojs/starlight/components'; Start here to get into the core concepts of the Aptos blockchain. Then review our [research papers](https://aptoslabs.com/research) and the Aptos source code found in the [Aptos-core](https://github.com/aptos-labs/aptos-core) repository of GitHub while continuing your journey through this site. The source contains READMEs and code comments invaluable to developing on Aptos. # SDKs Overview > Comprehensive software development kits for building on Aptos blockchain in TypeScript, Python, Go, Rust, C#, C++, Unity and more languages import { CardGrid, LinkCard } from '@astrojs/starlight/components'; ## Official SDKs Use these Aptos software development kits (SDKs), in combination with the [Aptos CLI](/build/cli) for your development on the Aptos blockchain. ## [Community SDKs](/build/sdks/community-sdks) SDKs provided by the community for Aptos. These may not be fully vetted by the Aptos team, and may still be in development. They are still provided as a resource for all developers. # Aptos APIs > Access the Aptos blockchain through various APIs including REST API, GraphQL, and specialized endpoints for different use cases import { CardGrid, LinkCard } from '@astrojs/starlight/components'; The Aptos Blockchain network can be accessed by several APIs, depending on your use-case. ## Aptos Fullnode This API - embedded into Fullnodes - provides a simple, low latency, yet low-level way of _reading_ state and _submitting_ transactions to the Aptos Blockchain. It also supports transaction simulation. ## Indexer ## Faucet (Only Testnet/Devnet) The code of each of the above-mentioned APIs is open-sourced on [GitHub](https://github.com/aptos-labs/aptos-core). As such anyone can operate these APIs and many independent operators and builders worldwide choose to do so. ### Aptos Labs operated API Deployments [Aptos Labs](https://aptoslabs.com) operates a deployment of these APIs on behalf of [Aptos Foundation](https://aptosnetwork.com/foundation) for each [Aptos Network](/network/nodes/networks) and makes them available for public consumption. These APIs allow for limited access on a per-IP basis without an API key (anonymous access). To get much higher rate limits you can sign up for an [Geomi](https://geomi.dev/) account. # Aptos CLI – Install, Setup, and Use the Command-Line Interface > Learn how to install, configure, and use the Aptos CLI to compile Move contracts, interact with the blockchain, run a local network, and manage nodes. import { CardGrid, LinkCard } from '@astrojs/starlight/components'; The Aptos command line interface (CLI) is a tool to help you compile and test Move contracts. It can also help you quickly play with Aptos features on-chain. For more advanced users, the CLI can also be used to run a private Aptos network (to help test code locally) and can be helpful managing a network node. ## πŸ“₯ Install the Aptos CLI ## βš™οΈ Setup the Aptos CLI ## πŸ› οΈ Using the Aptos CLI # Indexer > Query indexed blockchain data using GraphQL API, create custom processors with the Indexer SDK, or stream raw transactions from Aptos blockchain import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components'; import { GraphQLEditor } from '~/components/react/GraphQLEditor'; We have several offerings for getting indexed data from the Aptos blockchain. 1. Query the [Indexer API](/build/indexer/indexer-api) to get basic data about transactions, fungible assets, and tokens 2. Index your custom contract with the [Indexer SDK](/build/indexer/indexer-sdk) 3. Stream raw transactions from [Transaction Stream Service](/build/indexer/txn-stream) to your processor or service ## Indexer API The Aptos Indexer is an API you can use to get: 1. Aggregate data (ex. How many NFTs exist?) 2. Historical data (ex. What transactions has this account submitted?) 3. Data that is hard to get from the simpler [Aptos Node API](/build/apis/fullnode-rest-api) (ex. What account owns a token named "ExampleToken"?). For example, you can use the Indexer API to look up the fungible asset balances of any account like so: ## Using the Indexer API Learn how to use the Indexer API, what each table represents, and the architecture. ### Example Queries To help get you started, here are the most common queries the Indexer is used for. ## Indexer SDK If the hosted Indexer API is not enough or if you want to index your custom contract, you can create a processor with the [Indexer SDK](/build/indexer/indexer-sdk). ## Transaction Stream Service Transaction Stream Service is a GRPC service that streams raw transactions to your processor or service. If you're using the Indexer SDK, you'll need an authorization token to connect to Transaction Stream Service. ## Legacy Indexer Find information about the legacy indexer [here](/build/indexer/legacy). # Learn from Guides > Comprehensive step-by-step tutorials to help you build on Aptos blockchain, from beginner basics to advanced development patterns import { CardGrid, LinkCard } from '@astrojs/starlight/components'; Welcome to Aptos guides! Whether you're just getting started or building advanced applications, these step-by-step tutorials will help you accomplish specific tasks on the Aptos blockchain. ## Beginner Guides Start your journey with these foundational tutorials: ## Advanced Guides Ready for more complex scenarios? Explore these advanced topics: # AI Tools for Aptos Development > Use AI tools like Claude, Cursor, and GitHub Copilot to build on Aptos faster. Access the Aptos MCP server and LLMs.txt documentation feeds. import { CardGrid, LinkCard } from '@astrojs/starlight/components'; Aptos provides first-class support for AI-powered development workflows. Whether you're using an AI coding assistant or an AI chat tool, you can give it deep knowledge of the Aptos blockchain, Move language, SDKs, and APIs. ## AskAptos Chatbot The AskAptos chatbot is built into the documentation site β€” look for it in the top-right corner of the navigation bar. It can answer questions about Aptos concepts, help you plan implementations, and point you to the right documentation pages. ## Aptos MCP Server The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) lets AI coding tools directly call Aptos APIs, query on-chain data, and generate correct Aptos code β€” without needing to copy-paste documentation. ## Aptos Agent Skills [Aptos Agent Skills](https://github.com/aptos-labs/aptos-agent-skills) provides pre-built AI skills that give coding assistants deep Aptos expertise β€” covering Move smart contracts, the TypeScript SDK, and full-stack dApp scaffolding. ```bash npx skills add aptos-labs/aptos-agent-skills ``` This command uses the [Skills CLI](https://www.npmjs.com/package/skills), which is invoked via `npx`. If `npx skills` is not available in your environment, ensure you have Node.js and npm installed and refer to the Skills CLI documentation for installation and troubleshooting details. ## LLMs.txt Documentation Feeds The [LLMs.txt standard](https://llmstxt.org/) provides machine-readable documentation feeds that AI tools can ingest to understand the full Aptos documentation. We publish three feeds to suit different context window sizes: | Feed | URL | Best for | | ------------------ | ------------------------------------ | ------------------------------------------------------------------------- | | **llms.txt** | [`/llms.txt`](/llms.txt) | Structured index with page titles, descriptions, and per-page `.md` links | | **llms-small.txt** | [`/llms-small.txt`](/llms-small.txt) | Condensed docs for smaller context windows | | **llms-full.txt** | [`/llms-full.txt`](/llms-full.txt) | Full documentation β€” all pages concatenated | # Aptos Improvement Proposals (AIPs) > Learn about Aptos Improvement Proposals - how the community proposes changes and improvements to the Aptos protocol Aptos Improvement Proposals (AIPs) are a way for the Aptos community to propose changes, improvements, and new features to the Aptos protocol. AIPs are designed to be a collaborative process that allows anyone in the community to contribute ideas and feedback. AIPs are documented in the [AIPs repository](https://github.com/aptos-foundation/AIPs) and are administered by the Aptos Foundation. Each AIP is assigned a unique number and goes through a rigorous review process before it is accepted or rejected. ## What do AIPs cover? AIPs can cover a wide range of topics, including: - Node protocol changes - Mempool changes, consensus changes, etc. - Framework (smart contract) changes - New modules, new functions, etc. - Governance changes - Changes to the way the Aptos Foundation operates, changes to the way AIPs are processed, etc. ## What is this section of the docs mostly about? This section of the docs is mostly about AIPs that are relevant to developers and providing FAQs and quick information about them. # Aptos Agent Skills > Pre-built AI skills for Claude Code, Cursor, and GitHub Copilot to write Move smart contracts, use the Aptos TypeScript SDK, and build full Aptos dApps. import { LinkCard } from '@astrojs/starlight/components'; [Aptos Agent Skills](https://github.com/aptos-labs/aptos-agent-skills) is a collection of pre-built AI skills that give coding assistants like Claude Code, Cursor, and GitHub Copilot deep expertise in Aptos development. The skills cover Move smart contracts, the TypeScript SDK, and full-stack dApp scaffolding. ## Installation ### Using the Skills CLI (recommended) ```bash npx skills add aptos-labs/aptos-agent-skills ``` This installs the skills into your project so your AI coding assistant can use them automatically. ### Claude Code Plugin ```bash claude plugin add aptos-labs/aptos-agent-skills ``` ### Manual Installation Clone the repository and copy the skills into your project's `.claude/skills/aptos/` directory: ```bash git clone https://github.com/aptos-labs/aptos-agent-skills.git cp -r aptos-agent-skills/skills/* .claude/skills/aptos/ ``` ## Available Skills ### Move Skills These skills help AI assistants write, test, audit, and deploy Move smart contracts: | Skill | Description | | ---------------------------- | --------------------------------------------------------------------- | | **write-contracts** | Write Move smart contracts following Aptos best practices | | **generate-tests** | Generate comprehensive unit and integration tests for Move modules | | **security-audit** | Audit Move contracts for common vulnerabilities and security issues | | **deploy-contracts** | Deploy Move modules to Aptos devnet, testnet, or mainnet | | **search-aptos-examples** | Search official Aptos example contracts for reference implementations | | **analyze-gas-optimization** | Analyze and optimize Move code for gas efficiency | | **modernize-move** | Upgrade Move code to use the latest Aptos Move features | ### TypeScript SDK Skills These skills help AI assistants use the Aptos TypeScript SDK correctly: | Skill | Description | | ------------------------- | ----------------------------------------------------------- | | **use-ts-sdk** | General guidance for using the Aptos TypeScript SDK | | **ts-sdk-client** | Configure and use the Aptos client for network interactions | | **ts-sdk-account** | Create and manage Aptos accounts and key pairs | | **ts-sdk-address** | Work with Aptos addresses and address formats | | **ts-sdk-transactions** | Build, simulate, sign, and submit transactions | | **ts-sdk-view-and-query** | Query on-chain data and call view functions | | **ts-sdk-types** | Use TypeScript types from the SDK correctly | | **ts-sdk-wallet-adapter** | Integrate wallet connections in frontend applications | ### Project Skills | Skill | Description | | ------------------------ | ------------------------------------------------------------------------------ | | **create-aptos-project** | Scaffold a new Aptos project with Move contracts, TypeScript SDK, and frontend | ## Recommended Workflow For a typical Aptos dApp, use the skills in this order: 1. **Scaffold** β€” Use `create-aptos-project` to set up the project structure 2. **Write** β€” Use `write-contracts` to create your Move smart contracts 3. **Test** β€” Use `generate-tests` to create comprehensive test coverage 4. **Audit** β€” Use `security-audit` to check for vulnerabilities 5. **Deploy** β€” Use `deploy-contracts` to publish to devnet or testnet 6. **Frontend** β€” Use `ts-sdk-wallet-adapter` and other SDK skills to build the UI ## Community Skills The repository includes a `community-skills/` directory where anyone can contribute additional skills. To add your own: 1. Create a new skill file following the existing format 2. Submit a pull request to the [aptos-agent-skills repository](https://github.com/aptos-labs/aptos-agent-skills) See the [CONTRIBUTING.md](https://github.com/aptos-labs/aptos-agent-skills/blob/main/CONTRIBUTING.md) for details on the contribution process. # Aptos Model Context Protocol (MCP) > Learn how to use the Aptos MCP server to build applications with AI tools like Cursor and Claude Code import { CardGrid, LinkCard } from '@astrojs/starlight/components'; The Aptos Model Context Protocol (MCP) is a server that provides a set of tools, prompts, and resources to help developers build applications on the Aptos blockchain. It is designed to be used with AI tools like Cursor, Claude Code, and others that support the Model Context Protocol. ## Getting Started ### Prerequisites - [node and npm](https://nodejs.org/en) - Build Bot Api Key ### Generate a Build Bot Api Key To be able to make Geomi actions like managing API keys, etc., follow these instructions to generate a new Bot API Key to use with the MCP. - Go to [https://geomi.dev/](https://geomi.dev/) - Click on your name in the bottom left corner - Click on "Bot Keys" - Click on the "Create Bot Key" button - Copy the Bot Key and paste it into the MCP configuration file as an env arg: `APTOS_BOT_KEY=` ### Supported Interfaces We've provided guides for Cursor and Claude Code to help you integrate the Aptos MCP into your development environment. If you're using a different AI tool, follow the steps for your favorite AI tool, and refer to the documentation for Cursor or Claude Code for examples. # Setting up Aptos MCP with Claude Code > Step-by-step guide to configure and use Aptos MCP with Claude Code for blockchain development 1. Install the `claude-code` package ```bash npm install -g @anthropic-ai/claude-code ``` 2. Locate where Claude Code stores its configuration, usually on Mac it is at `~/.claude.json` 3. Edit the `mcpServers` object in the `json` file with ```json { "mcpServers": { "aptos-mcp": { "command": "npx", "args": ["-y", "@aptos-labs/aptos-mcp"], "type": "stdio", "env": { "APTOS_BOT_KEY": "" } } } } ``` 4. Obtain your `APTOS_BOT_KEY`: - Visit [Geomi](https://geomi.dev/) and log in with your account. - Navigate to the API Keys section and create a new key. - Copy the generated key for use in the next step. 5. Make sure to update the `APTOS_BOT_KEY` with the key you generated in the previous step. 6. Navigate to your project ```bash cd your-awesome-project ``` 7. In a new terminal window type: ```bash claude ``` 8. You can now use Claude Code to interact with the Aptos MCP. Prompt the agent with `what aptos mcp version are you using?` to verify the connection. The agent should reply with something like: ```text I'm using Aptos MCP version 0.0.2. ``` # Setting up Aptos MCP with Cursor > Complete guide to integrate Aptos MCP with Cursor IDE for enhanced blockchain development workflows 1. Open the Cursor IDE 2. On the project root folder, create a `.cursor` folder 3. In the `.cursor` folder, create a `mcp.json` file 4. Paste this content ```json { "mcpServers": { "aptos-mcp": { "command": "npx", "args": ["-y", "@aptos-labs/aptos-mcp"], "env": { "APTOS_BOT_KEY": "" } } } } ``` 5. Obtain your `APTOS_BOT_KEY`: - Visit [Geomi](https://geomi.dev/) and log in with your account. - Navigate to the API Keys section and generate a new key. - Copy the generated key for use in the next step. 6. Make sure to update the `APTOS_BOT_KEY` in the `mcp.json` file with the key you just generated. ### Verify Cursor runs your MCP 1. Open Cursor Settings: `cursor -> settings -> cursor settings` 2. Head to the `MCP` or `Tools & Integrations` section 3. Make sure it is enabled and showing a green color indicator ![Make sure it is enabled and showing a green color indicator](~/images/cursor/verify-cursor_step-3.png) 4. Click the β€œrefresh” icon to update the MCP. 5. Make sure the Cursor AI window dropdown is set to `Agent` ![Make sure the Cursor AI window dropdown is set to Agent](~/images/cursor/verify-cursor_step-5.png) 6. Prompt the agent with `what aptos mcp version are you using?` to verify the connection. The agent should reply with something like: ![Prompt the agent with what aptos mcp version are you using? to verify the connection.](~/images/cursor/verify-cursor_step-6.png) # AIP-115: Stateless Accounts > Learn about AIP-115 which introduces stateless accounts that operate without explicitly created Account resources [AIP-115](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-115.md) covers stateless accounts. ## General FAQ ### What is a Stateless Account? A Stateless Account is a new behavior for Aptos accounts that allows them to operate without requiring an explicitly created `0x1::account::Account` resource. Instead, these accounts use default behaviors until an action necessitates the creation of the resource. This change simplifies account management and reduces unnecessary resource creation, making it easier for developers and users to interact with the Aptos blockchain. ### How is it different from a regular account? Technically, there is no separate account type. All accounts are the same under the hood. The difference is that accounts without a resource behave in a "stateless" manner using default values. The account resource is only created on-demand when needed. ### How does it work? When an account signs its first transaction sequence number transaction, it will not have the `0x1::account::Account` resource created. Instead, it will create the `0x1::account::Account` resource only when an action that requires to increment the sequence number. For an orderless transaction, the account resource is not needed at all, and the account resource will not be created. ## Technical Details FAQ ### What is the default auth\_key for Stateless Accounts? If the `0x1::account::Account` resource does not exist, the auth\_key defaults to the account address itself. This allows the account to sign and submit transactions without needing a resource. ### What is the sequence number of a Stateless Account? It defaults to `0` if the account resource does not exist. In the future, with Orderless Transactions, the sequence number may be eliminated entirely. ### When is the account resource automatically created? The resource is created when an action that requires on-chain state, such as: - Rotating the authentication key - Using capabilities or features that rely on the account resource such as sequence number - Explicitly calling functions that access fields in the account resource ### Does creating the account resource incur extra gas cost? Yes. The creation of the resource is deferred, and the corresponding gas and storage fees are only charged at the moment of actual creation, not beforehand. ### Any behavior change to account module at the Move level? `0x1::account::exists_at` always returns true, as all on-chain account addresses are considered valid and treated as existing by default. There is no move function in the module to check whether the underlying account resource really exists since the goal is to make it transparent to users. As a result, any logic that first checks whether an account exists before attempting to create it is now obsolete. ### Can users force-create the account resource upfront? Yes. Users can explicitly call functions like `0x1::account::create_account_if_does_not_exist` to create the resource manually, if desired. ### Any behavior change to API? If you rely on the following API behavior, please adjust correspondingly. `GET /accounts/{address}` will never return β€œ404 not found” but the default authentication key and sequence number mentioned above for stateless accounts. Therefore, if it is desired to check whether the account resource exists or not, try `GET /accounts/{address}/resource/0x1::account::Account` ### Do existing accounts get affected? No. Existing accounts with resources already created will continue to work exactly as they do now. Stateless Account behavior only applies to accounts that have not yet created a resource. ### Do dApps / CEX need to change anything? Maybe. Previously, checking whether an account existed often relied on calling APIs that return a 404 error if the account resource was not found. Applications would then use this as a signal to warn users (e.g., "This account does not exist"). Under the new model, all addresses are considered valid, and such 404-based existence checks are no longer reliable or meaningful. However, we are not banning this patternβ€”developers may still choose to warn users that an account may not have performed any on-chain activity and thus might not have a resource created yet. If you still want to detect whether an account has an associated resource, you can refer to the method described in Q9 or check whether the sequence\_number is 0\. But be aware that with the introduction of orderless transactions, some accounts may only submit transactions that never create a resource, which could result in false negatives. We recommend designing your application to be robust regardless of whether the account resource exists, and to avoid assuming resource presence as a proxy for account existence. Examples: - A wallet might check for an account to see if it’s a new account, and provide a user a warning. With this change, instead a mitigation like Q9 will be needed. - A custodial wallet may send funds to initialize an account with gas. With this change, it will need to check the account’s balance instead of just the account existing. ### Is this compatible with Orderless Transactions? Yes. Orderless Transactions and Stateless Accounts are complementary. Once Orderless Transactions are enabled, sequence numbers will no longer be needed, enabling truly stateless usage. ## Will all accounts become Stateless in the future? No. Stateless Accounts are not a new account type. It simply allows accounts to behave with default logic until the account resource is needed. This lazy resource creation, does not transform existing account state. All accounts can behave in a stateless way by default, but they will still create the standard resource if and when advanced features are used. # AIP-88: Block Epilogue Transactions > Understanding AIP-88 which introduces block epilogue transactions to provide information about executed blocks [AIP-88](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-88.md) covers block epilogue transactions, which are a new type of transaction that give information about the block after it has been executed. These transactions can only be created by the consensus and are not user-initiated. They contain information about gas usage in the block and will contain more information in the future. It replaces the previous `StateCheckpoint` transaction type, which was used to "sometimes" signal the end of a block. The new `BlockEpilogue` transaction is now sometimes created at the end of a block instead, and it is guaranteed to be the last transaction in the block. The only case this does not apply is the last block of an epoch, which will have no `BlockEpilogue` transaction. ## General FAQ ### What is in the Block Epilogue Transaction? The block epilogue transaction contains a `BlockEndInfo` enum. It is purposely designed to be an enum so that it can be extended in the future without breaking existing code. The current version is `V0` and contains the following fields: ```move module 0x1::epilogue { enum BlockEndInfo { V0 { /// Whether block gas limit was reached block_gas_limit_reached: bool, /// Whether block output limit was reached block_output_limit_reached: bool, /// Total gas_units block consumed block_effective_block_gas_units: u64, /// Total output size block produced block_approx_output_size: u64, }, } } ``` These mainly contain information about the gas usage in the block for debugging purposes. The JSON output will look like this: ```json { "version":"1912", "hash":"0x54a8efc93fc94f5b545dadb63da3d4dc192125c717b336dc446d55a5b553913f", "state_change_hash":"0xafb6e14fe47d850fd0a7395bcfb997ffacf4715e0f895cc162c218e4a7564bc6", "event_root_hash":"0x414343554d554c41544f525f504c414345484f4c4445525f4841534800000000", "state_checkpoint_hash":"0x841a43956ca09a02b1c1cdadc65f24c390170aa666015a2e8f7ec5c9d6a3875f", "gas_used":"0", "success":true, "vm_status":"Executed successfully", "accumulator_root_hash":"0x6561976b4560ff25239dffc6cada70e7008dd42fc4d3df2eca6a86b6d2ec384d", "changes":[], "timestamp":"1719263322836578", "block_end_info": { "block_gas_limit_reached":false, "block_output_limit_reached":false, "block_effective_block_gas_units":0, "block_approx_output_size":1163 }, "type":"block_epilogue_transaction" } ``` ## Compatibility FAQ ### What does this mean for my dApp? If you process transactions in your dApp, and expect the last transaction in a block to be a `StateCheckpoint`, you will need to update your code to handle the `BlockEpilogue` transaction instead. Note that, the `BlockEpilogue` transaction is guaranteed to be the last transaction of a block except for the last block of an epoch, which will not have a `BlockEpilogue` transaction. ### What apps are likely to be affected? Apps that index all transactions such as block explorers and centralized exchange indexer processors may be affected. However, most of these are informational and do not affect the core functionality of the dApp. ### What can I do to process the new transaction type? If you're using the Aptos Go SDK or the Aptos TypeScript SDK, you can update to the latest version, which will automatically handle the new transaction type. # Aptos Labs Geomi > Access Aptos Labs APIs, gas station services, and no-code indexing through the Geomi developer portal [Geomi](https://geomi.dev) is your gateway to access Aptos Labs provided APIs in a quick and easy fashion to power your dapp. Beyond API access it offers gas station and no code indexing services. Learn more about Geomi at the dedicated [Geomi docs site](https://geomi.dev/docs). # Data Providers > Access Aptos blockchain data through SQL interfaces and analytics dashboards for aggregated data analysis # Data Providers In addition to the API run by Aptos full nodes, we also provide a few different ways to get Aptos blockchain data. ## Overview of aptos data endpoints [REST API](/build/apis/fullnode-rest-api) allows you to query the full node directly and will have the latest data (historical data will be missing unless it's an archival full node). [GRPC transaction stream](/build/indexer/txn-stream/aptos-hosted-txn-stream) is a stream layer we built that serves typed version of above data [GraphQL](/build/indexer) is an endpoint where we provide product tables (such as transfers and balances) that can be queried Product tables are parsed out from transaction and logic is [public](https://github.com/aptos-labs/aptos-indexer-processors-v2), some vendors have implemented similar parsing logic to create a subset of tables and made them available to query. ## SQL Tables Indexer (the stack that powers our GraphQL endpoint) defines several processors that create different product tables in Postgres. This type of data is often used for analytics since it allows for aggregations. ### Core tables These contain raw data very similar to what is found in REST API. Note that (transaction) version is often used instead of transaction hash. - Blocks - version, block height, epoch, timestamp - Transactions - version, type, sender, entry function, gas - Signatures - signature types, signer, fee payer address - Events - type and data for events On chain data is stored as: [table items](/build/smart-contracts/table), [resources](/network/blockchain/resources) or [modules](/build/smart-contracts/modules-on-aptos) (executable) - (write set) changes - change index, change type (write or delete on what type of data), resource address - Table items - table key, table handle, decoded key (content and type), value (content and type) - (move) resources - resource address, resource type, data - (move) modules - bytecode for deployed modules, friends and exposed functions ## Vendors for core tables and metrics: We have a few options that will let you access this data using SQL or UIs for building dashboards. ### Google bigquery public dataset Provides data through [google public data](https://console.cloud.google.com/marketplace/product/bigquery-public-data/crypto-aptos-mainnet-us) ![bq\_sql](~/images/screenshots/bq_sql.png) We also have sample analytics queries [using the above resources](https://github.com/aptos-labs/explorer/tree/main/analytics) ### Dune We have a dashboard here: [https://dune.com/aptos/aptos-chain-metrics-overview](https://dune.com/aptos/aptos-chain-metrics-overview) ### Allium Data source for many downstream vendors such as defillama and rwa.xyz. Raw data is available: [https://docs.allium.so/historical-data/supported-blockchains/move-ecosystem/aptos](https://docs.allium.so/historical-data/supported-blockchains/move-ecosystem/aptos) They also have transfers for stablecoins [https://docs.allium.so/historical-data/stablecoins#stablecoin-metrics](https://docs.allium.so/historical-data/stablecoins#stablecoin-metrics) ### Artemis Provides [topline metrics](https://app.artemis.xyz/asset/aptos) as well as chart builder ### Nansen Provides [topline metrics](https://app.nansen.ai/macro/blockchains?chain=aptos) with additional functionality with account. ### Sentio They have a [guide](https://docs.sentio.xyz/docs/aptos) and data is found in data source -> external project -> sentio/aptos-overview They also provide [stack tracing](https://app.sentio.xyz/explorer) of transactions ### RWA.xyz High level metrics for RWAs in [dashboard](https://app.rwa.xyz/networks/aptos). You'll need to make an account to access stablecoin details. ### Pangea Provides [raw data](https://docs.pangea.foundation/api-reference/aptosvm/reference), coin/fa transfers, and some dex parsing. ### Other vendors We also have some partners who target more enterprise use cases - [Token Terminal](https://tokenterminal.com/resources/articles/aptos-data-partnership) - [The Tie](https://www.thetie.io/insights/news/introducing-aptos-ecosystem-dashboard-and-on-chain-data/) - [Elliptic](https://www.elliptic.co/media-center/elliptic-partners-with-aptos-foundation-as-a-data-integration-provider-to-offer-compliance-screening-and-risk-services-for-aptos-network) ## Vendors for real-time data - [Geomi](https://geomi.dev/) (prev. Aptos Build) - Provides [API key](https://geomi.dev/docs/start) for higher rate limits on REST API and GRPC transaction stream. - Provides [no code indexing](https://geomi.dev/docs/no-code-indexing) to parse and turn events into tables that can be queried. - [NODEREAL](https://nodereal.io/aptos) for REST API endpoint - [SHINAMI](https://docs.shinami.com/reference/api-references-overview) for REST API and GraphQL endpoints - [QuickNode](https://www.quicknode.com/chains/apt) for REST API endpoint Additional RPC vendors can be found [here](https://aptosnetwork.com/ecosystem/directory/category/rpc) ## Tips for analyzing data - Aptos [data overview](https://medium.com/aptoslabs/data-analyst-guide-to-aptos-pt-1-816367edc1c5) (pt.1) - Parsing [Defi Swaps](https://medium.com/aptoslabs/data-analyst-guide-to-aptos-defi-swaps-pt2-e343ac6be84e) (pt.2) - Calculating [supply and volume](https://medium.com/aptoslabs/data-analyst-guide-to-aptos-supply-and-volume-pt-3-535e312946ad) (pt.3) - [Aptos Explorer analytics queries](https://github.com/aptos-labs/explorer/tree/main/analytics) - [Aptos Spellbook on Dune](https://github.com/duneanalytics/spellbook/tree/main/dbt_subprojects/daily_spellbook/models/aptos) - Module bytecode can be decompiled with [Revela](https://revela.verichains.io/) or aptos cli with `aptos move decompile --decompiler-version v2` # Faucet API > Get free APT tokens on devnet and testnet for development and testing purposes using the faucet API The faucet allows users to get `APT` on devnet. On testnet you can only mint at the [mint page](/network/faucet). It is not available on Mainnet. The endpoints for each faucet are: - Devnet: [https://faucet.devnet.aptoslabs.com](https://faucet.devnet.aptoslabs.com) ## Using the faucet Each SDK has integration for devnet to use the faucet. Below are a few examples, but you can see more information on each individual [SDK's documentation](/build/sdks). ### Using the faucet in a wallet Most wallets, such as [Petra](https://aptosnetwork.com/ecosystem/directory/petra), will have a faucet button for devnet. See full list of [Aptos Wallets](https://aptosnetwork.com/ecosystem/projects/wallets). ### Using the faucet in the Aptos CLI Once you've [set up your CLI](/build/cli/setup-cli), you can simply call fund-with-faucet. The amount used is in Octas (1 APT = 100,000,000 Octas). ```shellscript filename="Terminal" aptos account fund-with-faucet --account 0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6 --amount 100000000 ``` ### Using the faucet in the TypeScript SDK Here is an example funding the account `0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6` with 1 APT in Devnet. The amount used is in Octas (1 APT = 100,000,000 Octas). ```typescript filename="index.ts" import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; const aptos = new Aptos(new AptosConfig({network: Network.Devnet})); aptos.fundAccount({accountAddress: "0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6", amount: 100000000}); ``` ### Using the faucet in the Go SDK Here is an example funding the account `0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6` with 1 APT in Devnet. The amount used is in Octas (1 APT = 100,000,000 Octas). ```go filename="index.go" import "github.com/aptos-labs/aptos-go-sdk" func main() { client, err := aptos.NewClient(aptos.LocalnetConfig) if err != nil { panic(err) } client.Fund("0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6", 100000000) } ``` ### Calling the faucet: Other languages not supported by SDKs If you are trying to call the faucet in other languages, you have two options: 1. Generate a client from the [OpenAPI spec](https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos-faucet/doc/spec.yaml). 2. Call the faucet on your own. For the latter, you will want to build a query similar to this: ```shellscript filename="Terminal" curl -X POST 'https://faucet.devnet.aptoslabs.com/mint?amount=10000&address=0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6' ``` This means mint 10000 [octas](/network/glossary#Octa) to address `0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6`. # Fullnode REST API > Low-level REST API for reading state, submitting transactions, and simulating operations on the Aptos blockchain import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components'; This API - embedded into Fullnodes - provides a simple, low latency, yet low-level way of reading state and submitting transactions to the Aptos Blockchain. It also supports transaction simulation. For more advanced queries, we recommend using the [Indexer GraphQL API](/build/indexer). ## Fullnode REST API Explorer ## Understanding rate limits As with the [Aptos Indexer](/build/indexer/indexer-api), the Aptos REST API has rate limits based on compute units. You can learn more about how the ratelimiting works by reading the [Geomi docs](https://geomi.dev/docs/admin/billing). ## Viewing current and historical state Most integrations into the Aptos blockchain benefit from a holistic and comprehensive overview of the current and historical state of the blockchain. Aptos provides historical transactions, state, and events, all the result of transaction execution. - Historical transactions specify the execution status, output, and tie to related events. Each transaction has a unique version number associated with it that dictates its global sequential ordering in the history of the blockchain ledger. - The state is the representation of all transaction outputs up to a specific version. In other words, a state version is the accumulation of all transactions inclusive of that transaction version. - As transactions execute, they may emit events. [Events](/network/blockchain/events) are hints about changes in on-chain data. The storage service on a node employs two forms of pruning that erase data from nodes: - state - events, transactions, and everything else While either of these may be disabled, storing the state versions is not particularly sustainable. Events and transactions pruning can be disabled via setting the [`enable_ledger_pruner`](https://github.com/aptos-labs/aptos-core/blob/cf0bc2e4031a843cdc0c04e70b3f7cd92666afcf/config/src/config/storage_config.rs#L141) to `false` in `storage_config.rs`. This is default behavior in Mainnet. In the near future, Aptos will provide indexers that mitigate the need to directly query from a node. The REST API offers querying transactions and events in these ways: - [Transactions for an account](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_account_transactions) - [Transactions by version](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_transaction_by_version) - [Events by event handle](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_events_by_event_handle) ## Reading state with the View function View functions do not modify blockchain state when called from the API. A [View function](https://github.com/aptos-labs/aptos-core/blob/main/api/src/view_function.rs) and its [input](https://github.com/aptos-labs/aptos-core/blob/main/api/types/src/view.rs) can be used to read potentially complex on-chain state using Move. For example, you can evaluate who has the highest bid in an auction contract. Here are related files: - [`view_function.rs`](https://github.com/aptos-labs/aptos-core/blob/main/api/src/tests/view_function.rs) for an example - related [Move](https://github.com/aptos-labs/aptos-core/blob/90c33dc7a18662839cd50f3b70baece0e2dbfc71/aptos-move/framework/aptos-framework/sources/coin.move#L226) code - [specification](https://github.com/aptos-labs/aptos-core/blob/90c33dc7a18662839cd50f3b70baece0e2dbfc71/api/doc/spec.yaml#L8513). The view function operates like the Aptos simulation API, though with no side effects and an accessible output path. View functions can be called via the `/view` endpoint. Calls to view functions require the module and function names along with input type parameters and values. A function does not have to be immutable to be tagged as `#[view]`, but if the function is mutable it will not result in state mutation when called from the API. If you want to tag a mutable function as `#[view]`, consider making it private so that it cannot be maliciously called during runtime. In order to use the View functions, you need to [publish the module](/build/cli/working-with-move-contracts) through the [Aptos CLI](/build/cli). In the Aptos CLI, a view function request would look like this: ```shellscript filename="Terminal" aptos move view --function-id devnet::message::get_message --profile devnet --args address:devnet { "Result": [ "View functions rock!" ] } ``` In the TypeScript SDK, a view function request would look like this: ```typescript filename="index.ts" import { Aptos } from "@aptos-labs/ts-sdk"; const aptos = new Aptos(); const [balance] = aptos.view<[string]>({ function: "0x1::coin::balance", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [alice.accountAddress] }); expect(balance).toBe("100000000"); ``` The view function returns a list of values as a vector. By default, the results are returned in JSON format; however, they can be optionally returned in Binary Canonical Serialization (BCS) encoded format. # Formatting Move Contracts > Learn how to format and beautify Move smart contract code using the movefmt tool integrated into the Aptos CLI with configuration options. `movefmt` is a formatter tool that makes Move code much easier to write, read, and maintain β€” greatly improving the development experience on Aptos. ## Installation `movefmt` is integrated into the Aptos CLI. To begin using it, first install it using the CLI update command. ```shellscript filename="Terminal" # Install movefmt for first time usage aptos update movefmt ``` To install a specific version of `movefmt`: ```shellscript filename="Terminal" # Install movefmt with the target aptos update movefmt --target-version ``` The latest release of `movefmt` can be found [here](https://github.com/movebit/movefmt/releases). ## Format your code Similar to compilation and testing, you can use the following command to format the Move package: ```shellscript filename="Terminal" # Format the Move package aptos move fmt ``` Different ways of emitting the formatting result is supported: ```shellscript filename="Terminal" # Format and overwrite all the target move files in the package. # This is the default behavior if `--emit-mode` is not explicitly specified aptos move fmt --emit-mode=overwrite # Print the formatting result to terminal aptos move fmt --emit-mode=std-out # Print the formatting result to new files with the suffix `.fmt.out` in the same directory aptos move fmt --emit-mode=new-file # Print the difference between before and after formatting aptos move fmt --emit-mode=diff ``` `movefmt` also provides different options to configure how the code will be formatted. Here is the default configuration: ``` max_width = 90 # each line can have at most 90 characters indent_size = 4 # the indent is 4 spaces tab_spaces = 4 # each tab is identical to 4 spaces hard_tabs = false # when a tab is inserted, it will be automatically replaced by 4 spaces ``` To override the default option, users can either specify a configuration file `movefmt.toml` and put it in Move package directory or manually specify it in the command line: ```shellscript filename="Terminal" # When formatting the code, set `max_width` to 80 and `indent_size` to 2 aptos move fmt --config max_width=80,indent_size=2 ``` ## Feedback Aptos Labs remains committed to improving the developer experience for builders using Move on Aptos. If you’re interested in shaping the style guidelines for Move, we would love to hear your comments and feedback [here](https://github.com/movebit/movefmt/issues). # Install Aptos CLI with ASDF or Mise > Install the Aptos CLI or specific versions with the ASDF or Mise package managers. import { Steps } from '@astrojs/starlight/components'; If you're already using the ASDF or Mise package managers, you can install the Aptos CLI with the following steps. ## Install with ASDF 1. Check that you have [ASDF](https://asdf-vm.com/) installed. 2. Install the Aptos CLI plugin. ```sh asdf plugin add aptos https://github.com/gregnazario/asdf-aptos.git ``` 3. Install the Aptos CLI ```sh asdf install aptos latest ``` 4. (Optional) Install a specific version. ```sh # Locally asdf install aptos 7.9.0 # Globally asdf global aptos 7.9.0 ``` 5. Verify the installation worked. ```sh aptos help ``` These help instructions also serve as a useful detailed guide for specific commands. ## Install with Mise 1. Check that you have [Mise](https://mise.jds.dev/) installed. 2. Install the Aptos CLI plugin. ```sh mise plugin add aptos https://github.com/gregnazario/asdf-aptos.git ``` 3. Install the Aptos CLI. ```sh mise install aptos ``` 4. (Optional) Install a specific version. ```sh mise install aptos@7.9.0 ``` 5. Add the tool to your local configuration. ```sh mise use aptos ``` 6. Verify the installation worked. ```sh aptos help ``` These help instructions also serve as a useful detailed guide for specific commands. # Install the Aptos CLI on Linux > Step-by-step instructions to install the Aptos CLI on Linux using shell scripts, package managers, or pre-compiled binaries with troubleshooting guidance. import { Aside, Steps } from '@astrojs/starlight/components'; For Linux, the easiest way to install the Aptos CLI tool is via shell script, although if that does not work, you can also install manually via downloading pre-compiled binaries. The pre-compiled binaries approach is not generally recommended as updating is very manual. # Install via Script 1. In the terminal, use one of the following commands: ```shellscript filename="Terminal" curl -fsSL "https://aptos.dev/scripts/install_cli.sh" | sh ``` Or use the equivalent `wget` command: ```shellscript filename="Terminal" wget -qO- "https://aptos.dev/scripts/install_cli.sh" | sh ``` 2. (Optional) It can be helpful to add the Aptos CLI to a folder in your PATH, or to add it to your PATH directly. - The steps to add a folder to your PATH are shell dependent. - You can run `echo $SHELL` to print the default shell for your machine, then google specific steps to add a folder to your PATH for that shell. 3. Verify the script is installed by opening a new terminal and running aptos help - You should see a list of commands you can run using the CLI. - In the future, this is a helpful resource to learn exactly how each command works. # Install via Package Manager (Optional) ### Arch Linux #### Install via AUR (Arch User Repository) ```shellscript filename="Terminal" git clone https://aur.archlinux.org/aptos-bin.git cd aptos-bin makepkg -si ``` or use an AUR helper like `yay`: ```shellscript filename="Terminal" yay -S aptos-bin ``` # Install via Pre-Compiled Binaries (Backup Method) 1. Go to the . 2. Click the "Assets" expandable menu for the latest release to see the pre-compiled binaries. 3. Download the zip file for Linux. 1. It’ll have a name like: `aptos-cli--Linux-x86_64.zip` or `aptos-cli--Linux-aarch64.zip`. 2. Make sure you choose the right zip file for your computer architecture (x86\_64 for Intel / AMD or aarch64 for ARM). 3. You will likely have to dismiss warnings that this is a suspicious file when downloading. 4. Unzip the downloaded file. 5. Move the extracted Aptos binary file into your preferred folder. 6. Open a terminal and navigate to your preferred folder. 7. Make ~/aptos an executable by running chmod +x ~/aptos. 8. Verify that this installed version works by running ~/aptos help. You should see instructions for how to use all CLI commands. These can be helpful in the future when you are trying to understand how to use specific commands. 9. (Optional) It can be helpful to add the Aptos CLI to a folder in your PATH, or to add it to your PATH directly. - The steps to add a folder to your PATH are shell dependent. - You can run `echo $SHELL` to print the default shell for your machine, then google specific steps to add a folder to your PATH for that shell. # Install the Aptos CLI on Mac > Complete installation guide for the Aptos CLI on macOS using Homebrew, shell scripts, or pre-compiled binaries with upgrade instructions. import { Aside, Steps } from '@astrojs/starlight/components'; For Mac, the easiest way to install the Aptos CLI is with the package manager `brew`. # Installation 1. Ensure you have brew installed [https://brew.sh/](https://brew.sh/). 2. Open a new terminal and enter the following commands. ```shellscript filename="Terminal" brew update brew install aptos ``` 3. Open another terminal and run aptos help to verify the CLI is installed. ```shellscript filename="Terminal" aptos help ``` # Upgrading the CLI Upgrading the CLI with brew just takes 2 commands: ```shellscript filename="Terminal" brew update brew upgrade aptos ``` # Install via Script 1. In the terminal, use one of the following commands: ```shellscript filename="Terminal" curl -fsSL "https://aptos.dev/scripts/install_cli.sh" | sh ``` Or use the equivalent `wget` command: ```shellscript filename="Terminal" wget -qO- "https://aptos.dev/scripts/install_cli.sh" | sh ``` 2. (Optional) It can be helpful to add the Aptos CLI to a folder in your PATH, or to add it to your PATH directly. - The steps to add a folder to your PATH are shell dependent. - You can run `echo $SHELL` to print the default shell for your machine, then google specific steps to add a folder to your PATH for that shell. 3. Verify the script is installed by opening a new terminal and running aptos help - You should see a list of commands you can run using the CLI. - In the future, this is a helpful resource to learn exactly how each command works. # Install via Pre-Compiled Binaries (Backup Method) 1. Go to the . 2. Click the "Assets" expandable menu for the latest release to see the pre-compiled binaries. 3. Download the zip file for macOS. 1. It’ll have a name like: `aptos-cli--macOS-x86_64.zip` or `aptos-cli--macOS-arm64.zip`. 2. Make sure you choose the right zip file for your computer architecture (x86\_64 for Intel / AMD or arm64 for ARM). 3. You will likely have to dismiss warnings that this is a suspicious file when downloading. 4. Unzip the downloaded file. 5. Move the extracted Aptos binary file into your preferred folder. 6. Open a terminal and navigate to your preferred folder. 7. Make ~/aptos an executable by running chmod +x ~/aptos. 8. Verify that this installed version works by running ~/aptos help. You should see instructions for how to use all CLI commands. These can be helpful in the future when you are trying to understand how to use specific commands. 9. (Optional) It can be helpful to add the Aptos CLI to a folder in your PATH, or to add it to your PATH directly. - The steps to add a folder to your PATH are shell dependent. - You can run `echo $SHELL` to print the default shell for your machine, then google specific steps to add a folder to your PATH for that shell. # Install Specific Aptos CLI Versions (Advanced) > Advanced guide to build and install specific versions of the Aptos CLI from source code for specialized development needs and custom architectures. import {Aside, Steps} from '@astrojs/starlight/components'; # Using existing releases ## Using the Aptos CLI script If you are already using the installation script for Mac and Linux, you can install a specific version of the Aptos CLI using the following command: ```sh curl -fsSL "https://aptos.dev/scripts/install_cli.sh" | sh -s -- --cli-version 7.9.0 ``` ## Using asdf If you are already using [asdf](https://asdf-vm.com/), you can install a specific version of the Aptos CLI using the following command: ```sh asdf install aptos 7.9.0 ``` ## Using mise If you are already using [mise](https://mise.jdx.dev), you can install a specific version of the Aptos CLI using the following command: ```sh mise install aptos@7.9.0 ``` # Installation from source code If you need a specific version of the Aptos CLI, you can build it directly from the Aptos source code. This installation method is primarily used to interact with specific features on Devnet which may not have made it to Testnet / Mainnet yet. You may also want to follow these steps if you are running an architecture which does not play well with the existing releases / pre-compiled binaries. If you do not need this advanced method, you can find the normal install steps [here](/build/cli). ## Install on macOS / Linux 1. Checkout the Aptos source code. ```sh git clone https://github.com/aptos-labs/aptos-core.git ``` 2. Ensure you have [cargo](https://doc.rust-lang.org/cargo/) installed. 3. Build the Aptos CLI: ```sh cargo build --package aptos --profile cli ``` The binary will be available at `target/cli/aptos`. 4. (Optional) Move this executable to a place in your PATH. 5. Verify the installation worked. ```sh target/cli/aptos help ``` These help instructions also serve as a useful detailed guide for specific commands. ## Install on Windows 1. Checkout the Aptos source code. ```powershell git clone https://github.com/aptos-labs/aptos-core.git ``` 2. Ensure you have [cargo](https://doc.rust-lang.org/cargo/) installed. 3. Build the Aptos CLI. ```powershell cargo build --package aptos --profile cli ``` The binary will be available at `target\cli\aptos.exe`. 4. (Optional) Move this executable to a place in your PATH. 5. Verify the installation worked. ```powershell target\cli\aptos.exe help ``` These help instructions also serve as a useful detailed guide for specific commands. # Install the Aptos CLI on Windows > Complete guide to install the Aptos CLI on Windows using PowerShell scripts, package managers, or pre-compiled binaries with troubleshooting tips. import { Aside, Steps } from '@astrojs/starlight/components'; For Windows, the easiest way to install the Aptos CLI tool is via PowerShell script. If that does not work, you can also install manually via pre-compiled binaries. The pre-compiled binaries approach is not generally recommended as updating is very manual. # Install via PowerShell Script 1. In PowerShell, run the install script: ```powershell filename="Terminal" Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; iwr https://aptos.dev/scripts/install_cli.ps1 | iex ``` 2. Verify the script is installed by opening a new terminal and running aptos help. - You should see a list of commands you can run using the CLI. - In the future, this is a helpful resource to learn exactly how each command works. # Install via Package Manager (Optional) ### If you have [Scoop](https://scoop.sh/) installed, you can run the following command to install the Aptos CLI: ```powershell filename="Terminal" scoop install https://aptos.dev/scoop/aptos.json ``` ### If you have [Chocolatey](https://chocolatey.org/) installed, you can run the following command to install the Aptos CLI: ```powershell filename="Terminal" choco install aptos ``` ### If you have [winget](https://winget.run/) installed, you can run the following command to install the Aptos CLI: ```powershell filename="Terminal" winget install aptos ``` # Install via Pre-Compiled Binaries (Backup Method) 1. Go to the . 2. Expand "Assets" to see the pre-compiled binaries. 3. Download the zip file for Windows. - It will have a name like: `aptos-cli--Windows-x86_64.zip` - You will likely have to dismiss warnings that this is a suspicious file when downloading. 4. Unzip the downloaded file. - Move the file to whichever folder you would like to call `aptos` from in the future. 5. Right click, then copy the path to the executable. Ex. `C:\Users\\Downloads\aptos-cli-3.1.0-Windows-x86_64\aptos.exe`. 6. Open PowerShell via the Start Menu. 7. Verify the installation by running the help command. Use the path you copied earlier to call the Aptos CLI. Ex. `C:\Users\\Downloads\aptos-cli-3.1.0-Windows-x86_64\aptos.exe help`. # Managing a Network Node via Aptos CLI > Learn how to manage validator nodes and validator full nodes using the Aptos CLI for staking pool operations and governance voting. If you are running a [validator node or validator full node (VFN)](/network/nodes/validator-node), you can use the CLI to interact with your node. Specifically, you can use the CLI to: 1. [Manage staking pools you own](/network/nodes/validator-node/connect-nodes/staking-pool-operations). 2. [Vote on proposals](/network/nodes/validator-node/connect-nodes/staking-pool-voter). Beyond that, you can run this help command to see more specialized commands the CLI can do relating to operating your node: ```shellscript filename="Terminal" aptos node --help ``` # Running a Public Network (Advanced) > Advanced guide to bootstrap and run a public Aptos network using genesis ceremonies, validator configurations, and blockchain initialization. import { Aside } from '@astrojs/starlight/components'; ## Genesis ceremonies The `aptos` tool supports bootstrapping new blockchains through what is known as a genesis ceremony. The output of the genesis ceremony is the output of move instructions that prepares a blockchain for online operation. The input consists of: - A set of validators and their configuration - The initial set of Move modules, known as a framework - A unique `ChainId` (u8) that distinguishes this from other networks - For test chains, there also exists an account that manages the minting of AptosCoin ## Generating genesis - The genesis organizer constructs a `Layout` and distributes it. - The genesis organizer prepares the Aptos framework's bytecode and distributes it. - Each participant generates their `ValidatorConfiguration` and distributes it. - Each participant generates a `genesis.blob` from the resulting contributions. - The genesis organizer executes the `genesis.blob` to derive the initial waypoint and distributes it. - Each participant begins their `aptos-node`. The `aptos-node` verifies upon startup that the `genesis.blob` with the waypoint provided by the genesis organizer. - The blockchain will begin consensus after a quorum of stake is available. ### Prepare aptos-core The following sections rely on tools from the Aptos source. See [Building Aptos From Source](/network/nodes/building-from-source) for setup. ### The `layout` file The layout file contains: - `root_key`: an Ed25519 public key for AptosCoin management. - `users`: the set of participants - `chain_id`: the `ChainId` or a unique integer that distinguishes this deployment from other Aptos networks An example: ```yaml root_key: "0xca3579457555c80fc7bb39964eb298c414fd60f81a2f8eedb0244ec07a26e575" users: - alice - bob chain_id: 8 ``` ### Building the Aptos Framework From your Aptos-core repository, build the framework and package it: ```shellscript filename="Terminal" cargo run --package framework mkdir aptos-framework-release cp aptos-framework/releases/artifacts/current/build/**/bytecode_modules/* aptos-framework-release ``` The framework will be stored within the `aptos-framework-release` directory. ### The `ValidatorConfiguration` file The `ValidatorConfiguration` file contains: - `account_address`: The account that manages this validator. This must be derived from the `account_key` provided within the `ValidatorConfiguration` file. - `consensus_key`: The public key for authenticating consensus messages from the validator - `account_key`: The public key for the account that manages this validator. This is used to derive the `account_address`. - `network_key`: The public key for both validator and fullnode network authentication and encryption. - `validator_host`: The network address where the validator resides. This contains a `host` and `port` field. The `host` should either be a DNS name or an IP address. Currently only IPv4 is supported. - `full_node_host`: An optional network address where the fullnode resides. This contains a `host` and `port` field. The `host` should either be a DNS name or an IP address. Currently only IPv4 is supported. - `stake_amount`: The number of coins being staked by this node. This is expected to be `1`, if it is different the configuration will be considered invalid. An example: ```yaml account_address: ccd49f3ea764365ac21e99f029ca63a9b0fbfab1c8d8d5482900e4fa32c5448a consensus_key: "0xa05b8f41057ac72f9ca99f5e3b1b787930f03ba5e448661f2a1fac98371775ee" account_key: "0x3d15ab64c8b14c9aab95287fd0eb894aad0b4bd929a5581bcc8225b5688f053b" network_key: "0x43ce1a4ac031b98bb1ee4a5cd72a4cca0fd72933d64b22cef4f1a61895c2e544" validator_host: host: bobs_host port: 6180 full_node_host: host: bobs_host port: 6182 stake_amount: 1 ``` To generate this using the `aptos` CLI: 1. Generate your validator's keys: ```shellscript filename="Terminal" cargo run --package aptos -- genesis generate-keys --output-dir bobs ``` 2. Generate your `ValidatorConfiguration`: ```shellscript filename="Terminal" cargo run --package aptos -- \\ genesis set-validator-configuration \\ --keys-dir bobs \\ --username bob \\ --validator-host bobs_host:6180 \\ --full-node-host bobs_host:6180 \\ --local-repository-dir . ``` 3. The last command will produce a `bob.yaml` file that should be distributed to other participants for `genesis.blob` generation. ### Generating a genesis and waypoint `genesis.blob` and the waypoint can be generated after obtaining the `layout` file, each of the individual `ValidatorConfiguration` files, and the framework release. It is important to validate that the `ValidatorConfiguration` provided in the earlier stage is the same as in the distribution for generating the `genesis.blob`. If there is a mismatch, inform all participants. To generate the `genesis.blob` and waypoint: - Place the `layout` file in a directory, e.g., `genesis`. - Place all the `ValidatorConfiguration` files into the `genesis` directory. - Ensure that the `ValidatorConfiguration` files are listed under the set of `users` within the `layout` file. - Make a `framework` directory within the `genesis` directory and place the framework release `.mv` files into the `framework` directory. - Use the `aptos` CLI to generate genesis and waypoint: ```shellscript filename="Terminal" cargo run --package aptos -- genesis generate-genesis --local-repository-dir genesis ``` ### Starting an `aptos-node` Upon generating the `genesis.blob` and waypoint, place them into your validator and fullnode's configuration directory and begin your validator and fullnode. # Replaying Past Transactions > Learn how to replay historical blockchain transactions locally for debugging, benchmarking, and gas profiling using the Aptos CLI. import { Aside, FileTree } from '@astrojs/starlight/components'; ## Basics You can replay past transactions locally using the `aptos move replay` command. The command is fairly straightforward but it requires you to specify two pieces of required information: - `--network` - This is the network you want to replay on - Possible values: `mainnet`, `testnet`, `devnet` or `` - `--txn-id` - This is the id of the transaction you want to replay - This is also sometimes being referred to as `version` on explorers - Specifically it is NOT the hexadecimal transaction hash Let's use mainnet transaction [581400718](https://explorer.aptoslabs.com/txn/581400718?network=mainnet) (a simple coin transfer transaction) as an example. ```shellscript filename="Terminal" aptos move replay --network mainnet --txn-id 581400718 ```
Output ```shellscript Got 1/1 txns from RestApi. Replaying transaction... { "Result": { "transaction_hash": "0x1ba73d03a0442a845735a17c7be46f3b51e2acb0e5cf68749305c5a17539ac63", "gas_used": 7, "gas_unit_price": 100, "sender": "c94e16736910cc160347d01de345407fe2d350fce5635ac1150319b0fbf5630e", "sequence_number": 14637, "success": true, "version": 581400718, "vm_status": "status EXECUTED of type Execution" } } ```
Alternatively, if you want to simulate a new transaction, check out [Local Simulation, Benchmarking and Gas Profiling](/build/cli/working-with-move-contracts/local-simulation-benchmarking-and-gas-profiling). ## Alternate Modes Similar to local simulations, the replay command can be enhanced with one of the following options: - `--benchmark`: Benchmark the transaction and report the running time(s). - `--profile-gas` Profile the transaction for detailed gas usage. ### Benchmarking ```shellscript filename="Terminal" aptos move replay --network mainnet --txn-id 581400718 --benchmark ```
Output ```shellscript Got 1/1 txns from RestApi. Benchmarking transaction... Running time (cold code cache): 914.821Β΅s Running time (warm code cache): 820.189Β΅s { "Result": { "transaction_hash": "0x1ba73d03a0442a845735a17c7be46f3b51e2acb0e5cf68749305c5a17539ac63", "gas_used": 7, "gas_unit_price": 100, "sender": "c94e16736910cc160347d01de345407fe2d350fce5635ac1150319b0fbf5630e", "sequence_number": 14637, "success": true, "version": 581400718, "vm_status": "status EXECUTED of type Execution" } } ```
It's worth noting that these running times serve only as informational references, as they are contingent upon the specifications of your local machine and may be influenced by noise or other random factors. **If you are aiming to optimize your contract, you should base your decisions on the gas profiling results.** ### Gas Profiling The Aptos Gas Profiler is a powerful tool that can help you understand the gas usage of Aptos transactions. Once activated, it will simulate transactions using an instrumented VM, and generate a web-based report. The gas profiler can also double as a debugger since the report also includes a full execution trace. ```shellscript filename="Terminal" aptos move replay --network mainnet --txn-id 581400718 --profile-gas ```
Output ```shellscript Got 1/1 txns from RestApi. Profiling transaction... Gas report saved to gas-profiling/txn-1ba73d03-0x1-aptos_account-transfer. { "Result": { "transaction_hash": "0x1ba73d03a0442a845735a17c7be46f3b51e2acb0e5cf68749305c5a17539ac63", "gas_used": 7, "gas_unit_price": 100, "sender": "c94e16736910cc160347d01de345407fe2d350fce5635ac1150319b0fbf5630e", "sequence_number": 14637, "success": true, "version": 581400718, "vm_status": "status EXECUTED of type Execution" } } ```
You can then find the [generated gas report](/gas-profiling/sample-report-2/index.html) in the directory gas-profiling: - gas-profiling/ - txn-1ba73d03-0x1-aptos\_account-transfer/ - assets/ - index.html To understand the gas report, please refer to [this section](/build/cli/working-with-move-contracts/local-simulation-benchmarking-and-gas-profiling#understanding-the-gas-report) of the local simulation tutorial. # Running a Localnet via Aptos CLI > Set up and run a local Aptos network for testing and development with Docker, including Node API, Indexer API, and faucet services. import { Aside, Steps } from '@astrojs/starlight/components'; Local networks can be helpful when testing your code. They are not connected to any production Aptos networks like mainnet, but they are useful for three main reasons: 1. **No rate limits:** You can interact with hosted services like the Node API, Indexer API, and faucet with no rate-limits to speed up testing. 2. **Reproducibility:** You can set up specific on-chain scenarios and restart the network from scratch at any point to return to a clean slate. 3. **High availability**: The Aptos devnet and testnet networks are periodically upgraded, during which time they can be unavailable. Local development networks are also always available even if you have no internet access.
# Starting A Local Network 1. Ensure you have the installed. 2. Ensure you have installed. 1. This is exclusively needed for making a production-like environment by running the Indexer API. Many downstream tools such as the Aptos SDK depend on the Indexer API. 2. Docker recommends that you install via [Docker Desktop](https://www.docker.com/products/docker-desktop/) to get automatic updates. 3. Start Docker. 4. Run the following command in a new terminal to start the private network: ```shellscript filename="Terminal" aptos node run-local-testnet --with-indexer-api ``` You should expect to see an output similar to this: ```shellscript filename="Terminal" Readiness endpoint: http://0.0.0.0:8070/ Indexer API is starting, please wait... Node API is starting, please wait... Transaction stream is starting, please wait... Postgres is starting, please wait... Faucet is starting, please wait... Completed generating configuration: Log file: "/Users/dport/.aptos/testnet/validator.log" Test dir: "/Users/dport/.aptos/testnet" Aptos root key path: "/Users/dport/.aptos/testnet/mint.key" Waypoint: 0:397412c0f96b10fa3daa24bfda962671c3c3ae484e2d67ed60534750e2311f3d ChainId: 4 REST API endpoint: http://0.0.0.0:8080 Metrics endpoint: http://0.0.0.0:9101/metrics Aptosnet fullnode network endpoint: /ip4/0.0.0.0/tcp/6181 Indexer gRPC node stream endpoint: 0.0.0.0:50051 Aptos is running, press ctrl-c to exit Node API is ready. Endpoint: http://0.0.0.0:8080/ Postgres is ready. Endpoint: postgres://postgres@127.0.0.1:5433/local_testnet Transaction stream is ready. Endpoint: http://0.0.0.0:50051/ Indexer API is ready. Endpoint: http://127.0.0.1:8090 Faucet is ready. Endpoint: http://127.0.0.1:8081/ Applying post startup steps... Setup is complete, you can now use the local testnet! ``` 5. Wait for the network to start Once the terminal says `Setup is complete, you can now use the local testnet!` the local network will be running.
Common Errors On Network Startup ### Address Already In Use ```shellscript filename="Terminal" panicked at 'error binding to 0.0.0.0:8080: error creating server listener: Address already in use (os error 48)' ``` This means one of the ports needed by the local network is already in use by another process. To fix this on Unix systems, you can: 1. Identify the name and PID of the process by running `lsof -i :8080`. 2. Run `kill ` once you know the PID to free up that port. ### Too many open files error ```shellscript filename="Terminal" panicked at crates/aptos/src/node/local_testnet/logging.rs:64:10: called \`Result::unwrap()\` on an \`Err\` value: Os { code: 24, kind: Uncategorized, message: \"Too many open files\" } ``` This means there were too many open files on your system. On many Unix systems you can increase the maximum number of open files by adding something like this to your `.zshrc`: ```shellscript filename="Terminal" ulimit -n 1048576 ``` ### Docker is not available ```shellscript filename="Terminal" Unexpected error: Failed to apply pre-run steps for Postgres: Docker is not available, confirm it is installed and running. On Linux you may need to use sudo ``` To debug this, try the below fixes: 1. Make sure you have docker installed by running `docker --version`. 2. Ensure the Docker daemon is running by running `docker info` (if this errors saying `Cannot connect to the Docker daemon` Docker is NOT running). 3. Make sure the socket for connecting to Docker is present on your machine in the default location. For example, on Unix systems `/var/run/docker.sock` should exist. 1. If that file does not exist, open Docker Desktop and enable `Settings -> Advanced -> Allow the default Docker socket to be used`. 2. Or, you can find where the Docker socket is by running `docker context inspect | grep Host`, then symlink that location to the default location by running `sudo ln -s /Users/dport/.docker/run/docker.sock /var/run/docker.sock`
As you can see from the example output in step 4, once the local network is running, you have access to the following services: - [Node API](/build/apis/fullnode-rest-api): This is a REST API that runs directly on the node. It enables core write functionality such as transaction submission and a limited set of read functionality, such as reading account resources or Move module information. - [Indexer API](/build/indexer/indexer-api): This is a [GraphQL](https://graphql.org/) API that provides rich read access to indexed blockchain data. If you click on the URL for the Indexer API above, by default [http://127.0.0.1:8090](http://127.0.0.1:8090), it will open the Hasura Console, a web UI that will help you query the Indexer GraphQL API. - [Transaction Stream Service](/build/indexer/txn-stream): This is a gRPC stream of transactions used by the Indexer API and SDK. This is only relevant to you if you are developing a [Indexer SDK](/build/indexer/indexer-sdk) custom processor. - [Postgres](https://www.postgresql.org/): This is the database that the Indexer processors write to. The Indexer API reads from this database. - [Faucet](/build/apis/faucet-api): You can use this to fund accounts on your local network. If you do not want to run any of these sub-components of a network, there are flags to disable them. If you are writing a script and would like to wait for the local network to come up with all services, you can make a GET request to `http://127.0.0.1:8070`. At first this will return http code [503](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503). When it returns [200](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) it means all the services are ready. For more information on different flags you can pass when starting your local network, or configuration settings such as changing which port certain services run on, run the help command: ```shellscript filename="Terminal" aptos node run-local-testnet --help ```
## Using The Local Network Now that the network is running, you can use it like you would any other network. So, you can create a local profile like this: ```shellscript filename="Terminal" aptos init --profile --network local ``` You can then use that profile for any commands you want to use going forward. For example, if you wanted to publish a Move module like the [`hello_blockchain`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain) package to your local network you could run: ```shellscript filename="Terminal" aptos move publish --profile --package-dir /opt/git/aptos-core/aptos-move/move-examples/hello_blockchain --named-addresses HelloBlockchain=local ``` ### Configuring the TypeScript SDK If you want to use the local network with the TypeScript SDK, you can use local network URLs when initializing the client object (`Aptos`): ```tsx import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; const network = Network.LOCAL; const config = new AptosConfig({ network }); const client = new Aptos(config); ``` ### Resetting the local network Sometimes while developing it is helpful to reset the local network back to its initial state, for example: - You made backwards incompatible changes to a Move module, and you'd like to redeploy it without renaming it or using a new account. - You are building an [Indexer SDK](/build/indexer/indexer-sdk) custom processor and would like to index using a fresh network. - You want to clear all on chain state, e.g., accounts, objects, etc. To start with a brand new local network, use the `--force-restart` flag: ```shellscript filename="Terminal" aptos node run-local-testnet --force-restart ``` It will then prompt you if you really want to restart the chain, to ensure that you do not delete your work by accident. ```shellscript filename="Terminal" Are you sure you want to delete the existing chain? [yes/no] > yes ``` If you do not want to be prompted, include `--assume-yes` as well: ```shellscript filename="Terminal" aptos node run-local-testnet --force-restart --assume-yes ``` # Setup CLI Initial Configuration > Learn how to configure the Aptos CLI with network settings, profiles, and credentials for secure and efficient blockchain interactions. import { Aside } from '@astrojs/starlight/components'; If you are using the CLI to try things out on-chain, you will need to configure the network, faucet, and credentials you want the CLI to use. This makes using the CLI easier and more secure as you will not be forced to repeatedly copy addresses or private keys. 1. Run `aptos init` and follow the instructions in the command line. ```shellscript filename="Terminal" aptos init ``` ```shellscript filename="Output" Configuring for profile default Enter your rest endpoint [Current: None | No input: https://api.devnet.aptoslabs.com] No rest url given, using https://api.devnet.aptoslabs.com... Enter your faucet endpoint [Current: None | No input: https://faucet.devnet.aptoslabs.com] No faucet url given, using https://faucet.devnet.aptoslabs.com... Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)] No key given, generating key... Account 00f1f20ddd0b0dd2291b6e42c97274668c479bca70f07c6b6a80b99720779696 doesn't exist, creating it and funding it with 10000 coins Aptos is now set up for account 00f1f20ddd0b0dd2291b6e42c97274668c479bca70f07c6b6a80b99720779696! Run `aptos help` for more information about commands { "Result": "Success" } ``` 2. Later, if you want to update these settings, you can do so by running `aptos init` again. 3. The rest of these configuration steps are optional / quality of life. To continue to use the CLI for your specific use case, follow the [usage guide here](/build/cli#%EF%B8%8F-using-the-aptos-cli). ## (Optional) Creating Named Configurations (Profiles) For testing more complicated scenarios, you will often want multiple accounts on-chain. One way to do this is to create a named configuration which we call a profile. To create a profile, run `aptos init --profile `. The configuration you generate will be usable when calling CLI commands as replacements for arguments. For example: ```shellscript filename="Terminal" aptos init --profile bob ``` ```shellscript filename="Terminal" aptos account fund-with-faucet --profile bob ``` ```shellscript filename="Output" { "Result": "Added 100000000 Octas to account 0x63169727b08fc137b8720e451f7a90584ccce04c301e151daeadc7b8191fdfad" } ``` ## (Optional) Setting Up Shell Completion One quality of life feature you can enable is shell auto-completions. 1. Determine which shell you are using (you can run `echo $SHELL` if you are unsure). 2. Look up where configuration files for shell completions go for that shell (it varies from shell to shell). The supported shells are `[bash, zsh, fish, PowerShell, elvish]`. 3. Run the following command with your specific shell and the output file for completions using your shell: ```shellscript filename="Terminal" aptos config generate-shell-completions --shell --output-file ``` Example command for [`oh my zsh`](https://ohmyz.sh/): ```shellscript filename="Terminal" aptos config generate-shell-completions --shell zsh --output-file ~/.oh-my-zsh/completions/_aptos ``` ## (Optional) Global Config By default, the CLI will look for a configuration in `.aptos/config.yaml` in each workspace directory. If you would like to use a shared configuration for all workspaces, you can follow these steps: 1. Create a folder in your home directory called `.aptos` (so it has the path `~/.aptos`). 2. Create a yaml file inside `.aptos` called `global_config.yaml`. 3. Run the command: ```shellscript filename="Terminal" aptos config set-global-config --config-type global ``` You should see: ```json { "Result": { "config_type": "Global" } } ``` # Install the Move Prover > Step-by-step guide to install and set up the Move Prover dependencies for formal verification of Move smart contracts using the Aptos CLI. import { Aside } from '@astrojs/starlight/components'; If you want to use the [Move Prover](/build/smart-contracts/prover), install the Move Prover dependencies after [installing the CLI binary](/build/cli/setup-cli). There are two ways to install Prover dependencies. ## Installation through Aptos CLI (Recommended) 1. [Install the latest Aptos CLI binary](/build/cli/install-cli/install-cli-mac). 2. Execute the command `aptos update prover-dependencies`. ## Installation through `aptos-core` (Not Recommended) 1. See [Building Aptos From Source](/network/nodes/building-from-source) 2. Then, in the checked out aptos-core directory, install additional Move tools:
Linux / macOS 1. Open a Terminal session. 2. Run the dev setup script to prepare your environment: `./scripts/dev_setup.sh -yp` 3. Update your current shell environment: `source ~/.profile`
Windows 1. Open a PowerShell terminal as an administrator. 2. Run the dev setup script to prepare your environment: `PowerShell -ExecutionPolicy Bypass -File ./scripts/windows_dev_setup.ps1 -y`
After installation, you can run the Move Prover to prove an [example](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_prover): ```shellscript filename="Terminal" aptos move prove --package-dir aptos-move/move-examples/hello_prover/ ``` ## Troubleshooting If you encounter errors like the one below when running the command, double-check your Aptos CLI version or verify that you're using the correct `aptos` tool, especially if you have multiple versions installed. ```shellscript filename="Terminal error: unexpected token β”Œβ”€ ~/.move/https___github_com_aptos-labs_aptos-core_git_main/aptos-move/framework/aptos-framework/sources/randomness.move:515:16 β”‚ 515 β”‚ for (i in 0..n) { β”‚ - ^ Expected ')' β”‚ β”‚ β”‚ To match this '(' { "Error": "Move Prover failed: exiting with model building errors" } ``` # Start a Move package from a template > Quickly bootstrap new Move projects using built-in templates with the Aptos CLI, including the hello-blockchain template and customization options. import { CardGrid, LinkCard, Steps } from '@astrojs/starlight/components'; import { RemoteCodeblock } from '~/components/RemoteCodeblock'; Follow the steps below to quickly get started. 1. Initialize Run the following to initialize a package using the `hello-blockchain` template: ```shellscript filename="Terminal" aptos move init --name hello_blockchain --template hello-blockchain ``` 2. Start building The template creates a `hello_blockchain.move` file under `sources` to help get you started. 3. See all templates Run the following command to see all templates (and for general help initializing a package): ```shellscript aptos move init --help ``` ### Learn More # Trying Things On-Chain With Aptos CLI > Learn how to interact with the Aptos blockchain using CLI profiles, including account management, transaction sending, and hardware wallet integration. The CLI can be a convenient tool for quickly looking up on-chain data and sending transactions from your accounts. The most common way to specify what accounts you want to interact with is through profiles. You can create a new profile on the cli by running the following command: ```shellscript filename="Terminal" aptos init --profile ``` If any command takes an account, you can pass in the name of a profile instead. If a command implicitly uses the default profile, it will usually have an optional parameter to use a specified profile instead which you can find by running `aptos --help`. With that, the three main things you can use the CLI to do on-chain include: 1. [Looking Up On-Chain Account Info](/build/cli/trying-things-on-chain/looking-up-account-info) 2. [Creating test accounts and sending transactions](/build/cli/trying-things-on-chain/create-test-accounts) 3. [Securely interacting on-chain via a Hardware Ledger](/build/cli/trying-things-on-chain/ledger) # Create Test Accounts and Send Transactions From Aptos CLI > Learn how to create test accounts, fund them with faucet tokens, and send transactions between accounts using the Aptos CLI for testing and development. import { Aside } from '@astrojs/starlight/components'; In general, to make a new account on-chain, you will need to generate keys and then fund the account. On devnet, you can fund a new account by asking a "faucet" account with test Aptos tokens to send them to your account. On testnet you can mint at the [mint page](/network/faucet). Using the CLI, you can generate and fund a test account using: ```shellscript filename="Terminal" aptos init --profile ``` Once you have a funded account you can send coins between accounts with the `transfer` command like this: ```shellscript filename="Terminal" aptos account transfer --account superuser --amount 100 ``` You should see a result like: ```json filename="Output" { "Result": { "gas_used": 73, "balance_changes": { "742854f7dca56ea6309b51e8cebb830b12623f9c9d76c72c3242e4cad353dedc": { "coin": { "value": "10100" }, "deposit_events": { "counter": "2", "guid": { "id": { "addr": "0x742854f7dca56ea6309b51e8cebb830b12623f9c9d76c72c3242e4cad353dedc", "creation_num": "1" } } }, "withdraw_events": { "counter": "0", "guid": { "id": { "addr": "0x742854f7dca56ea6309b51e8cebb830b12623f9c9d76c72c3242e4cad353dedc", "creation_num": "2" } } } }, "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb": { "coin": { "value": "9827" }, "deposit_events": { "counter": "1", "guid": { "id": { "addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "creation_num": "1" } } }, "withdraw_events": { "counter": "1", "guid": { "id": { "addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "creation_num": "2" } } } } }, "sender": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "success": true, "version": 1139, "vm_status": "Executed successfully" } } ``` This can be useful for manual testing of Move contracts or just to try seeing how the chain works in practice. # Use Hardware Ledger via the Aptos CLI > Learn how to securely interact with the Aptos blockchain using hardware Ledger devices, including setup, key rotation, and transaction signing. import { Aside, Steps } from '@astrojs/starlight/components'; Using a hardware wallet like Ledger is the most secure way to sign transactions on `mainnet` as your private key never leaves your device. ## Initial Setup You will need to do a few steps of configuration for the Aptos CLI and your Ledger device to sign transactions. 1. Ensure you have the Aptos CLI installed. You can install the Aptos CLI by following [these steps](/build/cli) if you have not done so already. 2. Ensure you have done the basic setup for your Ledger device. You can find those steps on [Ledger’s website](https://www.ledger.com/). For example, here are the set up instructions for the [Ledger Nano X](https://support.ledger.com/article/360018784134-zd). 3. Plug your Ledger device into your computer. 4. Install the Aptos App on your Ledger device by following . 5. Unlock your Ledger device and open the Aptos app. 6. Create a new Ledger profile in the Aptos CLI ```shellscript filename="Terminal" aptos init --profile --ledger ``` Then follow the terminal prompts like so: ```text filename="Terminal" Configuring for 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 ! 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. 7. Finally, you will need to enable blind signing on your Ledger device by following . 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. 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: ```shellscript filename="Terminal" aptos move publish --profile --named-addresses hello_blockchain= ``` You should see a response like: ```shellscript filename="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 corresponds to a [BIP44 account index] private key held on 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 to avoid memory issues. This tutorial will walk you through both scenarios. 1. Complete the key rotation guide Confirm that you have completed the [key rotation guide](/build/guides/key-rotation). 2. Verify your Ledger is ready 1. Connect and unlock your Ledger. 2. Check what version of the Aptos app you have: `Aptos > About > Version`. 3. If you do not have version `0.6.9` or higher, update it using Ledger Live. 4. Enable blind signing: `Aptos > Settings > Enable Blind Signing`. 3. Start a localnet Start a localnet: ```shellscript filename="Terminal" aptos node run-localnet ``` The localnet is ready when it prints out: ```shellscript filename="Terminal" Applying post startup steps... Setup is complete, you can now use the localnet! ``` 4. Set up localnet hot wallet profile Create a private key corresponding to an authentication key, and thus initial account address, that starts with the vanity prefix `0xaaa`: ```shellscript filename="Terminal" aptos key generate \ --assume-yes \ --output-file private-key-a \ --vanity-prefix 0xaaa ```
Example output ```shellscript filename="Terminal" { "Result": { "PublicKey Path": "private-key-a.pub", "PrivateKey Path": "private-key-a", "Account Address:": "0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5" } } ```
Use the private key to initialize a `hot-wallet-1` profile on the localnet: ```shellscript filename="Terminal" aptos init \ --assume-yes \ --network local \ --private-key-file private-key-a \ --profile hot-wallet-1 ```
Example output ```shellscript filename="Terminal" Configuring for profile hot-wallet-1 Configuring for network Local Using command line argument for private key Account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 doesn\'t exist, creating it and funding it with 100000000 Octas Account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 funded successfully --- Aptos CLI is now set up for account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 as profile hot-wallet-1! Run `aptos --help` for more information about commands { "Result": "Success" } ```
5. Rotate the hot wallet key Rotate the authentication key of the hot wallet to use [BIP44 account index] 1000 on your Ledger: ```shellscript filename="Terminal" aptos account rotate-key \ --assume-yes \ --new-derivation-index 1000 \ --profile hot-wallet-1 \ --save-to-profile ledger-wallet-1000 ``` Follow the instructions from the CLI prompt: ```shellscript filename="Terminal" Approve rotation proof challenge signature on your Ledger device ```
Example output ```shellscript filename="Terminal" { "Result": { "message": "Saved new profile ledger-wallet-1000", "transaction": { "transaction_hash": "0x1a6df99651ac170bda10cfb9898fa196321d80a928033791b9d2231f77738bb2", "gas_used": 448, "gas_unit_price": 100, "sender": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", "sequence_number": 0, "success": true, "timestamp_us": 1717986382369736, "version": 186, "vm_status": "Executed successfully" } } } ```
Compare the `hot-wallet-1` and `ledger-wallet-1000` profiles, noting that they have the same `account` address but different `public_key` values: ```shellscript filename="Terminal" aptos config show-profiles --profile hot-wallet-1 aptos config show-profiles --profile ledger-wallet-1000 ```
Example output ```shellscript filename="Terminal" { "Result": { "hot-wallet-1": { "has_private_key": true, "public_key": "0xffb1240fd1267207cc3ed2e1b5386e090a9ca2c844d7f9e0077b3d7dd5d5e430", "account": "aaa271bca468fb8518f73a732a484b29a1bc296ebcb23f15639d4865a5cebe87", "rest_url": "http://localhost:8080", "faucet_url": "http://localhost:8081" } } } { "Result": { "ledger-wallet-1000": { "has_private_key": false, "public_key": "0x20ba83f9b9fdab73b0ace8fda26ce24c98cf55060b72b69cfbd25add6a25d09b", "account": "aaa271bca468fb8518f73a732a484b29a1bc296ebcb23f15639d4865a5cebe87", "rest_url": "http://localhost:8080", "faucet_url": "http://localhost:8081" } } } ```
Since the account is no longer secured by the hot private key, delete the private and public key files. Now that you have successfully rotated the authentication key of the hot wallet, you can delete the profiles too: ```shellscript filename="Terminal" aptos config delete-profile --profile hot-wallet-1 aptos config delete-profile --profile ledger-wallet-1000 ```
Example output ```shellscript filename="Terminal" { "Result": "Deleted profile hot-wallet-1" } { "Result": "Deleted profile ledger-wallet-1000" } ```
6. Recover profile Since you know that you rotated the authentication key of the hot wallet to the Ledger, and since you used the best practice of a [BIP44 account index] offset of 1000, you can easily recover the profile using the [BIP44 account index] alone: ```shellscript filename="Terminal" aptos init \ --assume-yes \ --derivation-index 1000 \ --network local \ --profile ledger-wallet-1000-recovered ```
Example output ```shellscript filename="Terminal" Configuring for profile ledger-wallet-1000-recovered Configuring for network Local Account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 has been already found onchain --- Aptos CLI is now set up for account 0xaaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5 as profile ledger-wallet-1000-recovered! Run `aptos --help` for more information about commands { "Result": "Success" } ```
Note that this profile corresponds to the specified `0xaaa...` vanity account address: ```shellscript filename="Terminal" aptos config show-profiles --profile ledger-wallet-1000-recovered ```
Example output ```shellscript filename="Terminal" { "Result": { "ledger-wallet-1000-recovered": { "has_private_key": false, "public_key": "0x20ba83f9b9fdab73b0ace8fda26ce24c98cf55060b72b69cfbd25add6a25d09b", "account": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", "rest_url": "http://localhost:8080", "faucet_url": "http://localhost:8081" } } } ```
7. Rotate to new hot private key If you have an account linked with a Ledger hardware wallet that you wish to use for publication of a large package, you'll be unable to sign the package publication transaction due to the Ledger's memory limitations. In this case, you'll want to temporarily rotate to a hot wallet. Start by generating a new private key: ```shellscript filename="Terminal" aptos key generate \ --assume-yes \ --output-file private-key-b \ --vanity-prefix 0xbbb ```
Example output ```shellscript filename="Terminal" { "Result": { "PublicKey Path": "private-key-b.pub", "PrivateKey Path": "private-key-b", "Account Address:": "0xbbbede2b4f1d49eff0b156ab0756889a6f2bb68f215399d5015da9ac45921b47" } } ```
Rotate the authentication key of the account linked with the Ledger to the new private key: ```shellscript filename="Terminal" aptos account rotate-key \ --assume-yes \ --new-private-key-file private-key-b \ --profile ledger-wallet-1000-recovered \ --save-to-profile temporary-hot-wallet ``` Follow the instructions from the CLI prompt: ```shellscript filename="Terminal" Approve rotation proof challenge signature on your Ledger device ``` ```shellscript filename="Terminal" Approve transaction on your Ledger device ```
Example output ```shellscript filename="Terminal" { "Result": { "message": "Saved new profile temporary-hot-wallet", "transaction": { "transaction_hash": "0xe49782e92d8fd824fd6dce8f6ed42a11cf8ee84c201f3aa639c435e737c80eaa", "gas_used": 449, "gas_unit_price": 100, "sender": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", "sequence_number": 1, "success": true, "timestamp_us": 1717986617911082, "version": 631, "vm_status": "Executed successfully" } } ```
Since the CLI profile `ledger-wallet-1000-recovered` is now stale, rename it in case you get interrupted and forget that the private key has been rotated: ```shellscript filename="Terminal" aptos config rename-profile \ --profile ledger-wallet-1000-recovered \ --new-profile-name ledger-wallet-1000-stale ```
Example output ```shellscript filename="Terminal" { "Result": "Renamed profile ledger-wallet-1000-recovered to ledger-wallet-1000-stale" } ```
8. Rotate back to Ledger Once you've signed the large package publication transaction with the hot key, you can then rotate the authentication key back to the corresponding to the private key on the Ledger at index 1000: ```shellscript filename="Terminal" aptos account rotate-key \ --assume-yes \ --new-derivation-index 1000 \ --profile temporary-hot-wallet \ --save-to-profile ledger-wallet-1000 ``` Follow the instructions from the CLI prompt: ```shellscript filename="Terminal" Approve rotation proof challenge signature on your Ledger device ```
Example output ```shellscript filename="Terminal" { "Result": { "message": "Saved new profile ledger-wallet-1000", "transaction": { "transaction_hash": "0x9503819d4ea13bcd9eafed25984807d86d22e8a9837565a7495b54d13890d103", "gas_used": 449, "gas_unit_price": 100, "sender": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", "sequence_number": 2, "success": true, "timestamp_us": 1717986672963544, "version": 742, "vm_status": "Executed successfully" } } } ```
Verify that the `ledger-wallet-1000-stale` and `ledger-wallet-1000` profiles have the same `account` address and `public_key`: ```shellscript filename="Terminal" aptos config show-profiles --profile ledger-wallet-1000-stale aptos config show-profiles --profile ledger-wallet-1000 ``` Delete the `temporary-hot-wallet` and `ledger-wallet-1000-stale` profiles, which you no longer need. ```shellscript filename="Terminal" aptos config delete-profile --profile temporary-hot-wallet aptos config delete-profile --profile ledger-wallet-1000-stale ```
Example output ```shellscript filename="Terminal" { "Result": "Deleted profile temporary-hot-wallet" } { "Result": "Deleted profile ledger-wallet-1000-stale" } ```
Since you no longer need the temporary private key, delete it too. 9. Clean up Delete the remaining test profile: ```shell filename="Terminal" aptos config delete-profile --profile ledger-wallet-1000 ``` Then stop the localnet.
[`account::OriginatingAddress`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70 [BIP44 account index]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki # Look Up On-Chain Account Info Using Aptos CLI > Learn how to query on-chain account information including balances, resources, and Move modules using the Aptos CLI with detailed examples and output explanations. import { Aside } from '@astrojs/starlight/components'; You can look up resources and data an account has on-chain by running the following command: ```shellscript filename="Terminal" aptos account list --account ``` This will show all resources that an account has. For example, below shows the balance as `coin:value`, and the associated coin for the native gas token APT would be `0x1::aptos_coin::AptosCoin`. This is represented in subdivisions, so in this case it's `10^-8` or 8 zeros of decimal points. ```json filename="Output" { "Result": [ { "coin": { "value": "110000" }, "deposit_events": { "counter": "3", "guid": { "id": { "addr": "0xf1f20ddd0b0dd2291b6e42c97274668c479bca70f07c6b6a80b99720779696", "creation_num": "2" } } }, "frozen": false, "withdraw_events": { "counter": "0", "guid": { "id": { "addr": "0xf1f20ddd0b0dd2291b6e42c97274668c479bca70f07c6b6a80b99720779696", "creation_num": "3" } } } } ] } ``` If you’re interested in a specific type of account data, you can specify that with the `--query` parameter. The supported queries are: - `balance` - to see the current balance and a list of deposit and withdrawal events. - `modules` - see the Move contracts that are published on this account. - `resources` - this is what the default command does with no query specified. Here’s an example of what calling with the `--query modules` parameter looks like: ```shellscript filename="Terminal" aptos account list --query modules ``` This will show all modules that an account has. For example: ```json filename="Output" { "Result": [ { "bytecode": "0xa11ceb0b050000000b01000a020a12031c2504410405452d0772da0108cc0240068c030a0a9603150cab03650d90040400000101010201030104000506000006080004070700020e0401060100080001000009020300010f0404000410060100031107000002120709010602130a030106050806080105010802020c0a02000103040508020802070801010a0201060c010800010b0301090002070b030109000900074d657373616765056572726f72056576656e74067369676e657206737472696e67124d6573736167654368616e67654576656e740d4d657373616765486f6c64657206537472696e670b6765745f6d6573736167650b7365745f6d6573736167650c66726f6d5f6d6573736167650a746f5f6d657373616765076d657373616765156d6573736167655f6368616e67655f6576656e74730b4576656e7448616e646c65096e6f745f666f756e6404757466380a616464726573735f6f66106e65775f6576656e745f68616e646c650a656d69745f6576656e74b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb0000000000000000000000000000000000000000000000000000000000000001030800000000000000000002020a08020b08020102020c08020d0b030108000001000101030b0a002901030607001102270b002b0110001402010104010105240b0111030c040e0011040c020a02290120030b05120e000b040e00380012012d0105230b022a010c050a051000140c030a050f010b030a04120038010b040b050f0015020100010100", "abi": { "address": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "name": "Message", "friends": [], "exposed_functions": [ { "name": "get_message", "visibility": "public", "is_entry": false, "generic_type_params": [], "params": [ "address" ], "return": [ "0x1::string::String" ] }, { "name": "set_message", "visibility": "public", "is_entry": true, "generic_type_params": [], "params": [ "signer", "vector" ], "return": [] } ], "structs": [ { "name": "MessageChangeEvent", "is_native": false, "abilities": [ "drop", "store" ], "generic_type_params": [], "fields": [ { "name": "from_message", "type": "0x1::string::String" }, { "name": "to_message", "type": "0x1::string::String" } ] }, { "name": "MessageHolder", "is_native": false, "abilities": [ "key" ], "generic_type_params": [], "fields": [ { "name": "message", "type": "0x1::string::String" }, { "name": "message_change_events", "type": "0x1::event::EventHandle<0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb::Message::MessageChangeEvent>" } ] } ] } } ] } ``` # Working With Move Contracts > Comprehensive guide to compile, test, publish, and run Move smart contracts using the Aptos CLI with examples and best practices. import { Aside } from '@astrojs/starlight/components'; The Aptos CLI is mostly used to compile, test, and formally verify Move contracts. If you have not installed the Aptos CLI yet, you can do so by following the steps here [Install the Aptos CLI](/build/cli#-install-the-aptos-cli). You can jump to specific sections by using the table of contents on the right. To see how to chain together Move contracts on-chain using the CLI, you can follow this ["CLI Arguments" tutorial](/build/cli/working-with-move-contracts/arguments-in-json-tutorial). ## 1. Compiling Move You can compile a Move package by running: ```shellscript filename="Terminal" aptos move compile --package-dir ``` Based on the settings in your `Move.toml` file, you may need to pass in additional information to that compile command. For example, if you look at the [hello\_blockchain example Move contract](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain), in the `Move.toml` file it specifies a variable named address called `hello_blockchain`. ```toml filename="Move.toml" [addresses] hello_blockchain = "_" ``` So, to compile this, you will need to pass in the value for `hello_blockchain` with the `--named-addresses` parameter. You can use either a full address e.g. `0x123456...7890` or a name of a profile in the CLI e.g. `default` or `superuser`. Below we will use `default` in our example: ```shellscript filename="Terminal" aptos move compile --package-dir aptos-move/move-examples/hello_blockchain/ --named-addresses hello_blockchain=default ``` You can learn more about optional parameters when compiling Move contracts by running `aptos move compile --help`. ## 2. Unit Testing Move Contracts The Aptos CLI can also be used to compile and run unit tests locally by running: ```shellscript filename="Terminal" aptos move test --package-dir ``` This command both compiles and runs tests, so it needs all the same optional parameters you use when compiling. You can learn more about the optional parameters for testing move contracts by running `aptos move test --help`. ### Printing Debugging Information When writing tests, it can be helpful to print out debug information or stack traces. You can do that by using `debug::print` and `debug::print_stack_trace` to print information when you use `aptos move test`. See an example of how they are used in [DebugDemo.move](https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/debug-move-example/sources/DebugDemo.move). To see the output of testing [DebugDemo.move](https://github.com/aptos-labs/aptos-core/blob/main/crates/aptos/debug-move-example/sources/DebugDemo.move)’s package: 1. Clone `[aptos-core](https://github.com/aptos-labs/aptos-core)`. 2. Navigate to the [debug-move-example](https://github.com/aptos-labs/aptos-core/tree/main/crates/aptos/debug-move-example) by running `cd crates/aptos/debug-move-example`. 3. Run `aptos move test`. You should see: ```shellscript filename="Terminal" Running Move unit tests [debug] 0000000000000000000000000000000000000000000000000000000000000001 Call Stack: [0] 0000000000000000000000000000000000000000000000000000000000000001::Message::sender_can_set_message Code: [4] CallGeneric(0) [5] MoveLoc(0) [6] LdConst(0) > [7] Call(1) [8] Ret Locals: [0] - [1] 0000000000000000000000000000000000000000000000000000000000000001 Operand Stack: ``` For more on how to write unit tests with Move, follow this [Move tutorial](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/move-tutorial) (step 2 focuses on unit tests). ## 3. Generating Test Coverage Reports The Aptos CLI can be used to analyze and improve the testing of your Move modules. To use this feature: To see the code coverage of your tests run the following command from your Move package’s directory: ```shellscript filename="Terminal" aptos move test --coverage ``` If you would like to focus your coverage down to specific packages, you can do so with the `--filter` option. To narrow even further to specific Move modules, use the `--module` parameter. For more detailed / advanced coverage information (such as your test coverage in the compiled bytecode) you can run `aptos move coverage` . With that command, the CLI will prompt you for more details on what specifically you would like more coverage information about. You can learn more about optional parameters for test coverage by running `aptos move test --help` and `aptos move coverage --help`. ## 4. Publishing Move Contracts To publish a Move contract, you will need to run: ```shellscript filename="Terminal" aptos move publish --package-dir ``` Note that when you are publishing on the main network, the credentials you pass into optional parameters like `--named-addresses` will need to reflect accounts on that network instead of test credentials. The package will be published to your default profile in the CLI. You can override that to specify which account to publish to using `--profile` in the command. To generate a new profile for a specific account, use `aptos init --profile ` and follow the prompts. Please also note that when publishing Move modules, if multiple modules are in one package, then all modules in that package must use the same account. If they use different accounts, then the publishing will fail at the transaction level. ## 5. Running Published Contracts Now that you have published your Move package, you can run it directly from the CLI. You will first need to construct your `function-id` by combining: ```jsx :::: ``` You can then pass in args by using the `--args` parameter. As an example, if you were to have published the [hello\_blockchain example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain) to an account with an address `b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb` you could run its `set_message` function via the following command: ```shellscript filename="Terminal" aptos move run --function-id 0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb::message::set_message --args string:hello! ``` Which should result in: ```json { "Result": { "changes": [ { "address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "data": { "authentication_key": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "self_address": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "sequence_number": "3" }, "event": "write_resource", "resource": "0x1::account::Account" }, { "address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "data": { "coin": { "value": "9777" }, "deposit_events": { "counter": "1", "guid": { "id": { "addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "creation_num": "1" } } }, "withdraw_events": { "counter": "1", "guid": { "id": { "addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "creation_num": "2" } } } }, "event": "write_resource", "resource": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>" }, { "address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "data": { "counter": "4" }, "event": "write_resource", "resource": "0x1::guid::Generator" }, { "address": "b9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "data": { "message": "hello!", "message_change_events": { "counter": "0", "guid": { "id": { "addr": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb", "creation_num": "3" } } } }, "event": "write_resource", "resource": "0xb9bd2cfa58ca29bce1d7add25fce5c62220604cd0236fe3f90d9de91ed9fb8cb::Message::MessageHolder" } ], "gas_used": 41, "success": true, "version": 3488, "vm_status": "Executed successfully" } } ``` ## 6. (Optional) Formally Verifying Move Scripts For cases where you want to guarantee that your code works as expected beyond unit testing, you can use the [Move Prover](/build/smart-contracts/prover) to formally verify your Move contract code. You can install the Move Prover by following [these steps](/build/cli/setup-cli/install-move-prover). Once you have installed the Move Prover, you can use it from the Aptos CLI by running: ```shellscript filename="Terminal" aptos move prove --package-dir ``` To learn how to formally verify your code, please follow the in-depth Move tutorial [here](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/move-tutorial) (step 7 and 8 cover how to use the Move Prover and write formal specifications in the example code). # Arguments in JSON Tutorial > Learn how to pass complex arguments to Move functions using JSON format in the Aptos CLI, including vectors, entry functions, view functions, and script functions. import { Aside } from '@astrojs/starlight/components'; ## Package info This section references the [`CliArgs` example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args), which contains the following manifest: ```toml filename="move.toml" [package] name = "CliArgs" version = "0.1.0" upgrade_policy = "compatible" [addresses] test_account = "_" [dependencies] AptosFramework = { git = "https://github.com/aptos-labs/aptos-framework.git", rev = "mainnet", subdir = "aptos-framework" } ``` Here, the package is deployed under the named address `test_account`. ## Deploying the package Start by mining a vanity address for Ace, who will deploy the package: ```shellscript filename="Terminal" aptos key generate \ --vanity-prefix 0xace \ --output-file ace.key ```
Output ```shellscript filename="Terminal" { "Result": { "Account Address:": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "PublicKey Path": "ace.key.pub", "PrivateKey Path": "ace.key" } } ```
Store Ace's address in a shell variable, so you can call it inline later on: ```shellscript filename="Terminal" # Your exact address will vary ace_addr=0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46 ``` Fund Ace's account with the faucet (only works on devnet): ```shellscript filename="Terminal" aptos account fund-with-faucet --account $ace_addr ```
Output ```shellscript filename="Terminal" { "Result": "Added 100000000 Octas to account acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46" } ```
Now publish the package under Ace's account: ```shellscript filename="Terminal" aptos move publish \ --named-addresses test_account=$ace_addr \ --private-key-file ace.key \ --assume-yes ```
Output ```json filename="Terminal" { "Result": { "transaction_hash": "0x1d7b074dd95724c5459a1c30fe4cb3875e7b0478cc90c87c8e3f21381625bec1", "gas_used": 1294, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 0, "success": true, "timestamp_us": 1685077849297587, "version": 528422121, "vm_status": "Executed successfully" } } ```
## Entry functions The only module in the package, `cli_args.move`, defines a simple `Holder` resource with fields of various data types: ```move filename="Holder in cli_args.move" module test_account::cli_args { use std::signer; use aptos_std::type_info::{Self, TypeInfo}; use std::string::String; struct Holder has key, drop { u8_solo: u8, bytes: vector, utf8_string: String, bool_vec: vector, address_vec_vec: vector>, type_info_1: TypeInfo, type_info_2: TypeInfo, } ``` A public entry function with multi-nested vectors can be used to set the fields: ```move filename="Setter function in cli_args.move" /// Set values in a `Holder` under `account`. public entry fun set_vals( account: signer, u8_solo: u8, bytes: vector, utf8_string: String, bool_vec: vector, address_vec_vec: vector>, ) acquires Holder { let account_addr = signer::address_of(&account); if (exists(account_addr)) { move_from(account_addr); }; move_to(&account, Holder { u8_solo, bytes, utf8_string, bool_vec, address_vec_vec, type_info_1: type_info::type_of(), type_info_2: type_info::type_of(), }); } ``` After the package has been published, `aptos move run` can be used to call `set_vals()`: ```shellscript filename="Running function with nested vector arguments from CLI" aptos move run \ --function-id $ace_addr::cli_args::set_vals \ --type-args \ 0x1::account::Account \ 0x1::chain_id::ChainId \ --args \ u8:123 \ "hex:0x1234" \ "string:hello, world\! β™₯" \ "bool:[false, true, false, false]" \ 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ --private-key-file ace.key \ --assume-yes ```
Output ```json filename="Terminal" { "Result": { "transaction_hash": "0x5e141dc6c28e86fa9f5594de93d07a014264ebadfb99be6db922a929eb1da24f", "gas_used": 504, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 1, "success": true, "timestamp_us": 1685077888820037, "version": 528422422, "vm_status": "Executed successfully" } } ```
The function ID, type arguments, and arguments can alternatively be specified in a JSON file: ```json filename="entry_function_arguments.json" { "function_id": "::cli_args::set_vals", "type_args": [ "0x1::account::Account", "0x1::chain_id::ChainId" ], "args": [ { "type": "u8", "value": 123 }, { "type": "hex", "value": "0x1234" }, { "type": "string", "value": "hello, world! β™₯" }, { "type": "bool", "value": [ false, true, false, false ] }, { "type": "address", "value": [ [ "0xace", "0xbee" ], [ "0xcad" ], [] ] } ] } ``` Here, the call to `aptos move run` looks like: ```shellscript filename="Running function with JSON input file" aptos move run \ --json-file entry_function_arguments.json \ --private-key-file ace.key \ --assume-yes ```
Output ```json filename="Terminal" { "Result": { "transaction_hash": "0x60a32315bb48bf6d31629332f6b1a3471dd0cb016fdee8d0bb7dcd0be9833e60", "gas_used": 3, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 2, "success": true, "timestamp_us": 1685077961499641, "version": 528422965, "vm_status": "Executed successfully" } } ```
## View functions Once the values in a `Holder` have been set, the `reveal()` view function can be used to check the first three fields, and to compare type arguments against the last two fields: ```move filename="View function" struct RevealResult has drop { u8_solo: u8, bytes: vector, utf8_string: String, bool_vec: vector, address_vec_vec: vector>, type_info_1_match: bool, type_info_2_match: bool } #[view] /// Pack into a `RevealResult` the first three fields in host's /// `Holder`, as well as two `bool` flags denoting if `T1` & `T2` /// respectively match `Holder.type_info_1` & `Holder.type_info_2`, /// then return the `RevealResult`. public fun reveal(host: address): RevealResult acquires Holder { let holder_ref = borrow_global(host); RevealResult { u8_solo: holder_ref.u8_solo, bytes: holder_ref.bytes, utf8_string: holder_ref.utf8_string, bool_vec: holder_ref.bool_vec, address_vec_vec: holder_ref.address_vec_vec, type_info_1_match: type_info::type_of() == holder_ref.type_info_1, type_info_2_match: type_info::type_of() == holder_ref.type_info_2 } } ``` This view function can be called with arguments specified either from the CLI or from a JSON file: ```shellscript filename="Arguments via CLI" aptos move view \ --function-id $ace_addr::cli_args::reveal \ --type-args \ 0x1::account::Account \ 0x1::account::Account \ --args address:$ace_addr ``` ```shellscript filename="Arguments via JSON file" aptos move view --json-file view_function_arguments.json ``` ```json filename="view_function_arguments.json" { "function_id": "::cli_args::reveal", "type_args": [ "0x1::account::Account", "0x1::account::Account" ], "args": [ { "type": "address", "value": "" } ] } ``` ```shellscript filename="Terminal" { "Result": [ { "address_vec_vec": [ [ "0xace", "0xbee" ], [ "0xcad" ], [] ], "bool_vec": [ false, true, false, false ], "bytes": "0x1234", "type_info_1_match": true, "type_info_2_match": false, "u8_solo": 123, "utf8_string": "hello, world! β™₯" } ] } ``` ## Script functions The package also contains a script, `set_vals.move`, which is a wrapper for the setter function: ```move filename="script" script { use test_account::cli_args; use std::vector; use std::string::String; /// Get a `bool` vector where each element indicates `true` if the /// corresponding element in `u8_vec` is greater than `u8_solo`. /// Then pack `address_solo` in a `vector>` and /// pass resulting argument set to public entry function. fun set_vals( account: signer, u8_solo: u8, bytes: vector, utf8_string: String, u8_vec: vector, address_solo: address, ) { let bool_vec = vector::map_ref(&u8_vec, |e_ref| *e_ref > u8_solo); let addr_vec_vec = vector[vector[address_solo]]; cli_args::set_vals(account, u8_solo, bytes, utf8_string, bool_vec, addr_vec_vec); } } ``` First compile the package (this will compile the script): ```shellscript filename="Compilation" aptos move compile --named-addresses test_account=$ace_addr ```
Output ```json filename="Terminal" { "Result": [ "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46::cli_args" ] } ```
Next, run `aptos move run-script`: ```shellscript filename="Arguments via CLI" aptos move run-script \ --compiled-script-path build/CliArgs/bytecode_scripts/set_vals.mv \ --type-args \ 0x1::account::Account \ 0x1::chain_id::ChainId \ --args \ u8:123 \ "hex:0x1234" \ "string:hello, world\! β™₯" \ "u8:[122, 123, 124, 125]" \ address:"0xace" \ --private-key-file ace.key \ --assume-yes ```
Output ```json filename="Terminal" { "Result": { "transaction_hash": "0x1d644eba8187843cc43919469112339bc2c435a49a733ac813b7bc6c79770152", "gas_used": 3, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 3, "success": true, "timestamp_us": 1685078415935612, "version": 528426413, "vm_status": "Executed successfully" } } ```
```shellscript filename="Arguments via JSON file" aptos move run-script \ --compiled-script-path build/CliArgs/bytecode_scripts/set_vals.mv \ --json-file script_function_arguments.json \ --private-key-file ace.key \ --assume-yes ```
Output ```json filename="Terminal" { "Result": { "transaction_hash": "0x840e2d6a5ab80d5a570effb3665f775f1755e0fd8d76e52bfa7241aaade883d7", "gas_used": 3, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 4, "success": true, "timestamp_us": 1685078516832128, "version": 528427132, "vm_status": "Executed successfully" } } ```
```json filename="script_function_arguments.json" { "type_args": [ "0x1::account::Account", "0x1::chain_id::ChainId" ], "args": [ { "type": "u8", "value": 123 }, { "type": "hex", "value": "0x1234" }, { "type": "string", "value": "hello, world! β™₯" }, { "type": "u8", "value": [ 122, 123, 124, 125 ] }, { "type": "address", "value": "0xace" } ] } ``` Both such script function invocations result in the following `reveal()` view function output: ```shellscript filename="View function call" aptos move view \ --function-id $ace_addr::cli_args::reveal \ --type-args \ 0x1::account::Account \ 0x1::chain_id::ChainId \ --args address:$ace_addr ``` ```json filename="View function output" { "Result": [ { "address_vec_vec": [["0xace"]], "bool_vec": [false, false, true, true], "bytes": "0x1234", "type_info_1_match": true, "type_info_2_match": true, "u8_solo": 123, "utf8_string": "hello, world! β™₯" } ] } ``` # Local Simulation, Benchmarking & Gas Profiling > Learn how to simulate, benchmark, and profile gas usage of Move transactions locally using the Aptos CLI for performance optimization and debugging. import { Aside, FileTree } from '@astrojs/starlight/components'; ## Overview The previous tutorial demonstrates how you can deploy and interact with Move contracts using various CLI commands. By default, those commands send a transaction to the remote fullnode for simulation and execution. You can override this behavior and simulate the transaction locally, by appending one of the following command line options of your preference: - `--local`: Simulate the transaction locally without conducting any further measurements or analysis. - `--benchmark`: Benchmark the transaction and report the running time(s). - `--profile-gas`: Profile the transaction for detailed gas usage. These additional options can be used in combination with the following CLI commands: - `aptos move run` - `aptos move run-script` - `aptos move publish` Alternatively, if you are interested in replaying a past transaction, check out [this tutorial](/build/cli/replay-past-transactions). ## Deploying the Example Contract For demonstration purposes, we will continue to use the [`hello_blockchain`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain) package as an example. First, publish the package to devnet or testnet (if you haven't done so already). Change into the package directory. ```shellscript filename="Terminal" cd aptos-move/move-examples/hello_blockchain ``` Then publish the package using the following command. ```shellscript filename="Terminal" aptos move publish --named-addresses hello_blockchain=default --assume-yes ```
Output ```shellscript { "Result": { "transaction_hash": "0xe4ae0ec4ea3474b2123838885b04d7f4b046c174d14d7dc1c56916f2eb553bcf", "gas_used": 1118, "gas_unit_price": 100, "sender": "dbcbe741d003a7369d87ec8717afb5df425977106497052f96f4e236372f7dd5", "sequence_number": 5, "success": true, "timestamp_us": 1713914742422749, "version": 1033819503, "vm_status": "Executed successfully" } } ```
Notice that you do need to have your CLI profile set up properly and bind the named addresses correctly. Please refer to [CLI Configuration](/build/cli/setup-cli) for more details. ## Local Simulation Next, execute the entry function message::set\_message with local simulation enabled using the additional command line option `--local`. This will execute the transaction locally without conducting any further measurements or analysis. ```shellscript filename="Terminal" aptos move run --function-id 'default::message::set_message' --args 'string:abc' --local ```
Output ```shellscript Simulating transaction locally... { "Result": { "transaction_hash": "0x5aab20980688185eed2c9a27bab624c84b8b8117241cd4a367ba2a012069f57b", "gas_used": 441, "gas_unit_price": 100, "sender": "dbcbe741d003a7369d87ec8717afb5df425977106497052f96f4e236372f7dd5", "success": true, "version": 1033887414, "vm_status": "status EXECUTED of type Execution" } } ```
## Benchmarking To measure the running time(s) of your transaction, use the `--benchmark` option. ```shellscript filename="Terminal" aptos move run --function-id 'default::message::set_message' --args 'string:abc' --benchmark ```
Output ```shellscript Benchmarking transaction locally... Running time (cold code cache): 985.141Β΅s Running time (warm code cache): 848.159Β΅s { "Result": { "transaction_hash": "0xa2fe548d37f12ee79df13e70fdd8212e37074c1b080b89b7d92e82550684ecdb", "gas_used": 441, "gas_unit_price": 100, "sender": "dbcbe741d003a7369d87ec8717afb5df425977106497052f96f4e236372f7dd5", "success": true, "version": 1033936831, "vm_status": "status EXECUTED of type Execution" } } ```
It's worth noting that these running times serve only as informational references, as they are contingent upon the specifications of your local machine and may be influenced by noise or other random factors. **If you are aiming to optimize your contract, you should base your decisions on the gas profiling results.** ## Gas Profiling The Aptos Gas Profiler is a powerful tool that can help you understand the gas usage of Aptos transactions. Once activated, it will simulate transactions using an instrumented VM, and generate a web-based report. The gas profiler can also double as a debugger since the report also includes a full execution trace. ### Using the Gas Profiler The gas profiler can be invoked by appending the `--profile-gas` option. ```shellscript filename="Terminal" aptos move run --function-id 'default::message::set_message' --args 'string:abc' --profile-gas ```
Output ```shellscript Simulating transaction locally using the gas profiler... Gas report saved to gas-profiling/txn-d0bc3422-0xdbcb-message-set_message. { "Result": { "transaction_hash": "0xd0bc342232f14a6a7d2d45251719aee45373bdb53f68403cfc6dc6062c74fa9e", "gas_used": 441, "gas_unit_price": 100, "sender": "dbcbe741d003a7369d87ec8717afb5df425977106497052f96f4e236372f7dd5", "success": true, "version": 1034003962, "vm_status": "status EXECUTED of type Execution" } } ```
You can then find the generated gas report in the directory `gas-profiling`: - hello\_blockchain/ - Move.toml - sources/ - gas-profiling/ - txn-XXXXXXXX-0xXXXX-message-set\_message/ - assets/ - index.html `index.html` is the main page of the report, which can view using your web browser. [Sample report](/gas-profiling/sample-report-2/index.html) ### Understanding the Gas Report The gas report consists of three sections that help you to understand the gas usage through different lenses. #### Flamegraphs The first section consists of visualization of the gas usage in the form of two flamegraphs: one for execution & IO, the other for storage. The reason why we need two graphs is that these are measured in different units: one in gas units, and the other in APT. It is possible to interact with various elements in the graph. If you hover your cursor over an item, it will show you the precise cost and percentage. ![gas-profiling-flamegraph-0.png](~/images/gas-profiling-flamegraph-0.png) If you click on an item, you can zoom into it and see the child items more clearly. You can reset the view by clicking the "Reset Zoom" button in the top-left corner. ![gas-profiling-flamegraph-1.png](~/images/gas-profiling-flamegraph-1.png) There is also "Search" button in the top-right corner that allows to match certain items and highlight them. ![gas-profiling-flamegraph-2.png](~/images/gas-profiling-flamegraph-2.png) #### Cost Break-down The second section is a detailed break-down of all gas costs. Data presented in this section is categorized, aggregated and sorted. This can be especially helpful if you know what numbers to look at. For example, the following tables show the execution costs of all Move bytecode instructions/operations. The percentage here is relative to the total cost of the belonging category (Exec + IO in this case). ![gas-profiling-cost-break-down-table.png](~/images/gas-profiling-cost-break-down-table.png) #### Full Execution Trace The final section of the gas report is the full execution trace of the transaction that looks like this: ```text filename="Example execution trace" intrinsic 2.76 85.12% dependencies 0.0607 1.87% 0xdbcb..::message 0.0607 1.87% 0xdbcb..::message::set_message 0.32416 10.00% create_ty 0.0004 0.01% create_ty 0.0004 0.01% create_ty 0.0004 0.01% create_ty 0.0004 0.01% create_ty 0.0008 0.02% imm_borrow_loc 0.00022 0.01% call 0.00441 0.14% 0x1::signer::address_of 0.007534 0.23% create_ty 0.0008 0.02% move_loc 0.000441 0.01% call 0.004043 0.12% 0x1::signer::borrow_address 0.000735 0.02% read_ref 0.001295 0.04% ret 0.00022 0.01% st_loc 0.000441 0.01% copy_loc 0.000854 0.03% load<0xdbcb..::0xdbcb..::message::MessageHolder> 0.302385 9.33% exists_generic 0.000919 0.03% not 0.000588 0.02% br_false 0.000441 0.01% imm_borrow_loc 0.00022 0.01% move_loc 0.000441 0.01% pack 0.000955 0.03% move_to_generic 0.001838 0.06% branch 0.000294 0.01% @28 ret 0.00022 0.01% ledger writes 0.097756 3.01% transaction events state write ops 0.097756 3.01% create<0xdbcb..::0xdbcb..::message::MessageHolder> 0.097756 3.01% ``` The left column lists all Move instructions and operations being executed, with each level of indentation indicating a function call. The middle column represents the gas costs associated with the operations. There is also a special notation `@number` that represents a jump to a particular location in the byte code. (`@28` in the snippet above) This is purely informational and to help understand the control flow. # Multisig Governance Tutorial > Learn how to create and use multisig accounts for governance operations using the Aptos CLI with hands-on examples for transaction proposals and execution. import { Aside } from '@astrojs/starlight/components'; ## Background This section builds upon the [Arguments in JSON tutorial](/build/cli/working-with-move-contracts/arguments-in-json-tutorial). If you have not done that, please complete that tutorial first. This tutorial likewise references the [`CliArgs` example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args). For this example, Ace and Bee will conduct governance operations from a 2-of-2 "multisig v2" account (an on-chain multisig account per [`multisig_account.move`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/multisig_account.move)) ## Account creation Since Ace's account was created during the [Arguments in JSON](/build/cli/working-with-move-contracts/arguments-in-json-tutorial) tutorial, start by mining a vanity address account for Bee too: ```shellscript filename="Terminal" aptos key generate \ --vanity-prefix 0xbee \ --output-file bee.key ```
Output ```shellscript filename="Terminal" { "Result": { "PublicKey Path": "bee.key.pub", "PrivateKey Path": "bee.key", "Account Address:": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc" } } ```
Store Bee's address in a shell variable, so you can call it inline later on: ```shellscript filename="Terminal" # Your exact address should vary bee_addr=0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc ``` Fund Bee's account using the faucet: ```shellscript filename="Terminal" aptos account fund-with-faucet --account $bee_addr ```
Output ```shellscript filename="Terminal" { "Result": "Added 100000000 Octas to account beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc" } ```
Ace can now create a multisig account: ```shellscript filename="Terminal" aptos multisig create \ --additional-owners $bee_addr \ --num-signatures-required 2 \ --private-key-file ace.key \ --assume-yes ```
Output ```shellscript filename="Terminal" { "Result": { "multisig_address": "57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5", "transaction_hash": "0x849cc756de2d3b57210f5d32ae4b5e7d1f80e5d376233885944b6f3cc2124a05", "gas_used": 1524, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 5, "success": true, "timestamp_us": 1685078644186194, "version": 528428043, "vm_status": "Executed successfully" } } ```
Store the multisig address in a shell variable: ```shellscript filename="Terminal" # Your address should vary multisig_addr=0x57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5 ``` ## Inspect the multisig Use the assorted [`multisig_account.move` view functions](https://github.com/aptos-labs/aptos-core/blob/9fa0102c3e474d99ea35a0a85c6893604be41611/aptos-move/framework/aptos-framework/sources/multisig_account.move#L237) to inspect the multisig: ```shellscript filename="Number of signatures required" aptos move view \ --function-id 0x1::multisig_account::num_signatures_required \ --args \ address:"$multisig_addr" ```
Output ```shellscript filename="Terminal" { "Result": [ "2" ] } ```
```shellscript filename="Owners" aptos move view \ --function-id 0x1::multisig_account::owners \ --args \ address:"$multisig_addr" ```
Output ```shellscript filename="Terminal" { "Result": [ [ "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46" ] ] } ```
```shellscript filename="Last resolved sequence number" aptos move view \ --function-id 0x1::multisig_account::last_resolved_sequence_number \ --args \ address:"$multisig_addr" ```
Output ```shellscript filename="Terminal" { "Result": [ "0" ] } ```
```shellscript filename="Next sequence number" aptos move view \ --function-id 0x1::multisig_account::next_sequence_number \ --args \ address:"$multisig_addr" ```
Output ```shellscript filename="Terminal" { "Result": [ "1" ] } ```
## Enqueue a publication transaction The first multisig transaction enqueued will be a transaction for publication of the [`CliArgs` example package](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args). First, generate a publication payload entry function JSON file: ```shellscript filename="Command" aptos move build-publish-payload \ --named-addresses test_account=$multisig_addr \ --json-output-file publication.json \ --assume-yes ```
Output ```shellscript filename="Terminal" { "Result": "Publication payload entry function JSON file saved to publication.json" } ```
Now have Ace propose publication of the package from the multisig account, storing only the payload hash on-chain: ```shellscript filename="Command" aptos multisig create-transaction \ --multisig-address $multisig_addr \ --json-file publication.json \ --store-hash-only \ --private-key-file ace.key \ --assume-yes ```
Output ```shellscript filename="Terminal" { "Result": { "transaction_hash": "0x70c75903f8e1b1c0069f1e84ef9583ad8000f24124b33a746c88d2b031f7fe2c", "gas_used": 510, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 6, "success": true, "timestamp_us": 1685078836492390, "version": 528429447, "vm_status": "Executed successfully" } } ```
Note that the last resolved sequence number is still 0 because no transactions have been resolved: ```shellscript filename="Last resolved sequence number" aptos move view \ --function-id 0x1::multisig_account::last_resolved_sequence_number \ --args \ address:"$multisig_addr" ```
Output ```shellscript filename="Terminal" { "Result": [ "0" ] } ```
However, the next sequence number has been incremented because a transaction has been enqueued: ```shellscript filename="Next sequence number" aptos move view \ --function-id 0x1::multisig_account::next_sequence_number \ --args \ address:"$multisig_addr" ```
Output ```shellscript filename="Terminal" { "Result": [ "2" ] } ```
The multisig transaction enqueued on-chain can now be inspected: ```shellscript filename="Get transaction" aptos move view \ --function-id 0x1::multisig_account::get_transaction \ --args \ address:"$multisig_addr" \ u64:1 ```
Output ```shellscript filename="Terminal" { "Result": [ { "creation_time_secs": "1685078836", "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "payload": { "vec": [] }, "payload_hash": { "vec": [ "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" ] }, "votes": { "data": [ { "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "value": true } ] } } ] } ```
Note from the above result that no payload is stored on-chain, and that Ace implicitly approved the transaction (voted `true`) upon the submission of the proposal. ## Enqueue a governance parameter transaction Now have Bee enqueue a governance parameter setter transaction, storing the entire transaction payload on-chain: ```shellscript filename="Command" aptos multisig create-transaction \ --multisig-address $multisig_addr \ --function-id $multisig_addr::cli_args::set_vals \ --type-args \ 0x1::account::Account \ 0x1::chain_id::ChainId \ --args \ u8:123 \ "bool:[false, true, false, false]" \ 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ --private-key-file bee.key \ --assume-yes ```
Output ```shellscript filename="Terminal" { "Result": { "transaction_hash": "0xd0a348072d5bfc5a2e5d444f92f0ecc10b978dad720b174303bc6d91342f27ec", "gas_used": 511, "gas_unit_price": 100, "sender": "beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", "sequence_number": 0, "success": true, "timestamp_us": 1685078954841650, "version": 528430315, "vm_status": "Executed successfully" } } ```
Note the next sequence number has been incremented again: ```shellscript filename="Next sequence number" aptos move view \ --function-id 0x1::multisig_account::next_sequence_number \ --args \ address:"$multisig_addr" ```
Output ```shellscript filename="Terminal" { "Result": [ "3" ] } ```
Now both the publication and parameter transactions are pending: ```shellscript filename="Get pending transactions" aptos move view \ --function-id 0x1::multisig_account::get_pending_transactions \ --args \ address:"$multisig_addr" ```
Output ```shellscript filename="Terminal" { "Result": [ [ { "creation_time_secs": "1685078836", "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "payload": { "vec": [] }, "payload_hash": { "vec": [ "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" ] }, "votes": { "data": [ { "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "value": true } ] } }, { "creation_time_secs": "1685078954", "creator": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", "payload": { "vec": [ "0x0057478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c508636c695f61726773087365745f76616c7302070000000000000000000000000000000000000000000000000000000000000001076163636f756e74074163636f756e740007000000000000000000000000000000000000000000000000000000000000000108636861696e5f696407436861696e49640003017b0504000100006403020000000000000000000000000000000000000000000000000000000000000ace0000000000000000000000000000000000000000000000000000000000000bee010000000000000000000000000000000000000000000000000000000000000cad00" ] }, "payload_hash": { "vec": [] }, "votes": { "data": [ { "key": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", "value": true } ] } } ] ] } ```
## Execute the publication transaction Since only Ace has voted on the publication transaction (which he implicitly approved upon proposing) the transaction can't be executed yet: ```shellscript filename="Can be executed" aptos move view \ --function-id 0x1::multisig_account::can_be_executed \ --args \ address:"$multisig_addr" \ u64:1 ```
Output ```shellscript filename="Terminal" { "Result": [ false ] } ```
Before Bee votes, however, she verifies that the payload hash stored on-chain matches the publication entry function JSON file: ```shellscript filename="Verifying transaction proposal" aptos multisig verify-proposal \ --multisig-address $multisig_addr \ --json-file publication.json \ --sequence-number 1 ```
Output ```shellscript filename="Terminal" { "Result": { "Status": "Transaction match", "Multisig transaction": { "creation_time_secs": "1685078836", "creator": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "payload": { "vec": [] }, "payload_hash": { "vec": [ "0x62b91159c1428c1ef488c7290771de458464bd665691d9653d195bc28e0d2080" ] }, "votes": { "data": [ { "key": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "value": true } ] } } } } ```
Since Bee has verified that the on-chain payload hash checks out against her locally-compiled package publication JSON file, she votes yes: ```shellscript filename="Approving transaction" aptos multisig approve \ --multisig-address $multisig_addr \ --sequence-number 1 \ --private-key-file bee.key \ --assume-yes ```
Output ```shellscript filename="Terminal" { "Result": { "transaction_hash": "0xa5fb49f1077de6aa6d976e6bcc05e4c50c6cd061f1c87e8f1ea74e7a04a06bd1", "gas_used": 6, "gas_unit_price": 100, "sender": "beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", "sequence_number": 1, "success": true, "timestamp_us": 1685079892130861, "version": 528437204, "vm_status": "Executed successfully" } } ```
Now the transaction can be executed: ```shellscript filename="Can be executed" aptos move view \ --function-id 0x1::multisig_account::can_be_executed \ --args \ address:"$multisig_addr" \ u64:1 ```
Output ```shellscript filename="Terminal" { "Result": [ true ] } ```
Now either Ace or Bee can invoke the publication transaction from the multisig account, passing the full transaction payload since only the hash was stored on-chain: ```shellscript filename="Publication" aptos multisig execute-with-payload \ --multisig-address $multisig_addr \ --json-file publication.json \ --private-key-file bee.key \ --max-gas 10000 \ --assume-yes ```
Output Also pending the resolution of [#8304](https://github.com/aptos-labs/aptos-core/issues/8304), the CLI output for a successful multisig publication transaction execution results in an API error if only the payload hash has been stored on-chain, but the transaction can be manually verified using an explorer.
## Execute the governance parameter transaction Since only Bee has voted on the governance parameter transaction (which she implicitly approved upon proposing), the transaction can't be executed yet: ```shellscript filename="Can be executed" aptos move view \ --function-id 0x1::multisig_account::can_be_executed \ --args \ address:"$multisig_addr" \ u64:2 ```
Output ```shellscript filename="Terminal" { "Result": [ false ] } ```
Before Ace votes, however, he verifies that the payload stored on-chain matches the function arguments he expects: ```shellscript filename="Verifying transaction proposal" aptos multisig verify-proposal \ --multisig-address $multisig_addr \ --function-id $multisig_addr::cli_args::set_vals \ --type-args \ 0x1::account::Account \ 0x1::chain_id::ChainId \ --args \ u8:123 \ "bool:[false, true, false, false]" \ 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ --sequence-number 2 ```
Output ```shellscript filename="Terminal" { "Result": { "Status": "Transaction match", "Multisig transaction": { "creation_time_secs": "1685078954", "creator": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", "payload": { "vec": [ "0x0057478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c508636c695f61726773087365745f76616c7302070000000000000000000000000000000000000000000000000000000000000001076163636f756e74074163636f756e740007000000000000000000000000000000000000000000000000000000000000000108636861696e5f696407436861696e49640003017b0504000100006403020000000000000000000000000000000000000000000000000000000000000ace0000000000000000000000000000000000000000000000000000000000000bee010000000000000000000000000000000000000000000000000000000000000cad00" ] }, "payload_hash": { "vec": [] }, "votes": { "data": [ { "key": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", "value": true } ] } } } } ```
Note that the verification fails if he modifies even a single argument: ```shellscript filename="Failed transaction verification with modified u8" aptos multisig verify-proposal \ --multisig-address $multisig_addr \ --function-id $multisig_addr::cli_args::set_vals \ --type-args \ 0x1::account::Account \ 0x1::chain_id::ChainId \ --args \ u8:200 \ "bool:[false, true, false, false]" \ 'address:[["0xace", "0xbee"], ["0xcad"], []]' \ --sequence-number 2 ```
Output ```shellscript filename="Terminal" { "Error": "Unexpected error: Transaction mismatch: The transaction you provided has a payload hash of 0xe494b0072d6f940317344967cf0e818c80082375833708c773b0275f3ad07e51, but the on-chain transaction proposal you specified has a payload hash of 0x070ed7c3f812f25f585461305d507b96a4e756f784e01c8c59901871267a1580. For more info, see https://aptos.dev/move/move-on-aptos/cli#multisig-governance" } ```
Ace approves the transaction: ```shellscript filename="Approving transaction" aptos multisig approve \ --multisig-address $multisig_addr \ --sequence-number 2 \ --private-key-file ace.key \ --assume-yes ```
Output ```shellscript filename="Terminal" { "Result": { "transaction_hash": "0x233427d95832234fa13dddad5e0b225d40168b4c2c6b84f5255eecc3e68401bf", "gas_used": 6, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 7, "success": true, "timestamp_us": 1685080266378400, "version": 528439883, "vm_status": "Executed successfully" } } ```
Since the payload was stored on-chain, it is not required to execute the pending transaction: ```shellscript filename="Execution" aptos multisig execute \ --multisig-address $multisig_addr \ --private-key-file ace.key \ --max-gas 10000 \ --assume-yes ```
Output ```shellscript filename="Terminal" { "Result": { "transaction_hash": "0xbc99f929708a1058b223aa880d04607a78ebe503367ec4dab23af4a3bdb541b2", "gas_used": 505, "gas_unit_price": 100, "sender": "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "sequence_number": 8, "success": true, "timestamp_us": 1685080344045461, "version": 528440423, "vm_status": "Executed successfully" ```
# Transaction Simulation Sessions > Guide on performing complex local simulations using Transaction Simulation Sessions. import { Aside, FileTree } from '@astrojs/starlight/components'; In the previous tutorials, we demonstrated how you can [simulate a transaction locally](/build/cli/working-with-move-contracts/local-simulation-benchmarking-and-gas-profiling), or [replay a past transaction](/build/cli/replay-past-transactions). However, these methods only work for a single transaction. To simulate multiple transactions in sequence, you'll need to use **Transaction Simulation Sessions** -- an advanced CLI feature that allows you to create a persistent local environment that saves and restores states between runs, with the added benefit of being able to inspect transaction outputs with ease. This makes it possible to test complex workflows β€” such as contract deployment, resource updates, or multi-step interactions. ## Initializing a Session You can initialize a session either from: - **A clean local genesis**, for a completely local, isolated environment, or - **A forked network state** (Devnet, Testnet, or Mainnet), allowing you to test your changes against real-world data. Regardless of which mode you choose, the interface, commands, and how session data is saved are exactly the same. Just specify your preferred mode when you initialize the session. ### From a Forked Network State With network forking, you can test your Move code using **live network data** β€” balances, resources, and published modules. To start with a forked network state, run: ```shellscript filename="Terminal" aptos move sim init --path --network --api-key ``` Here `` can be one of the following: `devnet`, `testnet`, `mainnet`, or a custom fullnode URL. The session will store all its data in the specified `` directory. ### From a Clean Local Genesis For day-to-day development, sometimes you just need a clean slate β€” no real network data needed. This is perfect for ad-hoc simulation,synthetic testing, or continuous integration. To start with a clean local genesis, run: ```shellscript filename="Terminal" aptos move sim init --path ``` Everything else works the same β€” except that the simulation won't touch the network at all, as all data is local. ## Running simulations Once initialized, you can run transactions against your session using the standard `aptos move` commands, adding the `--session` argument to indicate which local session to use. Supported commands include: - `run` - `run-script` - `publish` - `view` - `create-object-and-publish-package` - `upgrade-object-package` - `deploy-object` - `upgrade-object` - `create-resource-account-and-publish-package` There are also additional utilities under `aptos move sim` for managing and inspecting sessions. - `fund`: fund an account with a given amount of APT - `view-resource`: view a Move resource - `view-resource-group`: view a resource group For more detailed help, run `aptos move sim --help`. ### Example Workflow ```shellscript filename="Terminal" # 1. Fund your default account with 1 APT (for demo) aptos move sim fund --session sess --account default --amount 100000000 # 2. Execute a transfer transaction aptos move run --session sess \ --function-id 0x1::aptos_account::transfer \ --args address:default u64:100 # 3. Query your account's sequence number aptos move view --session sess \ --function-id 0x1::account::get_sequence_number \ --args address:default # 4. View your on-chain Account resource aptos move sim view-resource --session sess \ --account default \ --resource 0x1::account::Account # 5. View a resource group (e.g. your fungible store) aptos move sim view-resource-group --session sess \ --account default \ --resource-group 0x1::object::ObjectGroup \ --derived-object-address 0xA ``` Again, all state changes are local and are stored under your session directory. ## Inspecting Session Data Each Transaction Simulation Session organizes its data in a structured directory tree, making it easy to inspect every step of your simulation. Here's how the layout looks like for the sample session: - sess/ - \[0] fund (fungible)/ - summary.json - \[1] execute 0x1::aptos\_account::transfer/ - events.json - summary.json - write\_set.json - \[2] view 0x1::account::get\_sequence\_number/ - summary.json - \[3] view resource 0xdbcb...::0x1::account::Account/ - summary.json - \[4] view resource group 0x20ce...::0x1::object::ObjectGroup/ - summary.json - config.json - delta.json ### Sample Outputs `[1] execute 0x1::aptos_account::transfer/summary.json` ```json { "execute_transaction": { "status": { "Keep": "Success" }, "gas_used": 498, "fee_statement": { "total_charge_gas_units": 498, "execution_gas_units": 4, "io_gas_units": 3, "storage_fee_octas": 49160, "storage_fee_refund_octas": 0 } } } ``` `[1] execute 0x1::aptos_account::transfer/events.json` ```json [ { "V2": { "type_tag": "0x1::fungible_asset::Withdraw", "event_data": { "store": "20ce9f242351eae77cae7eb27e7e55f798e6c3b3528fcbb325bccea103e53ff9", "amount": 100 } } }, { "V2": { "type_tag": "0x1::fungible_asset::Deposit", "event_data": { "store": "20ce9f242351eae77cae7eb27e7e55f798e6c3b3528fcbb325bccea103e53ff9", "amount": 100 } } }, { "V2": { "type_tag": "0x1::transaction_fee::FeeStatement", "event_data": { "total_charge_gas_units": 498, "execution_gas_units": 4, "io_gas_units": 3, "storage_fee_octas": 49160, "storage_fee_refund_octas": 0 } } } ] ``` ## Future Plans We are working on adding more features to Transaction Simulation Sessions, such as: - Gas Profiler integration - Better performance - Snapshot and rollback support # create-aptos-dapp - A templating tool for Aptos dapps > Build template projects for dapp developers to easily create front-end and smart contracts on the Aptos network import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; `create-aptos-dapp` builds a template project for dapp developers to easily create a front-end and a smart contract on the Aptos network. ## Why use create-aptos-dapp? - **Templated Setup**: `create-aptos-dapp` generates predefined end-to-end dapp templates and configuration files for you. It saves manual setup of the project structure, which can be time-consuming and error-prone. - **Contract Directory:** `create-aptos-dapp` generates a `contract` directory that includes the basic structure for Move smart contract modules. - **Best Practices**: `create-aptos-dapp` incorporates best practices and structure recommendations to develop for the Aptos network. - **Built-in Move Commands**: `create-aptos-dapp` includes built-in commands for common tasks, such as initializing the Move compiler, compiling, and publishing smart contracts on-chain. ## Prerequisites - [node and npm](https://nodejs.org/en) (npm β‰₯ 5.2.0) - [Python 3.6+](https://www.python.org/) ## Using `create-aptos-dapp` 1. Navigate to the directory you want to work in. ```shellscript filename="Terminal" cd your/workspace ``` 2. Install create-aptos-dapp. ```shellscript filename="Terminal" npx create-aptos-dapp@latest ``` ```shellscript filename="Terminal" pnpx create-aptos-dapp@latest ``` ```shellscript filename="Terminal" yarn create aptos-dapp ``` ```shellscript filename="Terminal" pnpm create create-aptos-dapp@latest ``` 3. Follow the CLI prompts. After installing, you will need to answer several questions about your project including: 1. The project's name 2. Which template to use ([see below](#current-templates)) 3. Whether to use Mainnet or Devnet for testing ![cad](~/images/cad-video.gif) ## Templates `create-aptos-dapp` provides you with premade end-to-end dapp templates, i.e. a ready dapp with configurations and a beautiful UI to get you started with creating a dapp on Aptos. The goals of the templates are to: 1. Familiarize users with different Aptos Standards by having an end-to-end dapp template examples. 2. Educate users on how to build a dapp on Aptos from the front-end layer to the smart contract layer and how everything in-between. 3. Provide users with pre-made templates to quickly deploy simple dapps ### Current Templates All current templates are available on [Aptos Learn](https://learn.aptoslabs.com/en/dapp-templates). Read more about specific templates below: - [Boilerplate Template](https://github.com/aptos-labs/create-aptos-dapp/tree/main/templates/boilerplate-template) - [NFT minting dapp Template](https://github.com/aptos-labs/create-aptos-dapp/tree/main/templates/nft-minting-dapp-template) - [Token minting dapp Template](https://github.com/aptos-labs/create-aptos-dapp/tree/main/templates/token-minting-dapp-template) - [Token staking dapp Template](https://github.com/aptos-labs/create-aptos-dapp/tree/main/templates/token-staking-dapp-template) - [Custom indexer template](https://github.com/aptos-labs/create-aptos-dapp/tree/main/templates/custom-indexer-template) ## Tools `create-aptos-dapp` utilizes - React framework - Vite development tool - shadcn/ui + tailwind for styling - Aptos TS SDK - Aptos Wallet Adapter - Node based Move commands # Create Aptos Dapp FAQ > Frequently asked questions about using create-aptos-dapp tool and its templates ## Why do we use `import.meta.env`? The template is built in a way that there are pages meant to be accessed only on DEV mode and pages that are meant to be accessed also on PROD mode. For example, β€œcreate collection” and β€œmy collections” pages are only meant for local development and can only be accessed on DEV mode while the β€œpublic mint” page can be accessed on PROD mode. `import.meta.env` is the `Vite` way to know what is the environment the dapp is running on - DEV or PROD. ## I tried to publish my dapp to a live server but getting `404 error` Might need to update the root route, if you deployed your site to `user-name.github.io/my-repo` then root route should be updated to `my-repo` ## What is Tailwind CSS? Tailwind is a utility-first CSS framework that scans your components for class names and generates a static CSS file containing the corresponding styles at build-time. This framework makes it easy to quickly author styles that are co-located with your component markup without incurring any runtime performance costs. It also helps you to maintain a consistent theme throughout your app that is responsive to light and dark mode. To learn more about Tailwind CSS, please refer to their official [documentation](https://tailwindcss.com/docs/utility-first). ## What is `shadcn/ui`? Shadcn is a collection of accessible components that you can copy and paste into your app through their CLI tool. Since the source files live in your app's codebase, you can customize them as much as you need to. These components are built on top of [Radix UI Primitives](https://www.radix-ui.com/primitives) and are styled with [Tailwind CSS](https://tailwindcss.com/). To learn more about `shadcn/ui`, please refer to their official [documentation](https://ui.shadcn.com/docs). ## How to modify the theme? The theme for this template is split across `tailwind.config.js` and `frontend/index.css`. The Tailwind config declares all of the theme colors, text styles, animation keyframes, border radii, etc. The root CSS file (`index.css`) declares the actual color values for light and dark mode as CSS custom properties (CSS variables), the base radius value, and applies any global CSS required. For example, if you want to make all of the buttons and cards more round in your app, you can increase the base radius value (`--radius`) in `index.css`. If you want to add a new text style, you can define it in the `addTextStyles` function towards the end of `tailwind.config.js`. And if you want to modify the primary color of the app, you can update the HSL color values defined in `index.css`. ## How to add components? Additional components can be added through the `shadcn-ui` CLI. For example, if you wish to add a `Switch` component, you can run the following command: ```shellscript filename="Terminal" npx shadcn-ui@latest add switch ``` This command will create a `switch.tsx` file in your `frontend/components/ui` directory that contains a styled switch component. For a full list of available shadcn components, please refer to the [shadcn component documentation](https://ui.shadcn.com/docs/components). If you need to add a component that's not included in the `shadcn/ui` collection, you're welcome to add your own components under `frontend/components` or within the `frontend/pages` directory if they're specific to the page that you're working on. ## How to add colors? If you're creating your own custom components or adding to the UI in some way, you may need to add some new colors. To add a new color, you must first define the light and dark HSL color values in `frontend/index.css` and then add the new theme color token to the theme defined in `tailwind.config.js`. For more detailed instructions, please refer to the [shadcn documentation on theming](https://ui.shadcn.com/docs/theming). ## How to add dark mode? In an effort to maintain simplicity in the dapp template, only light mode is set up. However, color values are defined for both light and dark mode in the theme. If you wish to add dark mode to your app, you simply have to add the shadcn `ThemeProvider` and `ModeToggle` to your app. Once added, the UI will be fully responsive to both light and dark mode. For detailed instructions on how to achieve this, please refer to the [shadcn dark mode documentation](https://ui.shadcn.com/docs/dark-mode/vite). # External Resources > List of external resources to learn more about the Aptos Blockchain import {Aside} from '@astrojs/starlight/components'; ## Move Learning Resources ### Tutorials - [Aptos Learn](https://learn.aptoslabs.com) - Learn how to build on the Aptos Blockchain with various tutorials. ### Videos - [Aptos Dev YouTube Channel](https://www.youtube.com/@aptosdev) β€” Learn about how to build on the Aptos Blockchain with various videos. ### Examples - [Move By Examples](https://github.com/aptos-labs/move-by-examples) β€” A collection of Move examples. - [Daily Move](https://github.com/aptos-labs/daily-move) β€” A collection of short Move examples illustrating Aptos features. - [Aptos-Core Move Examples](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples) β€” A collection of Move examples actively tested in CI in the Aptos Core repository. ## External Tools ### Developer Tools - [Surf](https://github.com/ThalaLabs/surf) β€” Generated TypeScript interfaces and React hooks for Aptos smart contracts. # Get Started Building on Aptos > Learn how to build on Aptos with smart contracts, indexer queries, SDKs, APIs, and comprehensive developer resources import { CardGrid, LinkCard } from '@astrojs/starlight/components'; import { GraphQLEditor } from '~/components/react/GraphQLEditor'; ## What would you like to learn? ## What developer tools should I use? Here's an interactive example of our [Indexer](/build/indexer) and how you can query for the Current Fungible Asset Balances of an account. More usage examples can be found in [example queries](/build/indexer/indexer-api/fungible-asset-balances). ## Coming from another ecosystem? Quickly ramp up on some of the differences and similarities between Aptos and other ecosystems. ## Do you have any examples? We've got all kinds of examples and guides, catered to what you're looking for. ### End-to-end guides ### Smart Contract guides See the [Smart Contract](/build/smart-contracts) section for more info ### Interactive guides ## How do I setup a full node or validator? # Developer Environment Setup > Set up your development environment for frontend, smart contracts, and full-stack Aptos applications with step-by-step guides import { CardGrid, LinkCard, Steps, TabItem, Tabs } from '@astrojs/starlight/components'; Here is an easy way to setup your environment depending on the type of development. {/* Frontend */} 1. Initialize Frontend Project Here are some examples of popular choices: ```shellscript filename="Terminal" pnpx create-next-app@latest ``` ```shellscript filename="Terminal" pnpx create vite my-aptos-app --template react-ts ``` 2. Install @aptos-labs/ts-sdk ```shellscript npm2yarn npm i @aptos-labs/ts-sdk ``` 3. Setup TS SDK 4. Build your app! The developer setup for using Aptos in your frontend is now complete. Checkout our other tools that streamline the development process {/* Smart Contract */} 1. Install CLI 2. Setup Editor or IDE Add the following extensions to your editor of choice to make Move Development easier 3. Create Smart Contract Navigate to your application folder and initialize a new smart contract by doing: ```shellscript filename="Terminal" aptos move init --name my_todo_list ``` 4. Build, Compile, and Deploy Smart Contract! The developer setup for using Aptos for smart contracts is now complete. For more info see the link to the Dapp tutorial below {/* create-aptos-dapp */} 1. Install create-aptos-dapp Run the below command to install a dApp from a template in seconds: { /* npx */ } ```shellscript filename="Terminal" npx create-aptos-dapp@latest ``` { /* pnpx */ } ```shellscript filename="Terminal" pnpx create-aptos-dapp@latest ``` 2. Follow the prompts Follow the CLI prompts to select a name, [template](/build/create-aptos-dapp#templates), and network for your new dApp. ![cad](~/images/cad-video.gif) 3. Start building and customizing your new dApp! Navigate to your new project and open in your favorite IDE to continue building! Follow the generated `README.md` file for next steps. 4. Continue reading # Ethereum to Aptos Migration Guide > Comprehensive comparison and migration guide for Ethereum developers transitioning to Aptos blockchain development import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'; Aptos is built to allow you to quickly prototype and scale secure production applications. It combines a fast, cost-efficient, and stable blockchain layer with Move's compile-time safety that catches exploits before deployment, comprehensive tooling for rapid development, and a strong ecosystem of exchanges and bridges for seamless integration. ### High Level Overview | Feature | Ethereum | Aptos | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Account Addresses** | 160-bit | 256-bit | | **Storage Mindset** | Contract-based storage | Account centric mindset for code and data | | **Caller ID** | `msg.sender` | `&signer` reference | | **Smart Contracts** | Solidity, EVM | Move, MoveVM | | **Benefits** | Mature, wide adoption | Scalability, low latency, predictable fees | | **Transaction Fees** | Variable, can be high | Lower and more predictable | | **Sponsored Transactions** | Requires third-party services or EIP-7702 wallet support (2025+) | [Natively supported](/build/guides/sponsored-transactions) via fee payer field. [Geomi Gas Stations](https://geomi.dev/docs/gas-stations) provides production infrastructure | | **Account Structure** | Balance in a single field, uses nonce | Modules and resources, uses sequence number | | **Data Storage** | Patricia Merkle Trees | Global storage with resources and modules | | **Upgradeability** | Proxy patterns | Direct module upgrades | | **Safety & Security** | Vulnerable to attacks like reentrancy | Mitigates common vulnerabilities | | **Dispatch Type** | Dynamic dispatch | Static dispatch | | **Frontend SDK** | [Ethers.js library](https://docs.ethers.org/v6/) | [Aptos Typescript SDK](/build/sdks/ts-sdk) | | **NFT Standards** | [ERC-721](https://docs.openzeppelin.com/contracts/4.x/erc721), [ERC-1155](https://docs.openzeppelin.com/contracts/4.x/erc1155) | [Digital Asset](/build/smart-contracts/digital-asset) | | **FT Standard** | [ERC-20](https://docs.openzeppelin.com/contracts/4.x/erc20), factory pattern | See [Fungible Asset](/build/smart-contracts/fungible-asset), copy paste in your module: `use aptos_framework::fungible_asset...` | | **Example Code** | [ERC-20](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20) (new contract per deploy) | [Fungible Asset](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/fungible_asset.move) (single reusable module) |
Legacy [`Coin`](/build/smart-contracts/aptos-coin) documentation still covers the original standard; most new deployments should prefer the Fungible Asset module referenced above.
### Comparing Token Standards in Detail | | Solidity | Move (Aptos) | | ---------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | **Token Structure** | Each token is its own contract. | Every token is a typed `FungibleAsset` instantiation that reuses the same published module. | | **Token Standard** | Must conform to standards like ERC-20; implementations can vary per deploy. | Uniform interface and implementation enforced by the shared module; new tokens simply register a new type rather than redeploying code. | | **Balance Storage** | Balances stored in contract using a mapping structure. | **Resource-Oriented Balance**: balances live in an extensible object owned by the user's account. | | **Transfer Mechanism** | Tokens can be transferred without receiver's explicit permission. | Transfers can skip receiver permission, but only when the FA explicitly enables primary-store auto creation (visible in the token's creation code). |
### Comparing EVM and Move VM in Detail - **EVM**: Known for its flexibility and dynamic dispatch, which allows a wide range of smart contract behaviors. This flexibility, however, can lead to complexities in parallel execution and network operations. - **Move VM**: Focuses on safety and efficiency with a more integrated approach between the VM and the programming language. Its data storage model allows for better parallelization, and its static dispatch method enhances security and predictability.
| | EVM (Ethereum Virtual Machine) | Move VM (Move Virtual Machine) | | ------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | **Data Storage** | Data is stored in the smart contract's storage space. | Data is stored across smart contracts, user accounts, and objects. | | **Parallelization** | Parallel execution is limited due to shared storage space. | More parallel execution enabled due to flexible split storage design. | | **VM and Language Integration** | Separate layers for EVM and smart contract languages (e.g., Solidity). | Seamless integration between VM layer and Move language, with native functions written in Rust executable in Move. | | **Critical Network Operations** | Implementation of network operations can be complex and less direct. | Critical operations like validator set management natively implemented in Move, allowing for direct execution. | | **Function Calling** | Dynamic dispatch allows for arbitrary smart contract calls. | Static dispatch aligns with a focus on security and predictable behavior. | | **Type Safety** | Contract types provide a level of type safety. | Module structs and generics in Move offer robust type safety. | | **Transaction Safety** | Uses nonces for transaction ordering and safety. | Uses sequence numbers for transaction ordering and safety. | | **Authenticated Storage** | Yes, with smart contract storage. | Yes, leveraging Move’s resource model. | | **Object Accessibility** | Objects are not globally accessible; bound to smart contract scope. | Guaranteed global accessibility of objects. | ## Migration Tips for Ethereum Developers Ethereum stores all data in contract storage using mappings. Aptos uses an account-centric model where each account stores their own resources. Instead of a contract maintaining a `mapping(address => T)`, each user stores their own `T` resource at their address. ```move module my_hackathon_account::prototype { use std::string; use std::signer; // Unlike Solidity's mapping(address => string), each account stores their own resource struct MessageHolder has key, store, drop { message: string::String, } entry fun set_message(account: &signer, message: string::String) acquires MessageHolder { let addr = signer::address_of(account); // Check if resource exists at this account (like checking mapping[addr]) if (exists(addr)) { move_from(addr); // Remove old resource }; // Store resource at the user's address (in their account, not in contract storage!) move_to(account, MessageHolder { message }); } #[view] public fun get_message(addr: address): string::String acquires MessageHolder { assert!(exists(addr), 0); // Read message stored at the user's address borrow_global(addr).message } } ``` By default, modules deploy to your account address. For production apps, consider deploying to [Objects](/build/smart-contracts/objects), which creates a unique address per deployment and enables transferable code ownership. See [Using Objects](/build/smart-contracts/object/using-objects) for implementation details. Learn more: [`global storage operators`](/build/smart-contracts/book/global-storage-operators), [`structs and resources`](/build/smart-contracts/book/structs-and-resources) On Ethereum, `msg.sender` is set by the EVM, but many bugs come from using `tx.origin` for auth or trusting user-supplied addresses. Aptos' `&signer` goes further: it's an unforgeable capability created only by the VM for actual transaction signers, so any function that requires `&signer` can't be called with a spoofed identity. In dapps, wallets plus the [Aptos TypeScript SDK](/build/sdks/ts-sdk) and [wallet adapter](/build/sdks/wallet-adapter/wallets) bridge this signer identity from frontend to on-chain. ```move module my_hackathon_account::prototype { use std::string; use std::signer; struct MessageHolder has key, store, drop { message: string::String, } // Only the account owner can provide their &signer (unforgeable authentication) entry fun set_message(account: &signer, message: string::String) acquires MessageHolder { // Extract address from authenticated signer (no spoofing possible!) let addr = signer::address_of(account); if (exists(addr)) { move_from(addr); }; // account is guaranteed to be authentic move_to(account, MessageHolder { message }); } #[view] public fun get_message(addr: address): string::String acquires MessageHolder { assert!(exists(addr), 0); // Read message stored at the user's address borrow_global(addr).message } } ``` Create and fund an account using the [Aptos CLI](/build/cli): ```bash aptos init ``` Learn more: [`signer`](/build/smart-contracts/book/signer) type, [`functions`](/build/smart-contracts/book/functions) Move has four abilities: `copy`, `drop`, `store`, and `key`, which control how values can be used. In this example, MessageHolder deliberately omits `copy` so messages stored as resources can't be duplicated; you generally avoid `copy` on any type that represents on-chain state or assets. `key` + `store` allow it to live in global storage at an address, and `drop` lets you destroy the old resource safely when overwriting it. ```move module my_hackathon_account::prototype { use std::string; use std::signer; // Resources: structs with 'key' ability that live in global storage // key = can be stored at account addresses (makes it a "resource") // store = can be stored inside other structs // drop = can be destroyed/discarded implicitly struct MessageHolder has key, store, drop { message: string::String, } entry fun set_message(account: &signer, message: string::String) acquires MessageHolder { let addr = signer::address_of(account); // The 'drop' ability allows implicit destruction if (exists(addr)) { move_from(addr); // Old resource is destroyed (requires 'drop') }; move_to(account, MessageHolder { message }); } #[view] public fun get_message(addr: address): string::String acquires MessageHolder { assert!(exists(addr), 0); // Read message stored at the user's address borrow_global(addr).message } } ``` Learn more: [`abilities`](/build/smart-contracts/book/abilities), [`structs and resources`](/build/smart-contracts/book/structs-and-resources) Aptos packages support two upgrade policies: `compatible` (default; only backward-compatible changes allowed) and `immutable` (no upgrades allowed). By default, packages published with the [Aptos CLI](/build/cli) using `aptos move publish` use the **compatible** policy, which lets you push new versions as long as you don't break struct layouts or public function signatures. To prevent all future upgrades, set the immutable policy: ```bash aptos move publish --upgrade-policy immutable ``` Or configure in your `Move.toml`: ```toml [package] name = "MyPackage" version = "1.0.0" upgrade_policy = "immutable" ``` See [Package Upgrades](/build/smart-contracts/book/package-upgrades) for the exact compatibility rules. On Ethereum you "deploy a contract to a new address." On Aptos you **publish a package** of Move modules to an account (or object) address using the [Aptos CLI](/build/cli). Publish your package: ```bash aptos move publish ``` Call an `entry` function after deployment: ```bash aptos move run --function-id 'your_address::module_name::function_name' ``` Both account-based publishing and object-based deployment (via `aptos move deploy-object`) respect the package's upgrade policy. See [Your First Move Module](/build/guides/first-move-module) for a complete walkthrough and [Objects](/build/smart-contracts/objects) for object-centric patterns. # Solana to Aptos Migration Guide > Detailed comparison and transition guide for Solana developers moving to Aptos blockchain development To learn more about the differences and similarities see [Aptos Learn](https://learn.aptoslabs.com/en/tutorials/solana-to-aptos-guide/cheat-sheet?workshop=solana-to-aptos) | | Solana | Aptos | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | | **Smart Contracts** | Rust, SVM | Move, MoveVM | | **Transaction Fees** | Low | Low | | **Parallelization** | Pessimistic parallelism, need to declare all write accounts | Optimistic parallelism, chain infers write accounts for you | | **Contract Account Support** | PDA Account | [Object](/build/smart-contracts/objects) or [resource account](/build/smart-contracts/resource-accounts)(encourage to use object instead) | | **Data Storage** | Data stored in account owned by programs | Data stored as resource under user account or object | | **Storage Level** | Program level | Global when stored under object | | **Storage Mindset** | User data stored distributedly under account | User data stored distributedly under object | | **Example Code** | [Todo list contract on Solana](https://github.com/aptos-labs/move-by-examples/tree/main/advanced-todo-list/solana) | [Todo list contract on Aptos](https://github.com/aptos-labs/move-by-examples/tree/main/advanced-todo-list/aptos) | | **Caller ID** | `signer` | `signer` | | **Upgradability** | Program is upgradable | Module is upgradable | | **Dispatch Type** | Static dispatch | Static dispatch | | **FT Standards** | Token program | [Coin](/build/smart-contracts/aptos-coin) (legacy) and [Fungible Asset Standard](/build/smart-contracts/fungible-asset) | | **NFT Standards** | Token program | [Digital Asset Standard](/build/smart-contracts/digital-asset) | | **Blockchain Interaction** | Solana web3.js library | [Aptos Typescript SDK](/build/sdks/ts-sdk) | # Aptos Keyless > Integrate Keyless accounts for seamless user onboarding using social logins instead of traditional private key management. ## Integrate with Aptos Keyless accounts - [Introduction](/build/guides/aptos-keyless/introduction) - [OIDC Support and Configuration](/build/guides/aptos-keyless/oidc-support) - [Integration Guide](/build/guides/aptos-keyless/integration-guide) - [Simple Example](/build/guides/aptos-keyless/simple-example) - [How Aptos Keyless works](/build/guides/aptos-keyless/how-keyless-works) - [Terminology and FAQ](/build/guides/aptos-keyless/other) ## Using an IAM Provider? Integrate with Aptos Federated Keyless - [Federated Keyless](/build/guides/aptos-keyless/federated-keyless) # Federated Keyless > Extended Keyless support for additional OIDC providers through IAM systems like Auth0 and AWS Cognito. ## Federated Keyless [AIP-96](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-96.md): Federated Keyless is an extension of Aptos Keyless to support more OpenID Connect (OIDC) providers, beyond the ones that are allow-listed in `0x1::jwks` via JWK consensus, while maintaining its decentralization. Federated keyless adds support for authenticating users via identity & access management (IAM) providers (e.g. Auth0, AWS Cognito) as long as your project uses a supported IAM provider for user authentication. To elaborate further, Federated Keyless enables: 1. Extension of authentication methods a. All authentication methods supported by the IAM are available to the dApp including email/SMS OTP and their marketplace of social login integrations like Discord, Naver, X and more. Auth0 marketplace linked [here](https://marketplace.auth0.com/) as an example. 2. Compatibility with existing account systems a. Since IAMs also support custom authentication, it allows an application to bring its own username/password (Cognito [docs](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html), Auth0 [docs](https://auth0.com/blog/Custom-Authentication-With-Auth0/)). An application can start using an existing account system already set up with an IAM or they can migrate their existing account system to an IAM to generate Keyless-compatible JWTs. - [Federated Keyless Key Considerations](/build/guides/aptos-keyless/federated-keyless/key-considerations) - [Federated Keyless Integration Guide](/build/guides/aptos-keyless/federated-keyless/integration-guide) - [Federated Keyless FAQs](/build/guides/aptos-keyless/federated-keyless/other) # Federated Keyless Integration Guide > Step-by-step guide for integrating Federated Keyless with IAM providers like Auth0 and AWS Cognito. import { Aside, Steps } from '@astrojs/starlight/components'; 1. Step 1. Setup your IAM provider Set up your project with your IAM to match the account structure you are looking for. - [Getting Started with AWS Cognito](https://aws.amazon.com/cognito/getting-started/) - [Getting Started with Auth0](https://auth0.com/docs/get-started) 2. Step 2. Register the JSON Web Key Set (JWKS) on-chain Federated Keyless accounts require the JWKS to be registered on-chain. To register the JWKS - call the `0x1::jwks::update_federated_jwk_set` entry function with an Aptos account that will store the JWKs that will be used to validate transactions signed by federated keyless accounts. The JWK set can be found as follows - AWS Cognito - `https://cognito-idp..amazonaws.com//.well-known/jwks.json` Auth0 - `https:///.well-known/jwks.json` The typescript SDK contains functionality to simplify the process given the issuer for your IAM provider setup (the `iss` claim value on your user’s JWT tokens) and an account to use to make the update. ```tsx import {Aptos} from '@aptos-labs/ts-sdk'; // Requires version v1.29.1 or later const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Configure your network here const alice = // Derive your Aptos account here const jwkTxn = await aptos.updateFederatedKeylessJwkSetTransaction({ sender: alice, iss }); await aptos.signAndSubmitTransaction({ signer: alice, transaction: jwkTxn }); ``` You can use the interactive example provided by the SDK to easily register the JWKS for your IAM provider in devnet or testnet. This will setup the JWK owner account with a Google Keyless account. ```shellscript git clone https://github.com/aptos-labs/aptos-ts-sdk cd aptos-ts-sdk pnpm install && pnpm build cd examples/typescript pnpm install pnpm jwk_update ``` To setup the JWK owner account in mainnet, you will need create an account and use it to register the JWKS. Save the address of the account you used to register the JWKS as you will need it for the next step. To learn more about the `0x1::jwks::update_federated_jwk_set` entry function, see the [reference documentation](/move-reference/mainnet/aptos-framework/jwks#jwks_update_federated_jwk_set). 3. Step 3. Follow the Aptos Keyless integration guide Now that you have registered the JWKS, you can follow the Aptos Keyless integration guide starting from step 2. Be sure to set the `jwkAddress` to the address of the account you used to register the JWKS when deriving the `KeylessAccount`. [Aptos Keyless Integration Guide - Step 2](/build/guides/aptos-keyless/integration-guide#step-2-install-the-aptos-typescript-sdk) # Federated Keyless Key Considerations > Important considerations and supported IAM providers for implementing Federated Keyless accounts in production. ## Federated Keyless Key Considerations **Supported IAMs** Currently, the supported IAMs are Amazon Cognito and Auth0 across devnet, testnet, and mainnet. See a table of the full set of supported IAM providers [here](/build/guides/aptos-keyless/oidc-support). **Federated Keyless flow** The flow for Federated Keyless transactions is the same as described [here](/build/guides/aptos-keyless/how-keyless-works). However, the difference is that in Federated Keyless, instead of the OIDC provider (e.g., Google, Apple) acting as the issuer of the JWT, the IAM provider (e.g., Auth0, Cognito) acts as the issuer. The user authenticates with the application, the IAM receives the user’s credentials, and then the IAM issues the Keyless-compatible JWT. **Available authentication methods** All authentication methods that are supported by the IAM providers are available for use - this includes SMS OTP, email link, and the traditional username + password. **Configuration limitations** A Keyless account address varies according to the `aud` (AKA application ID or client ID), and `iss` (AKA issuer). The setup of your user data within the IAM must reflect the interoperability you seek to provide to your users. JWT tokens issued for a user in the same user pool but for different applications will result in a different address derivation if the `aud` value is different. **JSON Web Key Set management** If you or the IAM platform rotates the key pairs used to signed the JWT tokens, the JWK set must be updated on chain using the same account used to instantiate your app's Federated Keyless accounts. As such it is vital to - 1. Maintain access to your JWKS owner account 2. Update the JWK set on chain whenever a key rotation occurs When a keypair is rotated existing keyless account instantiations will continue to work so long as the old JWK has not been removed. Any new JWTs issued by the new keypair will not be accepted until the JWK set on chain is updated to contain its public key. **The trust and security model for Federated Keyless** Compared to the existing Keyless implementation, dApp developers utilizing Federated Keyless alongside certain authentication methods like email/SMS, OTP and email/password may have more access to user credentials when leveraging IAM providers than with the existing direct OIDC provider integrations. We recommend each dApp developer perform their own research and consult with their legal counsel before integrating an authentication method. Developers should also understand to what extent they may have access to user credentials and what controls they have in place. # Federated Keyless FAQ > Frequently asked questions about Federated Keyless implementation, IAM provider changes, and account migration. ## Federated Keyless FAQs **What if I stop using my IAM for my application? What if I switch IAM providers?** - An account address depends on values of several variables that are specific to an IAM service, including `aud` (client ID) and `iss` (issuer). If these values are changed, then a different address will be derived. - If you want to switch IAM providers, you will need to develop an account migration flow, resulting in a key rotation from the account derived from the prior IAM provider to the account derived from the new IAM provider. - We recommend allowing your users to add a secondary authentication method to their accounts (e.g., back-up private key) so that they can maintain access should the authentication path into their account via Federated Keyless be disrupted via a service provider change. In order to implement this, you need to do a key rotation to a multikey account. For relevant documentation see [key rotation](/build/guides/key-rotation) and [multikey SDK](https://aptos-labs.github.io/aptos-ts-sdk/@aptos-labs/ts-sdk-1.35.0/classes/MultiKeyAccount.html). **Does using an IAM cost money?** - Yes, IAMs usually cost money, but they can help provide useful functionality within your application such as role-based access control (authorization), user management, user authentication, security + compliance, and analytics + monitoring. **In the case the dApp or IAM provider goes offline, how do I make sure my users can continue accessing their accounts?** - We recommend allowing your users to add a secondary authentication method to their accounts (e.g., back-up private key) so that they can maintain access should the authentication path into their account via Federated Keyless is disrupted via service provider change or other outage. **I use an open source IAM like Keycloak. Can I use Federated Keyless?** - Not today. Due to the trust placed in the IAM to have sufficient uptime and security standards, we have limited the accepted IAM set to the currently supported issuers. If you believe your provider should be included for consideration, please consider raising an AIP or contact us in the Keyless developers [telegram](https://t.me/+h5CN-W35yUFiYzkx). # Federated Keyless Simple Example > Working example of Federated Keyless implementation using Auth0 as the IAM provider with complete code samples. The Federated Keyless Example shows how to set up a Federated Keyless account using Auth0 as the IAM provider. Explore the code in the [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/tree/main/examples/federated-keyless-example/). Follow the instructions in the repository's `README.md` to set up and run the example locally. # How Keyless Works > Technical deep dive into how Aptos Keyless derives and accesses blockchain accounts using OIDC provider authentication. import { YouTube } from 'astro-embed'; Aptos Keyless enables a dApp to **derive** and **access** a blockchain account for a user who successfully signed in to the dApp via an OIDC provider (e.g., Google). Importantly, this blockchain account is **scoped to the dApp**. This means other dApps, who can similarly sign-in the same user, via the same OIDC provider, are not able to access this account and instead get their own account. _But how does this work?_ This article will explain the full keyless flow depicted below, from the user first signing into a dapp, to obtaining her zero-knowledge proof and to, finally, transacting on-chain. ![Keyless overview](~/images/aptos-keyless/keyless-overview.png "Keyless overview") ## Overview At a very high level, a successful sign-in into the dApp via the OIDC provider will result in the dApp receiving a **JSON Web Token (JWT)** signed by the OIDC provider. The JWT will contain, among other things, three important pieces of information: 1. The user’s identity (contained in the JWT’s `sub` field) 2. The dApp’s identity (contained in the JWT’s `aud` field) 3. Application-specific data; specifically, an **ephemeral public key (EPK)** (contained in the JWT’s `nonce` field), whose associated **ephemeral secret key (ESK)** only the user knows. Now, assume that the user’s blockchain account address is (more or less) a hash of the user’s identity in `sub` and the dApp’s identity in `aud` from above. Then, the **key observation** is that the signed JWT effectively acts as a **digital certificate**, **temporarily** binding this blockchain address to the EPK, and allowing the EPK to sign TXNs for it. In other words, it securely delegates TXN signing rights for this blockchain account to the EPK (Note: The EPK contains an expiration date and is thus short-lived). Importantly, if the user loses their ESK, the user can obtain a new signed JWT over a new EPK via the application by simply signing in again via the OIDC provider (Or, in some cases, by requesting a new signed JWT using an OAuth refresh token). With this system, the **challenge** is maintaining privacy, since revealing the JWT on-chain would leak the user’s identity. Furthermore, revealing the EPK to the OIDC provider would allow it to track the user’s TXNs on-chain. We explain below how Keyless accounts work and how they address these challenges. ## Flow: Deriving a keyless account for a user in a dApp First, let us look at how a dApp can sign-in a user via (say) Google, derive that user’s keyless blockchain address and, for example, send that user an asset. ![Keyless account diagram](~/images/aptos-keyless/keyless-account.png "Keyless account diagram") **Step 1**: The user generates an ephemeral key pair: an EPK with an expiration date, and its associated ESK. The dApp keeps the EPK and safely stores the ESK on the user-side (e.g., in the browser’s local storage, or in a trusted enclave if the ESK is a WebAuthn passkey). **Step 2**: The dApp commits to the EPK as $H(\mathsf{epk}, \rho)$, where $\rho$ is a blinding factor. When the user clicks on the β€œSign in with Google” button, the dApp redirects the user to Google’s sign in page and, importantly, sets the `nonce` parameter in the URL to this EPK commitment. This hides the EPK from Google, maintaining privacy of the user’s TXN activity. **Step 3**: Typically, the user has an HTTP cookie from having previously-signed-in to their Google account, so Google merely checks this cookie. If the user has multiple Google accounts, Google asks the user to select which one they want to sign-in into the dApp (The less common path is for the user to have to type in their Google username and password). **Step 4**: Once the user has signed in, Google sends the dApp a signed JWT, which includes the user's `sub` identifier (e.g., `uid-123`), the application’s `aud` identifier (e.g., `"dapp-xyz"`) and the `nonce` with the EPK commitment (This assumes that the dApp has previously registered with Google and received this `"dapp-xyz"` identifier). **Step 5**: The dApp now has almost everything it needs to derive a keyless account for the user: the user’s identifier (`sub`) and the dApp’s identifier (`aud`). But, to preserve the privacy of the user, the dApp will use a third piece of information: a blinding factor $r$ called a **pepper**. The dApp will contact a so-called **guardian** who will deterministically derive a random $r$ from the given (`sub`, `aud`). Importantly, the guardian will only reveal $r$ to the dApp upon seeing a validly-signed JWT for the queried (`sub`, `aud`). **Step 6**: The dApp derives the address of the account as $\mathsf{addr} = H(\texttt{"uid-123"}, \texttt{"dapp-xyz"}, r)$, where $H$ is a cryptographic hash function. Note that the pepper $r$ is used to hide the user and app identity inside the address since, as we described above, only an authorized user with a valid JWT will be able to obtain this pepper. Also, note that the address is independent of the EPK. This is why the ESK need not be long-lived and can be lost. Finally, the dApp can, for example, send an NFT to the user at their address $\mathsf{addr}$. But how can the dApp authorize TXN from this account at $\mathsf{addr}$? We discuss that next. ## Flow: Obtaining a zero-knowledge proof before transacting In the previous flow, we showed how a dApp can sign in a Google user and derive their privacy-preserving keyless address, with the help of a guardian. Next, we show how this dApp can obtain a zero-knowledge proof (ZKP), which will allow it to authorize transactions from this address for the user. Importantly, the transaction will hide the user’s identifying information (e.g., the `sub` field). ![Keyless proof diagram](~/images/aptos-keyless/keyless-proof.png "Keyless proof diagram") **Step 1**: The dApp sends all the necessary public information (i.e., $\mathsf{epk}$, $\mathsf{GPK}$) and private information (i.e., JWT, signature $\sigma\_G$ from Google, EPK blinding factor $\rho$, and pepper $r$) to the **prover service**. **Step 2**: The prover derives the user’s address $\mathsf{addr}$ and computes a zero-knowledge proof (ZKP) $\pi$ for the keyless relation $\mathcal{R}\_\mathsf{keyless}$ (described below). This proof acts as a **privacy-preserving** digital certificate, and binds the user's address $\mathsf{addr}$ to the ephemeral public key $\mathsf{epk}$. The prover then sends $\pi$ to the dApp. In order to bind the $\mathsf{epk}$ with the user's address $\mathsf{addr}$, the ZKP will be used to convince the validators that the user is in possession of (1) a JWT signed by Google, (2) which commits to the $\mathsf{epk}$ in its `nonce` field, and (3) contains the same information as in the address, without leaking anything about the JWT, its signature $\sigma\_G$, $\rho$, or $r$. More formally, the ZKP $\pi$ convinces a verifier (i.e., the blockchain), who has public inputs $(\mathsf{addr}, \mathsf{epk}, \mathsf{GPK})$, that the prover knows secret inputs $(\mathsf{jwt}, \sigma\_G, \rho, r)$ such that the relation $\mathcal{R}\_\mathsf{keyless}$ depicted below holds: ![Keyless relation diagram](~/images/aptos-keyless/keyless_relation.png "Keyless relation diagram") Recall from before that the signed JWT itself binds the blockchain address $\mathsf{addr}$ to $\mathsf{epk}$, so that $\mathsf{epk}$ can sign transactions for $\mathsf{addr}$. However, the JWT would leak the user’s identity, so the ZKP serves to hide the JWT (and other private information) while arguing that the proper checks hold (i.e., the checks in $\mathcal{R}\_\mathsf{keyless}$). Next, we show how the dApp can now authorize TXNs from $\mathsf{addr}$. ## Flow: Sending a TXN from a keyless account The previous flow explained how a dApp can obtain a ZKP from the prover service. Next, we describe how the dApp leverages this ZKP to transact for the account. ![Keyless signing diagram](~/images/aptos-keyless/keyless-signing.png "Keyless signing diagram") **Step 1**: The dApp obtains an ephemeral signature $\sigma\_\mathsf{eph}$ over the TXN from the user. This could be done behind the user’s back, by the dApp itself who might manage the ESK. Or, it could be an actual signing request sent to the user, such as when the ESK is a WebAuthn passkey, which is stored on the user’s trusted hardware. **Step 2**: The dApp sends the TXN, the ZKP $\pi$, the ephemeral public key $\mathsf{epk}$, and the ephemeral signature $\sigma\_\mathsf{eph}$ to the blockchain validators. **Step 3**: To check the TXN is validly-signed, the validators perform several steps: (1) check that $\mathsf{epk}$ has not expired, (2) fetch the user’s address $\mathsf{addr}$ from the TXN, (3) verify the ZKP against $(\mathsf{addr}, \mathsf{epk}, \mathsf{GPK})$, and (4) verify the ephemeral signature $\sigma\_\mathsf{eph}$ on the TXN against the $\mathsf{epk}$. If all these checks pass, they can safely execute the TXN. ## Want more? The key ideas behind keyless accounts are also explained in this 20 minute presentation below.
# Keyless Integration Guide > Step-by-step guide to integrate domain-scoped Keyless accounts directly into your dApp with practical examples. import { Aside, Steps } from '@astrojs/starlight/components'; At a high level, there are three steps to follow in order to integrate Keyless Accounts. 1. **Configure your OpenID integration with your IdP.** In this step, the dApp will register with the IdP of choice (e.g. Google) and receive a `client_id` 2. **Install the Aptos TypeScript SDK.** 3. **Integrate Keyless Account support in your application client** 1. Set up the `"Sign In with [Idp]"` flow for your user. 2. Instantiate the user’s `KeylessAccount` 3. Sign and submit transactions via the `KeylessAccount`. ## Example Implementation You can find an example app demonstrating basic Keyless integration with Google in the [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/). Follow the directions in the README to start with the example. For more detailed instructions on keyless, please read the rest of this integration guide. 1. Step 1. Configure your OpenID integration with your IdP The first step is to setup the configuration with your IdP(s). [Follow the instructions here](/build/guides/aptos-keyless/oidc-support) 2. Step 2. Install the Aptos TypeScript SDK ```shellscript # Keyless is supported in version 1.18.1 and above pnpm install @aptos-labs/ts-sdk ``` 3. Step 3. Client Integration Steps Below are the default steps for a client to integrate Keyless Accounts #### 1. Present the user with a "Sign In with \[IdP]" button on the UI 1. In the background, we create an ephemeral key pair. Store this in local storage. ```typescript import {EphemeralKeyPair} from '@aptos-labs/ts-sdk'; const ephemeralKeyPair = EphemeralKeyPair.generate(); ``` 2. Save the `EphemeralKeyPair` in local storage, keyed by its `nonce`. ```typescript // This saves the EphemeralKeyPair in local storage storeEphemeralKeyPair(ephemeralKeyPair); ```
Example implementation for `storeEphemeralKeyPair` ```typescript filename="ephemeral.ts" /** * Store the ephemeral key pair in localStorage. */ export const storeEphemeralKeyPair = (ekp: EphemeralKeyPair): void => localStorage.setItem("@aptos/ekp", encodeEphemeralKeyPair(ekp)); /** * Retrieve the ephemeral key pair from localStorage if it exists. */ export const getLocalEphemeralKeyPair = (): EphemeralKeyPair | undefined => { try { const encodedEkp = localStorage.getItem("@aptos/ekp"); return encodedEkp ? decodeEphemeralKeyPair(encodedEkp) : undefined; } catch (error) { console.warn( "Failed to decode ephemeral key pair from localStorage", error ); return undefined; } }; /** * Stringify the ephemeral key pairs to be stored in localStorage */ export const encodeEphemeralKeyPair = (ekp: EphemeralKeyPair): string => JSON.stringify(ekp, (_, e) => { if (typeof e === "bigint") return { __type: "bigint", value: e.toString() }; if (e instanceof Uint8Array) return { __type: "Uint8Array", value: Array.from(e) }; if (e instanceof EphemeralKeyPair) return { __type: "EphemeralKeyPair", data: e.bcsToBytes() }; return e; }); /** * Parse the ephemeral key pairs from a string */ export const decodeEphemeralKeyPair = (encodedEkp: string): EphemeralKeyPair => JSON.parse(encodedEkp, (_, e) => { if (e && e.__type === "bigint") return BigInt(e.value); if (e && e.__type === "Uint8Array") return new Uint8Array(e.value); if (e && e.__type === "EphemeralKeyPair") return EphemeralKeyPair.fromBytes(e.data); return e; }); ```
3. Prepare the URL params of the login URL. Set the `redirect_uri` and `client_id` to your configured values with the IdP. Set the `nonce` to the nonce of the `EphemeralKeyPair` from step 1.1. ```typescript const redirectUri = 'https://.../login/callback' const clientId = env.IDP_CLIENT_ID // Get the nonce associated with ephemeralKeyPair const nonce = ephemeralKeyPair.nonce ``` 4. Construct the login URL for the user to authenticate with the IdP. Make sure the `openid` scope is set. Other scopes such as `email` and `profile` can be set based on your app’s needs. ```typescript const loginUrl = `https://accounts.google.com/o/oauth2/v2/auth?response_type=id_token&scope=openid+email+profile&nonce=${nonce}&redirect_uri=${redirectUri}&client_id=${clientId}` ``` 5. When the user clicks the login button, redirect the user to the `loginUrl` that was created in step 1.4. #### 2. Handle the callback by parsing the token and create a Keyless account for the user 1. Once the user completes the login flow, they will be redirected to the `redirect_uri` set in step 1. The JWT will be set in the URL as a search parameter in a URL fragment, keyed by `id_token`. Extract the JWT from the `window` by doing the following: ```typescript const parseJWTFromURL = (url: string): string | null => { const urlObject = new URL(url); const fragment = urlObject.hash.substring(1); const params = new URLSearchParams(fragment); return params.get('id_token'); }; // window.location.href = https://.../login/google/callback#id_token=... const jwt = parseJWTFromURL(window.location.href) ``` 2. Decode the JWT and get the extract the nonce value from the payload. ```typescript import { jwtDecode } from 'jwt-decode'; const payload = jwtDecode<{ nonce: string }>(jwt); const jwtNonce = payload.nonce ``` 3. Fetch the `EphemeralKeyPair` stored in step 1.2. Make sure to validate the nonce matches the decoded nonce and that the `EphemeralKeyPair` is not expired. ```typescript const ekp = getLocalEphemeralKeyPair(); // Validate the EphemeralKeyPair if (!ekp || ekp.nonce !== jwtNonce || ekp.isExpired() ) { throw new Error("Ephemeral key pair not found or expired"); } ``` 4. Instantiate the user’s `KeylessAccount` Depending on the type of Keyless you are using, follow the instructions below: 1. Normal Keyless ```tsx import {Aptos, AptosConfig, Network} from '@aptos-labs/ts-sdk'; const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Configure your network here const keylessAccount = await aptos.deriveKeylessAccount({ jwt, ephemeralKeyPair, }); ``` 2. Federated Keyless ```tsx import {Aptos, AptosConfig, Network} from '@aptos-labs/ts-sdk'; const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Configure your network here const keylessAccount = await aptos.deriveKeylessAccount({ jwt, ephemeralKeyPair, jwkAddress: jwkOwner.accountAddress }); ``` #### 3. Store the KeylessAccount in local storage (Optional) 1. After the account has been derived, store the `KeylessAccount` in local storage. This allows the user to return to the application without having to re-authenticate. ```typescript filename="keyless.ts" export const storeKeylessAccount = (account: KeylessAccount): void => localStorage.setItem("@aptos/account", encodeKeylessAccount(account)); export const encodeKeylessAccount = (account: KeylessAccount): string => JSON.stringify(account, (_, e) => { if (typeof e === "bigint") return { __type: "bigint", value: e.toString() }; if (e instanceof Uint8Array) return { __type: "Uint8Array", value: Array.from(e) }; if (e instanceof KeylessAccount) return { __type: "KeylessAccount", data: e.bcsToBytes() }; return e; }); ``` 2. Whenever the user returns back to the application, retrieve the `KeylessAccount` from local storage and use it to sign transactions. ```typescript filename="keyless.ts" export const getLocalKeylessAccount = (): KeylessAccount | undefined => { try { const encodedAccount = localStorage.getItem("@aptos/account"); return encodedAccount ? decodeKeylessAccount(encodedAccount) : undefined; } catch (error) { console.warn( "Failed to decode account from localStorage", error ); return undefined; } }; export const decodeKeylessAccount = (encodedAccount: string): KeylessAccount => JSON.parse(encodedAccount, (_, e) => { if (e && e.__type === "bigint") return BigInt(e.value); if (e && e.__type === "Uint8Array") return new Uint8Array(e.value); if (e && e.__type === "KeylessAccount") return KeylessAccount.fromBytes(e.data); return e; }); ``` #### 4. Submit transactions to the Aptos blockchain 1. Create the transaction you want to submit. Below is a simple coin transfer transaction for example: ```tsx import {Account} from '@aptos-labs/ts-sdk'; const bob = Account.generate(); const transaction = await aptos.transferCoinTransaction({ sender: keylessAccount.accountAddress, recipient: bob.accountAddress, amount: 100, }); ``` 2. Sign and submit the transaction to the chain. ```tsx const committedTxn = await aptos.signAndSubmitTransaction({ signer: keylessAccount, transaction }); ``` 3. Wait for the transaction to be processed on-chain ```tsx const committedTransactionResponse = await aptos.waitForTransaction({ transactionHash: committedTxn.hash }); ```
# Keyless Introduction > Revolutionary blockchain accounts using OIDC authentication - eliminate private keys for seamless user onboarding. Keyless accounts represent a pivotal advancement within the Aptos ecosystem, revolutionizing the way users onboard and interact with decentralized applications (dApps). Aptos Keyless allows users to gain ownership of a **self-custodial** Aptos blockchain account from their existing OpenID Connect (OIDC) account(s) (e.g., Sign in with Google; Sign in with Apple), rather than from a traditional secret key or mnemonic. In a nutshell, with Aptos Keyless, a user’s blockchain account is their OIDC account. Over time, Keyless will evolve to support many IdPs who support the OIDC standard, but we will begin with support for the providers listed [here](/build/guides/aptos-keyless/oidc-support). At the core of the keyless accounts paradigm lies a deep understanding of user experience and security challenges prevalent in traditional blockchain systems. Managing private keys, the cornerstone of user identity and asset ownership, often proves cumbersome and error-prone for users, particularly those lacking technical expertise. Keyless accounts offer an elegant solution by obviating the need for users to grapple with the intricacies of private key management. Instead, users authenticate themselves through access to common social sign in options like Google, Apple, and many more. With this new system comes some important tradeoffs to understand on behalf of your users before implementing Keyless in your application. The following pages will expand on the benefits of Keyless accounts, how to integrate, the system architecture, and FAQs. For a more verbose and technical dive into Keyless accounts, please see [AIP-61-Keyless Accounts](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-61.md). There are two ways to interact with Keyless accounts in the Aptos ecosystem. Developers are able to either 1) integrate the Aptos Keyless SDK directly into their dApp or 2) integrate a wallet, like Aptos Connect, that supports Keyless account creation. This documentation will focus on case #1 and more details on #2 can be found [here](https://aptosconnect.app/docs/). Please note that a direct integration of the Keyless SDK will result in user accounts being domain specific to your dApp whereas the use of a wallet integration will allow your users to carry their accounts to any application that supports that wallet. Note: the Aptos Keyless SDK and Aptos Connect are representative examples of the aforementioned product experience, but developers in our ecosystem are building alternatives, like a Keyless Unity SDK and alternative wallet products with Keyless integration. ## Aptos Keyless Benefits Keyless accounts are revolutionary to users for the following reasons: 1. **Simplified login user experience**: "1-click" account creation via familiar Web2 logins like Sign In with Google. 2. **Enhanced dApp user experience**: Ability to transact on the Aptos blockchain without needing to navigate away from the application experience to download a wallet. 3. **Secure key management**: Requires no manual secret key management by the user. Users sign transactions with the JSON Web Token (JWT) token issued by OIDC providers. As such, blockchain account access is synonymous with access to one’s OIDC account 4. **Improved account recovery**: Web2-like recovery flows are available to regain access to one’s blockchain account in case the user ever loses access to their OIDC account. 5. **Seamless cross-device experiences**: Users log in with their OIDC account no matter what device they are on - no need to download wallet software on each device, import their keys and encrypt them with a password, which must be maintained. With these benefits, come some important structural components of Keyless accounts for developers to be aware of. You can see more on this in our FAQs. # Keyless OIDC Support > Comprehensive list of supported OpenID Connect identity providers and configuration options across networks. import { Steps } from '@astrojs/starlight/components'; Aptos Keyless supports the following IdPs and IAM providers on our network(s). Support for additional IdPs to come. Please reach out if you have need for coverage for a specific use case. | Identity Provider | Federated Only | Devnet | Testnet | Mainnet | | ----------------- | -------------- | --------- | ------- | ------- | | Google | No | Live | Live | Live | | Apple | No | Live | Live | Live | | Auth0 | Yes | Live | Live | Live | | Cognito | Yes | Live | Live | Live | | Microsoft | No | In review | - | - | | Github | No | In review | - | - | | Facebook | No | In review | - | - | If your identity provider is marked as "Federated Only", you will need to follow the instructions for [Federated Keyless](/build/guides/aptos-keyless/federated-keyless). To integrate Aptos Keyless into your dApp, you must register your dApp with at least one of the available identity providers via their OIDC registration process. Each respective registration process will assign a Client ID to your application, which will serve as an identifier for your application in the Keyless architecture. ## Registering your dApp with Google 1. Step 1: Sign in to Google Developer Console 1. Navigate to the [Google Cloud Console](https://console.cloud.google.com/). 2. Sign in with your Google account credentials. 2. Step 2: Create a New Project 1. If you don't have an existing project, click on the "Select a project" dropdown menu at the top of the page and choose "New Project." 2. Enter a name for your project and click "Create." Detailed instructions can be found [here](https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project). 3. Step 3: Configure Consent Screen 1. In the left sidebar, navigate to "APIs & Services" > "OAuth consent screen." 2. Choose "External" user type and click "Create." 3. Enter the required details such as the application name, user support email, and developer contact information. 4. Optionally, add additional details like the application logo and privacy policy URL. 5. Click "Save and continue." Detailed steps are available [here](https://developers.google.com/workspace/guides/create-credentials#configure_the_oauth_consent_screen). 4. Step 4: Register Your Application 1. In the left sidebar, navigate to "APIs & Services" > "Credentials." ![Google Credentials navigation screenshot](~/images/aptos-keyless/google-credentials-nav.png "Google Credentials navigation screenshot") 2. Click on "Create Credentials" and select "OAuth client ID." ![Google create credentials screenshot](~/images/aptos-keyless/google-create-credentials.png "Google create credentials screenshot") 3. Choose the application type (e.g., Web application, Desktop app, or Mobile app). 4. Enter the necessary details such as the name of your application and the authorized redirect URIs. For OIDC, the redirect URIs should follow the format [https://your-app-domain.com/auth/google/callback](https://your-app-domain.com/auth/google/callback). 5. Click "Create." 5. Step 5: Obtain Client ID and Client Secret 1. After creating the OAuth client ID, Google will provide you with a client ID and client secret. These credentials are essential for authenticating your application. 2. Note down the client ID and client secret securely. Do not expose them publicly. 6. Step 6: Configure OIDC Integration in Your Application 1. Integrate OIDC authentication into your application using a suitable OIDC library or framework (e.g., Passport.js for Node.js, Spring Security for Java, or Auth0 for various platforms). 2. Use the client ID and client secret obtained from Google to configure OIDC authentication in your application settings. 3. Set up the appropriate callback URL ([https://your-app-domain.com/auth/google/callback](https://your-app-domain.com/auth/google/callback)) for handling authentication responses from Google. ## Registering your dApp with Apple 1. Step 1: Sign in to Apple Developer Account 1. Go to the [Apple Developer website](https://developer.apple.com/). 2. Sign in with your Apple ID. 3. Enroll in the Apple Developer Program if not already. ![Apple developer program enrollment screenshot](~/images/aptos-keyless/apple-dev-program.png "Apple developer program enrollment screenshot") 2. Step 2: Create a New App ID 1. Navigate to the "Certificates, Identifiers & Profiles" section. 2. Click on "Identifiers" in the sidebar. 3. Click the "+" button to create a new App ID. 4. Fill in the details for your app, including the name and bundle ID. 5. Enable "Sign in with Apple" under the "Capabilities" section. 6. Click "Continue" and then "Register" to create the App ID. 3. Step 3: Generate a Private Key 1. In the "Keys" section of the "Certificates, Identifiers & Profiles" page, click the "+" button to create a new key. 2. Enter a name for the key, enable the "Sign in with Apple" capability, and click "Continue." 3. Download the generated private key and securely store it. This key will be used to authenticate your app with Apple's OIDC service. 4. Step 4: Configure Redirect URIs 1. Under the "App ID" section, locate your newly created App ID and click on it. 2. Scroll down to the "Sign in with Apple" section and click on "Edit." 3. Add the redirect URIs that your application will use for callback after authentication. The format should be [https://your-app-domain.com/auth/apple/callback](https://your-app-domain.com/auth/apple/callback). 4. Click "Save" to update the settings. 5. Step 5: Set Up Your OIDC Integration 1. Use an OIDC library or framework compatible with Apple's OIDC service (e.g., Passport.js for Node.js, Spring Security for Java). 2. Configure your application to use the client ID and private key obtained from Apple during the registration process. 3. Set up the appropriate callback URL ([https://your-app-domain.com/auth/apple/callback](https://your-app-domain.com/auth/apple/callback)) for handling authentication responses from Apple. # Keyless Terminology and FAQ > Essential terminology, definitions, and frequently asked questions about Aptos Keyless accounts and OIDC integration. ## Terminology - **OpenID Connect (OIDC)**: is the identity authentication protocol used to enable federated identity verification. This protocol is what is used when a user goes through the "Sign in with Google" flow for example. - **Identity Provider (IdP)**: is the trusted authority who authenticates your identity via OIDC. Supported example includes: Google. - **JSON Web Token (JWT):** is an open standard used to share security information between two parties β€” a client and a server. Each JWT contains encoded JSON objects, including a set of claims. JWTs are signed using a cryptographic algorithm to ensure that the claims cannot be altered after the token is issued. - `iss`, an identifier for the OIDC provider (e.g., [https://accounts.google.com](https://accounts.google.com)) - `aud`, the OAuth `client_id` of the application that the user is signing in to (e.g., [Notion.so](https://notion.so)) - `sub`, an identifier that the OIDC provider uses to identify the user - This could be an identifier specific to this `client_id` - Or, it could be an identifier shared across different `client_id`'s (e.g., Facebook’s OIDC does this) - `email`, some providers might also expose the user’s email as one of the fields (e.g., Google) - in addition, an `email_verified` field will be exposed to indicate if the provider has verified that the user owns this email address - `nonce`, arbitrary data that the application wants the OIDC provider to sign over - `iat`, the time the JWT was issued at. - **Ephemeral Key Pair:** a temporary public/private key pair that is used to sign transactions for an Aptos Keyless account. The public key and its expiration date are committed in the JWT token via the `nonce` field. - **Keyless Account:** a blockchain account that is directly-derived from (1) a user’s OIDC account (e.g., `alice@gmail.com`) and (2) an associated application’s OAuth client\_id (e.g., Notion.so). Users authenticate through the OIDC flow. - **JSON Web Key (JWK):** is the cryptographic public key of the OIDC provider. This public key is used to verify the signature on the JWTs that the OIDC provider issues to the client application. This way, the client application can verify the authenticity of the tokens and ensure that they have not been tampered with. - **client\_id:** the OAuth identifier for your application that you will receive from the IdP after registering your application with them. This will be used in our keyless architecture in the address derivation for your users. - **redirect\_uri:** the URI of the callback handler once the user successfully authenticates. Needs to be registered with your IdP. ## Ceremony Aptos engaged in iterative trusted setup ceremonies to secure our Groth16 based ZK circuit. A trusted setup ceremony is a multi-party computation (MPC) that outputs the prover and verifier keys used in a zkSNARK system, common for efficient zero-knowledge proof systems. As long as a single participant in the ceremony is honest, the process is considered secure and the outputs will be valid. Our initial ceremony consisted of 140+ members of the Aptos ecosystem, which was an incredible show of the power of decentralization, security, and community - and a follow up ceremony was held following a developer feedback phase that allowed us to identify and implement an improvement to our circuit that helped us ensure Keyless is universally accessible. Our final ceremony contributions can be found in this repo \[here] and verified using the process outlined \[here]. ## Frequently Asked Questions **What is the best way to use Keyless accounts?** - The best way to use Keyless accounts depends on your use case. If seamless account interoperability across our ecosystem is important to your dApp experience (think: mint an NFT on your platform and allow users to sell their NFT on an external NFT marketplace), you might want to consider integrating a wallet that supports Keyless. If you want to create a fully embedded account experience in your dApp, allowing users to transact without ever leaving your application, you might want to do a direct integration of the Aptos Keyless SDK. **Does Keyless work with sponsored transactions or do my users always need to pay for their own gas?** - Yes, Keyless works with sponsored transactions like any regular private key based account. **If I use the Aptos Keyless SDK, can my user’s use their accounts across other dApps?** - Keyless accounts are scoped to the domain they are created with as the address derivation includes a unique identifier for the application. **What is Aptos Connect?** - Account Management Infrastructure: Central to the keyless accounts paradigm is a robust account management infrastructure that facilitates the creation, deletion, and management of user accounts, alongside the storage and retrieval of associated metadata. - While the adoption of keyless accounts heralds a paradigm shift towards enhanced usability and security, it is imperative for developers to remain cognizant of tradeoffs associated with this system vs. common alternatives like plaintext private keys. **Are there dependency on external services?** - Yes, Keyless accounts introduce a degree of dependency on external authentication services (pepper and prover), necessitating contingency plans and fallback mechanisms to mitigate service disruptions and ensure uninterrupted user access **If my dApp goes down, my users cannot access their Keyless accounts. How can I help protect them in that case?** - We encourage dApp developers to support additional backup recovery options for your users when integrating Keyless into a dApp. Specifically, we recommend that you support adding a backup private key to Keyless accounts in your dApp. Practically, this would transform the accounts into 1 of 2 multi-signature accounts where both keys are owned by the user. This would allow users to continue using OIDC login via your dApp to access their Keyless accounts but would add the ability for your users to export their backup private key to any self custodial product, where they could sign transactions from that same account with their traditional private key. Doing this will ensure that users never lose access to their digital assets, even if your dApp shuts down or the user loses access to their OIDC account. - You should make a determination at what point in the user journey to incorporate a back-up is appropriate for your dApp. Incorporating a backup method later in the user journey would preserve the seamless onboarding experience that Keyless offers but could result in less users receiving a recovery key. Prompting users to add a backup key during the onboarding process would likely lead to more users receiving a recovery key but could add potential friction during the onboarding process. # Keyless Simple Example > Example demonstrating Keyless account integration with Google authentication and practical code samples. Explore the code in the [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/tree/main/examples/keyless-example/). Follow the instructions in the repository's `README.md` to set up and run the example locally with your own Google `client_id`. # Build an End-to-End Dapp on Aptos > Complete tutorial for building a todo list dapp from smart contract to frontend with wallet integration on Aptos. import { Steps } from '@astrojs/starlight/components'; A common way to learn a new framework or programming language is to build a simple todo list. In this tutorial, we will learn how to build an end-to-end todo list dapp, starting from the smart contract side through the front-end side and finally use of a wallet to interact with the two. See the completed code in the [source-code](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/my_first_dapp). ## Prerequisites You must have: - [node and npm](https://nodejs.org/en/) - [VSCode IDE](https://code.visualstudio.com/) or [Cursor](https://www.cursor.com/) Although we will explain some React decisions, we are not going to deep dive into how React works; so we assume you have some previous experience with React. ## Setup In this section, we will setup three things: 1. API key using [Geomi](https://geomi.dev/) 2. Project using [create-aptos-dapp](/build/create-aptos-dapp) which will hold our project files, both client-side code (React based) and the Move code (our smart contract) 3. [Move On Aptos](/build/smart-contracts/move-vscode-extension) VSCode extension ### Setup API key First let's create an API key using [Geomi](https://geomi.dev/). The Aptos TypeScript SDK uses Geomi API by default, and without an API key, you will get very low rate limits. 1. **Visit Geomi**: Go to [geomi.dev](https://geomi.dev/) in your web browser. 2. **Sign up or Log in**: Create a new account or log in to your existing Geomi account. 3. **Create a new project**: Click on "Create New Project" to create a new project and name it `my-first-dapp`. ![Geomi API key creation 1](~/images/screenshots/geomi-api-1.png) ![Geomi API key creation 2](~/images/screenshots/geomi-api-2.png) 4. **Navigate to API Resource tab**: Navigate to the API Resource tab in your dashboard. ![Geomi API key creation 3](~/images/screenshots/geomi-api-3.png) 5. **Create New API Resource**: Name it `my-first-dapp-api` and set the network to `Devnet`. ![Geomi API key creation 4](~/images/screenshots/geomi-api-4.png) 6. **Configure API Key**: Since we will be using the API key in our web app, toggle on `Client usage`. Here you can configure allowed URLs and extension IDs that can use the API key. Set `Allowed URLs` to `http://localhost`, which is the development server we will be using. Also set `Per IP rate limit` to `100,000`. ![Geomi API key creation 5](~/images/screenshots/geomi-api-5.png) 7. **Copy and Save**: Now you have your API key. Copy the key as we will be using it in the next step. ![Geomi API key creation 6](~/images/screenshots/geomi-api-6.png) ### Setup Project with create-aptos-dapp Next, we will be using [create-aptos-dapp](/build/create-aptos-dapp) to create the project. 1. **Open a terminal and navigate to the desired directory** for the project (for example, the `Desktop` directory). 2. **Run the create-aptos-dapp command** to create the project: ```shellscript filename="Terminal" npx create-aptos-dapp@latest ``` 3. **Follow the instructions to create the project** with these settings: - Choose a name for the project, for example `my-first-dapp` - Choose the `Full-stack project` option - Choose the `Boilerplate Template` option - For simplicity, choose not to use Surf - Choose the `Vite app` framework option - Choose the `Devnet` network option - Say `Yes` for using API key and paste the Geomi API key you copied earlier - Say `No` for customizing default selections 4. The tool will create the project in a directory with the same name as the project and install the required dependencies. 5. Follow the `Next Steps` instructions. ### Setup Move On Aptos VSCode extension Finally, let's install the [Move On Aptos](/build/smart-contracts/move-vscode-extension) VSCode extension. 1. Open VSCode (or Cursor) and navigate to the Extensions tab. 2. Search for `Move On Aptos` published by `aptoslabs` and install the extension. ## Chapters After meeting the [prerequisites](#prerequisites) and [getting set up](#setup) as described below, you will follow this tutorial in this order: 1. [Create a smart contract](/build/guides/build-e2e-dapp/1-create-smart-contract) 2. [Set up a frontend](/build/guides/build-e2e-dapp/2-set-up-the-frontend) 3. [Fetch Data from Chain](/build/guides/build-e2e-dapp/3-fetch-data-from-chain) 4. [Submit data to chain](/build/guides/build-e2e-dapp/4-submit-data-to-chain) 5. [Handle Tasks](/build/guides/build-e2e-dapp/5-handle-tasks) Now let's [create the Todo List smart contract](/build/guides/build-e2e-dapp/1-create-smart-contract). # 1. Create a Smart Contract > First step in building an end-to-end dapp: create and deploy the Move smart contract for managing todo tasks. import { Aside, Steps } from '@astrojs/starlight/components'; This is the first chapter of the tutorial on [building an end-to-end dapp on Aptos](/build/guides/build-e2e-dapp). If you haven’t done it, review that introduction, and ensure your environment meets the [prerequisites](/build/guides/build-e2e-dapp#prerequisites) listed there. Now that you are all set up, let's explore the `contract` directory. ![contract-directory](~/images/build-e2e-dapp-img-1-1.png) ### What is a `Move.toml` file? A `Move.toml` file is a manifest file that contains metadata such as name, version, and dependencies for the package. Take a look at the new `Move.toml` file. You should see your package information and an `AptosFramework` dependency. The `AptosFramework` dependency points to the `aptos-core/aptos-move/framework/aptos-framework` GitHub repo main branch. ### Why `sources` directory? The `sources` directory holds a collection of `.move` modules files. And later when we want to compile the package using the CLI, the compiler will look for that `sources` directory and its `Move.toml` file. ### What is the `tests` directory? The `tests` directory holds `.move` files that are used to test the files in our `sources` directory. ### Create a Move module An account is needed to publish a Move module. When we installed the template, the tool created a new account for us and added it to the `.env` file. If you open that file, you will see content resembling: ```shellscript filename=".env" PROJECT_NAME=my-first-dapp VITE_APP_NETWORK=devnet VITE_APTOS_API_KEY=YOUR_API_KEY VITE_MODULE_PUBLISHER_ACCOUNT_ADDRESS=0x1cecfef9e239eff12fb1a3d189a121c37f48908d86c0e9c02ec103e0a05ddebb #This is the module publisher account's private key. Be cautious about who you share it with, and ensure it is not exposed when deploying your dApp. VITE_MODULE_PUBLISHER_ACCOUNT_PRIVATE_KEY=0x84638fd5c42d0937503111a587307169842f355ab661b5253c01cfe389373f43 ``` The Boilerplate template comes with a pre generated `message_board.move` file, a relevant test file and a `Move.toml` file. We will not be using `message_board.move` in this tutorial, so delete it. As mentioned, our sources directory holds our `.move` module files; so let's create a new `todolist.move` file. 1. **Create a new `todolist.move` file** within the `sources` directory and add the following to that file: ```move filename="todolist.move" module todolist_addr::todolist { } ``` 2. **Open the `Move.toml` file**, replace the name from `MessageBoard` to `Todolist` and the address from `message_board_addr` to `todolist_addr`, like the following code: ```toml filename="Move.toml" [package] name = "Todolist" version = "1.0.0" authors = [] [addresses] todolist_addr = "_" [dependencies] AptosFramework = { git = "https://github.com/aptos-labs/aptos-framework.git", rev = "mainnet", subdir = "aptos-framework" } [dev-dependencies] ``` ### What is the `'_'` in the `Move.toml` file? The `'_'` is a placeholder for the account address. When we run the `move` compiler, the compiler will replace it with the actual account address. ### Scripts for running `move` commands `create-aptos-dapp` comes with premade scripts to easily run `move` commands, like `compile`, `test` and `publish`. 1. Open each of the files in the `scripts/move` directory and update the `message_board_addr` variable to be `todolist_addr`. ```js filename="scripts/move/compile.js" ... namedAddresses: { todolist_addr: process.env.VITE_MODULE_PUBLISHER_ACCOUNT_ADDRESS, }, ... ``` ### Our contract logic Before jumping into writing code, let’s first understand what we want our smart contract program to do. For ease of understanding, we will keep the logic pretty simple: 1. An account creates a new list. 2. An account creates a new task on their list. - Whenever someone creates a new task, emit a `TaskCreated` event. 3. Let an account mark their task as completed. We can start with defining a `TodoList` struct, that holds the: - tasks array - a task counter that counts the number of created tasks (we can use that to differentiate between the tasks) And also create a `Task` struct that holds: - `task_id` - derived from the TodoList task counter. - `creator_addr` - the account address who created that task. - `content` - the task content. - `completed` - a boolean that marks whether that task is completed or not. On the `todolist.move` file, update the content in the module with: ```move filename="todolist.move" ... /// Main resource that stores all tasks for an account struct TodoList has key { tasks: Table, task_counter: u64 } /// Individual task structure struct Task has store, drop, copy { task_id: u64, creator_addr: address, content: String, completed: bool, } ... ``` **What did we just add?** **TodoList** A struct that has the `key` and `store` abilities: - `Key` ability allows struct to be used as a storage identifier. In other words, `key` is an ability to be stored at the top-level and act as a storage. We need it here to have `TodoList` be a resource stored in our user account. When a struct has the `key` ability, it turns this struct into a `resource`: - `Resource` is stored under the account - therefore it _exists_ only when assigned to an account and can be _accessed_ through this account only. **Task** A struct that has the `store`, `drop` and `copy`abilities. β€’ `Store` - Task needs `Store` as it’s stored inside another struct (TodoList) β€’ `Copy` - value can be _copied_ (or cloned by value). β€’ `Drop` - value can be _dropped_ by the end of scope. Let’s try to compile what we have now **(Spoiler alert: it will not work)**: 1. Run: `npm run move:compile` **Seeing errors?!** Let’s understand them. We have some errors on `Unbound type`- this is happening because we used some types but never imported them, and the compiler doesn't know where to get them from. On the top of the module, import those types by adding: ```move filename="todolist.move" ... use aptos_std::table::Table; use std::string::String; ... ``` That will tell the compiler where it can get those types from. 2. Run the `npm run move:compile` command again; If all goes well, we should see a response resembling (where the resulting account address is your default profile account address): ```shellscript filename="Terminal" Compiling, may take a little while to download git dependencies... UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-core.git INCLUDING DEPENDENCY AptosFramework INCLUDING DEPENDENCY AptosStdlib INCLUDING DEPENDENCY MoveStdlib BUILDING Todolist { "Result": [ "1cecfef9e239eff12fb1a3d189a121c37f48908d86c0e9c02ec103e0a05ddebb::todolist" ] } ``` At this point, we have successfully compiled our Move module. Yay! We also have a new `move/build` directory (created by the compiler) that holds our compiled modules, build information and `sources` directory. ### Create list function The first thing an account can and should do with our contract is to create a new list. Creating a list is essentially submitting a transaction, and so we need to know the `signer` who signed and submitted the transaction: 1. Add a `create_list` function that accepts a `signer` inside the `Todolist` module. ```move filename="todolist.move" public entry fun create_list(account: &signer){ } ``` **Let’s understand the components of this function** - `entry` - an _entry_ function is a function that can be called via transactions. Simply put, whenever you want to submit a transaction to the chain, you should call an entry function. - `&signer` - The **signer** argument is injected by the Move VM as the address who signed that transaction. Our code has a `TodoList` resource. Resource is stored under the account; therefore, it _exists_ only when assigned to an account and can be _accessed_ only through this account. That means to create the `TodoList` resource, we need to assign it to an account that only this account can have access to. The `create_list` function can handle that `TodoList` resource creation. 2. Add the following to the `create_list` function ```move filename="todolist.move" /// Initializes a new todo list for the account public entry fun create_list(account: &signer) { let tasks_holder = TodoList { tasks: table::new(), task_counter: 0 }; // Move the TodoList resource under the signer account move_to(account, tasks_holder); } ``` This function takes in a `signer`, creates a new `TodoList` resource, and uses `move_to` to have the resource stored in the provided signer account. 3. Let’s make sure everything is still working by running the `npm run move:compile` command again. ### Create task function As mentioned before, our contract has a create task function that lets an account create a new task. Creating a task is also essentially submitting a transaction, and so we need to know the `signer` who signed and submitted the transaction. Another element we want to accept in our function is the task `content`. 1. Add a `create_task` function that accepts a `signer` and task `content` and the function logic. ```move filename="todolist.move" /// Creates a new task in the todo list public entry fun create_task(account: &signer, content: String) acquires TodoList { // Get the signer address let signer_address = signer::address_of(account); // Get the TodoList resource let todo_list = borrow_global_mut(signer_address); // Increment task counter let counter = todo_list.task_counter + 1; // Create a new task let new_task = Task { task_id: counter, creator_addr: signer_address, content, completed: false }; // Add the new task to the tasks table todo_list.tasks.upsert(counter, new_task); // Update the task counter todo_list.task_counter = counter; // Emit a task created event event::emit(TaskCreated { task_id: counter, creator_addr: signer_address, content, completed: false }) } ``` 2. You will notice that we have not created the `TaskCreated` event struct yet. Create it at the top of the file (under the use statements) with the following code: ```move filename="todolist.move" #[event] struct TaskCreated has drop, store { task_id: u64, creator_addr: address, content: String, completed: bool, } ``` 3. Since we now use three new modules - signer, event, and table (you can see it being used in `signer::`, `event::`, and `table::`) - we need to import these modules. At the top of the file, add those two use statements (replace the `table` use statement with the following code): ```move filename="todolist.move" use aptos_framework::event; use aptos_std::table::{Self, Table}; // This one we already have, need to modify it use std::signer; ``` 4. Let’s make sure everything is still working by running the `npm run move:compile` command again. **Back to the code; what is happening here?** - First, we want to get the signer address, so we can get this account’s `TodoList` resource. - Then, we retrieve the `TodoList` resource with the `signer_address`; with that we have access to the `TodoList` properties. - We can now increment the `task_counter` property, and create a new `Task` with the `signer_address`, `counter` and the provided `content`. - We push it to the `todo_list.tasks` table that holds all of our tasks along with the new `counter` (which is the table key) and the newly created Task. - Then we assign the global `task_counter` to be the new incremented counter. - Finally, we emit the `TaskCreated` event that holds the new Task data. `event::emit()` is an `aptos-framework` function that emits a module event with payload `msg`. In our case, we are passing the function a `TaskCreated` event struct with the new Task data. ### Complete task function Another function we want our contract to hold is the option to mark a task as completed. 1. Add a `complete_task` function that accepts a `signer` and a `task_id`: ```move filename="todolist.move" /// Marks a task as completed public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { // Get the signer address let signer_address = signer::address_of(account); // Get the TodoList resource let todo_list = borrow_global_mut(signer_address); // Get the task record let task_record = todo_list.tasks.borrow_mut(task_id); // Mark the task as completed task_record.completed = true; } ``` **Let’s understand the code.** - As before in our create list function, we retrieve the `TodoList` struct by the signer address, so we can have access to the tasks table that holds all the account tasks. - Then, we get a mutable reference for the task with the provided `task_id` on the `todo_list.tasks` table. - Finally, we update that task `completed` property to be true. 2. Now compile the code by running: `npm run move:compile` to make sure everything is still working. ### Add validations As this code now compiles, we want to have some validations and checks before creating a new task or updating the task as completed, so we can be sure our functions work as expected. 1. Add a check to the `create_task` function to make sure the signer account has a list: ```move filename="todolist.move" public entry fun create_task(account: &signer, content: String) acquires TodoList { // gets the signer address let signer_address = signer::address_of(account); // assert signer has created a list assert!(exists(signer_address), 1); ... } ``` 2. Add a check to the `complete_task` function to make sure the: - signer has created a list. - task exists. - task is not completed. With the following code: ```move filename="todolist.move" /// Marks a task as completed public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { // Get the signer address let signer_address = signer::address_of(account); // Ensure the account has initialized a todo list assert!(exists(signer_address), 1); // Get the TodoList resource let todo_list = borrow_global_mut(signer_address); // Ensure the task exists assert!(todo_list.tasks.contains(task_id), 2); // Get the task record let task_record = todo_list.tasks.borrow_mut(task_id); // Ensure the task is not already completed assert!(task_record.completed == false, 3); // Mark the task as completed task_record.completed = true; } ``` We just added our first `assert` statements! If you noticed, `assert` accepts two arguments: the first is what to check for, and the second is an error code. Instead of passing in an arbitrary number, a convention is to declare `errors` on the top of the module file and use these instead. On the top of the module file (under the `use` statements), add those error declarations: ```move filename="todolist.move" // Errors /// Account has not initialized a todo list const ENOT_INITIALIZED: u64 = 1; /// Task does not exist const ETASK_DOESNT_EXIST: u64 = 2; /// Task is already completed const ETASK_IS_COMPLETED: u64 = 3; ``` Now we can update our asserts with these constants: ```move filename="todolist.move" /// Creates a new task in the todo list public entry fun create_task(account: &signer, content: String) acquires TodoList { // Get the signer address let signer_address = signer::address_of(account); // Ensure the account has initialized a todo list assert!(exists(signer_address), ENOT_INITIALIZED); ... } /// Marks a task as completed public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { // Get the signer address let signer_address = signer::address_of(account); // Ensure the account has initialized a todo list assert!(exists(signer_address), ENOT_INITIALIZED); // Get the TodoList resource let todo_list = borrow_global_mut(signer_address); // Ensure the task exists assert!(todo_list.tasks.contains(task_id), ETASK_DOESNT_EXIST); // Get the task record let task_record = todo_list.tasks.borrow_mut(task_id); // Ensure the task is not already completed assert!(task_record.completed == false, ETASK_IS_COMPLETED); // Mark the task as completed task_record.completed = true; } ``` **WONDERFUL!!** Let’s stop for one moment and make sure our code compiles by running the `npm run move:compile` command. If all goes well, we should output resembling: ```shellscript filename="Terminal" Compiling, may take a little while to download git dependencies... UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-core.git INCLUDING DEPENDENCY AptosFramework INCLUDING DEPENDENCY AptosStdlib INCLUDING DEPENDENCY MoveStdlib BUILDING MessageBoard { "Result": [ "1cecfef9e239eff12fb1a3d189a121c37f48908d86c0e9c02ec103e0a05ddebb::todolist" ] } ``` If you encounter errors, make sure you followed the steps above correctly and try to determine the cause of the issues. ### Write tests Now that we have our smart contract logic ready, we need to add some tests for it. First, delete the `test_end_to_end.move` file in the `tests` directory, as we won't be using it. 1. For simplicity, and because we don't have much code to test, we will have the tests in the `todolist.move` file. If you need to write a more complex test, you should create a separate test file in the `tests` directory. The test steps are: ```move filename="todolist.move" // create a list // create a task // update task as completed ``` 2. Add the following code to the bottom of the `todolist.move` file: ```move filename="todolist.move" #[test] public entry fun test_flow() { } ``` Note: Test functions use the `#[test]` annotation. 3. Update the test function to be: ```move filename="todolist.move" #[test(admin = @0x123)] public entry fun test_flow(admin: signer) acquires TodoList { // Create an admin account for testing account::create_account_for_test(signer::address_of(&admin)); // Initialize a todo list for the admin account create_list(&admin); // Create a task and verify it was added correctly create_task(&admin, string::utf8(b"Create e2e guide video for aptos devs.")); let todo_list = borrow_global(signer::address_of(&admin)); assert!(todo_list.task_counter == 1, 5); // Verify task details let task_record = todo_list.tasks.borrow(todo_list.task_counter); assert!(task_record.task_id == 1, 6); assert!(task_record.completed == false, 7); assert!(task_record.content == string::utf8(b"Create e2e guide video for aptos devs."), 8); assert!(task_record.creator_addr == signer::address_of(&admin), 9); // Complete the task and verify it was marked as completed complete_task(&admin, 1); let todo_list = borrow_global(signer::address_of(&admin)); let task_record = todo_list.tasks.borrow(1); assert!(task_record.task_id == 1, 10); assert!(task_record.completed == true, 11); assert!(task_record.content == string::utf8(b"Create e2e guide video for aptos devs."), 12); assert!(task_record.creator_addr == signer::address_of(&admin), 13); } ``` Our `#[test]` annotation has changed and declares an account variable. Additionally, the function itself now accepts a signer argument. **Let’s understand our tests.** Since our tests run outside an account scope, we need to _create_ accounts to use in our tests. The `#[test]` annotation gives us the option to declare those accounts. We use an `admin` account and set it to a random account address (`@0x123`). The function accepts this signer (account) and creates it by using a built-in function to create an account for test. Then we simply go through the flow by: - creating a list - creating a task - updating a task as completed And assert the expected data/behavior at each step. Before running the tests again, we need to import (`use`) some new modules we are now employing in our code: 4. At the top of the file, add these `use` statements: ```move filename="todolist.move" #[test_only] use aptos_framework::account; #[test_only] use std::string::{Self}; ``` Note that we are using the `#[test_only]` annotation to import the modules only for testing. This is because we don't want to use these modules in our production code. 5. Run the `npm run move:test` command. If all goes right, we should see a success message like: ```move filename="todolist.move" Running Move unit tests [ PASS ] 0x1cecfef9e239eff12fb1a3d189a121c37f48908d86c0e9c02ec103e0a05ddebb::todolist::test_flow Test result: OK. Total tests: 1; passed: 1; failed: 0 { "Result": "Success" } ``` 6. Let’s add one more test to make sure our `complete_task` function works as expected. Add another test function with: ```move filename="todolist.move" #[test(admin = @0x123)] #[expected_failure(abort_code = ENOT_INITIALIZED)] public entry fun account_can_not_update_task(admin: signer) acquires TodoList { // Create an admin account for testing account::create_account_for_test(signer::address_of(&admin)); // Attempt to complete a task without creating a list first (should fail) complete_task(&admin, 2); } ``` This test confirms that an account can’t use that function if they haven’t created a list before. The test also uses a special annotation `#[expected_failure]` that, as the name suggests, expects to fail with an `ENOT_INITIALIZED` error code. 7. Run the `npm run move:test` command. If all goes right, we should see a success message like: ```shellscript filename="Terminal" Running Move unit tests [ PASS ] 0x1cecfef9e239eff12fb1a3d189a121c37f48908d86c0e9c02ec103e0a05ddebb::todolist::account_can_not_update_task [ PASS ] 0x1cecfef9e239eff12fb1a3d189a121c37f48908d86c0e9c02ec103e0a05ddebb::todolist::test_flow Test result: OK. Total tests: 2; passed: 2; failed: 0 { "Result": "Success" } ``` Now that everything works, we can compile the Move modules and publish the Move package to chain so our React app (and everyone else) can interact with our smart contract! ### Publish todolist module to chain 1. Run: `npm run move:test` and `npm run move:compile` - all should work without errors. 2. Run: `npm run move:publish` 3. Enter `yes` to the prompt `Do you want to publish this package at object address 0x8f66343d40de3eeef5dd45cab8c1531a542f0e5f546da9f11852d4c2b30165a7 [yes/no] >`. **(Spoiler alert: it will fail)** **Oh no! We got an error!** It complains about an account mismatch with the `MODULE_ADDRESS_DOES_NOT_MATCH_SENDER` error code. Apparently we compiled the package with a different account we try to publish it. Let's fix it. 1. Open the `scripts/move/publish.js` file. 2. Update the `addressName` variable value to be `todolist_addr`. That will use the same account we used for compiling the package. Let's try again: 1. Run: `npm run move:publish` 2. Enter `yes` in the prompt. 3. Enter `yes` in the second prompt. 4. That will compile, simulate and finally publish your module into devnet. You should see a success message: ```shellscript filename="Terminal" Transaction submitted: https://explorer.aptoslabs.com/txn/0x68dadf24b9ec29b9c32bd78836d20032de615bbef5f10db580228577f7ca945a?network=devnet Code was successfully deployed to object address 0x2bce4f7bb8a67641875ba5076850d2154eb9621b0c021982bdcd80731279efa6 { "Result": "Success" } ``` 5. You can now head to the [Aptos Explorer](https://explorer.aptoslabs.com/?network=devnet) link and view the transaction details. You can also see the module published on chain by looking for the object address. ### Full Todolist module code Here is the full `todolist.move` file to confirm your work: ```move filename="todolist.move" module todolist_addr::todolist { use aptos_framework::event; use aptos_std::table::{Self, Table}; use std::signer; use std::string::String; #[test_only] use aptos_framework::account; #[test_only] use std::string::{Self}; // Errors /// Account has not initialized a todo list const ENOT_INITIALIZED: u64 = 1; /// Task does not exist const ETASK_DOESNT_EXIST: u64 = 2; /// Task is already completed const ETASK_IS_COMPLETED: u64 = 3; #[event] struct TaskCreated has drop, store { task_id: u64, creator_addr: address, content: String, completed: bool, } /// Main resource that stores all tasks for an account struct TodoList has key { tasks: Table, task_counter: u64 } /// Individual task structure struct Task has store, drop, copy { task_id: u64, creator_addr: address, content: String, completed: bool, } /// Initializes a new todo list for the account public entry fun create_list(account: &signer) { let tasks_holder = TodoList { tasks: table::new(), task_counter: 0 }; // Move the TodoList resource under the signer account move_to(account, tasks_holder); } /// Creates a new task in the todo list public entry fun create_task(account: &signer, content: String) acquires TodoList { // Get the signer address let signer_address = signer::address_of(account); // Ensure the account has initialized a todo list assert!(exists(signer_address), ENOT_INITIALIZED); // Get the TodoList resource let todo_list = borrow_global_mut(signer_address); // Increment task counter let counter = todo_list.task_counter + 1; // Create a new task let new_task = Task { task_id: counter, creator_addr: signer_address, content, completed: false }; // Add the new task to the tasks table todo_list.tasks.upsert(counter, new_task); // Update the task counter todo_list.task_counter = counter; // Emit a task created event event::emit(TaskCreated { task_id: counter, creator_addr: signer_address, content, completed: false }) } /// Marks a task as completed public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { // Get the signer address let signer_address = signer::address_of(account); // Ensure the account has initialized a todo list assert!(exists(signer_address), ENOT_INITIALIZED); // Get the TodoList resource let todo_list = borrow_global_mut(signer_address); // Ensure the task exists assert!(todo_list.tasks.contains(task_id), ETASK_DOESNT_EXIST); // Get the task record let task_record = todo_list.tasks.borrow_mut(task_id); // Ensure the task is not already completed assert!(task_record.completed == false, ETASK_IS_COMPLETED); // Mark the task as completed task_record.completed = true; } #[test(admin = @0x123)] public entry fun test_flow(admin: signer) acquires TodoList { // Create an admin account for testing account::create_account_for_test(signer::address_of(&admin)); // Initialize a todo list for the admin account create_list(&admin); // Create a task and verify it was added correctly create_task(&admin, string::utf8(b"Create e2e guide video for aptos devs.")); let todo_list = borrow_global(signer::address_of(&admin)); assert!(todo_list.task_counter == 1, 5); // Verify task details let task_record = todo_list.tasks.borrow(todo_list.task_counter); assert!(task_record.task_id == 1, 6); assert!(task_record.completed == false, 7); assert!(task_record.content == string::utf8(b"Create e2e guide video for aptos devs."), 8); assert!(task_record.creator_addr == signer::address_of(&admin), 9); // Complete the task and verify it was marked as completed complete_task(&admin, 1); let todo_list = borrow_global(signer::address_of(&admin)); let task_record = todo_list.tasks.borrow(1); assert!(task_record.task_id == 1, 10); assert!(task_record.completed == true, 11); assert!(task_record.content == string::utf8(b"Create e2e guide video for aptos devs."), 12); assert!(task_record.creator_addr == signer::address_of(&admin), 13); } #[test(admin = @0x123)] #[expected_failure(abort_code = ENOT_INITIALIZED)] public entry fun account_can_not_update_task(admin: signer) acquires TodoList { // Create an admin account for testing account::create_account_for_test(signer::address_of(&admin)); // Attempt to complete a task without creating a list first (should fail) complete_task(&admin, 2); } } ``` Now let's [set up the frontend](/build/guides/build-e2e-dapp/2-set-up-the-frontend) in chapter 2. # 2. Set up the frontend > Configure the React frontend with wallet adapter integration for interacting with your Aptos smart contract. This is the second chapter of the tutorial on [building an end-to-end dapp on Aptos](/build/guides/build-e2e-dapp) where you have already [created a smart contract](/build/guides/build-e2e-dapp/1-create-smart-contract) and are now setting up the frontend. ## Set up the frontend `create-aptos-dapp` has already created the frontend for us with a basic layout and Wallet implementation using the [aptos-wallet-adapter](/build/sdks/wallet-adapter#aptos-wallet-adapter) library. 1. Run: `npm run dev` At this point you should have your app running on [http://localhost:5173](http://localhost:5173), which displays the default template layout. 2. In the `frontend` directory, find all the frontend files. Let’s clean it up a bit. 3. Open the `App.tsx` file and update its content to be: ```typescript filename="App.tsx" import { Header } from "@/components/Header"; import { TopBanner } from "@/components/TopBanner"; function App() { return ( <>
My app goes here
); } export default App; ``` Once you save the changes, you should see that the app content has changed in the browser and displays `My app goes here`. ## Our dapp UI First we will build the dapp UI layout. We have two UI states for the app: - When an account hasn't created a list yet (on the left). - When an account has created a list and can now add tasks to it (on the right). ![dapp-ui](~/images/build-e2e-dapp-img-3.png) We now have a working client with a Wallet connect button and a wallet selector modal. Feel free to play with it and connect a wallet with it. Then learn how to [fetch data from chain](/build/guides/build-e2e-dapp/3-fetch-data-from-chain) in chapter 3. # 3. Fetch Data from Chain > Learn to retrieve on-chain data by checking for TodoList resources and implementing conditional UI logic. import { Steps } from '@astrojs/starlight/components'; In the third chapter of the tutorial on [building an end-to-end dapp on Aptos](/build/guides/build-e2e-dapp), you will be learning to fetch data from chain. Our UI logic relies on whether the connected account has created a todo list. If the account has created a todo list, our app should display that list; if not, the app should display a button offering the option to create a new list. For that, we first need to check if the connected account has a `TodoList` resource. In our smart contract, whenever someone creates a todo list we create and assign a `TodoList` resource to their account. To fetch data from chain, we can use the [Aptos TypeScript SDK](/build/sdks/ts-sdk). The SDK provides classes and functions for us to easily interact and query the Aptos chain. To get started: 1. **Stop the local server** if running and go to the `App.tsx` file. 2. **Import wallet from the wallet adapter React provider**: ```tsx filename="App.tsx" import { useWallet } from "@aptos-labs/wallet-adapter-react"; ``` 3. **Extract the account object from the wallet adapter**: ```tsx filename="App.tsx" function App ( const { account } = useWallet(); ) ``` The `account` object is `null` if there is no account connected; when an account is connected, the `account` object holds the account information, including the account address. 4. **Set up React hooks and state**: - Import `useEffect` and `useState` using: ```tsx filename="App.tsx" import { useState, useEffect } from "react"; ``` - Create local state to track whether the account has a todo list and add a `useEffect` hook to fetch data when the account changes: ```tsx filename="App.tsx" function App() { const [accountHasList, setAccountHasList] = useState(false); useEffect(() => { fetchList(); }, [account?.address]); } ``` 5. **Import required dependencies**: - Import `MODULE_ADDRESS` variable using: ```tsx filename="App.tsx" import { MODULE_ADDRESS } from "./constants"; ``` - Import `aptosClient` using: ```tsx filename="App.tsx" import { aptosClient } from "./utils/aptosClient"; ``` 6. **Create the `fetchList` function**: ```tsx filename="App.tsx" const fetchList = async () => { if (!account) return []; const moduleAddress = MODULE_ADDRESS; try { const todoListResource = await aptosClient().getAccountResource( { accountAddress:account?.address, resourceType:`${moduleAddress}::todolist::TodoList` } ); setAccountHasList(true); } catch (e: any) { setAccountHasList(false); } }; ``` 7. **Update the UI based on the `accountHasList` state**: - Import `Button` component using: ```tsx filename="App.tsx" import { Button } from "./components/ui/button"; ``` - Update the UI based on the `accountHasList` state: ```tsx filename="App.tsx" return ( <>
{!accountHasList && (
)}
); ``` The `aptosClient().getAccountResource()` expects an _account address_ that holds the resource we are looking for and a string representation of an on-chain _Move struct type_: - **account address** - is the current connected account (we are getting it from the wallet account object) - **Move struct type string syntax**: - The account address who holds the move module - The module name the resource lives in = `todolist` - The resource name = `TodoList` If the request succeeds and there is a resource for that account, we want to set our local state to `true`; otherwise, we would set it to `false`. We now have an **Add new list** button that appears only if the account doesn't have a list. 8. Start the local server with `npm run dev`. You should see the **Add new list** button. Next, let’s understand how to create a new list by [submitting data to chain](/build/guides/build-e2e-dapp/4-submit-data-to-chain) in chapter 4. # 4. Submit Data to Chain > Implement transaction submission to create new todo lists using wallet adapter's signAndSubmitTransaction function. In the fourth chapter of the tutorial on [building an end-to-end dapp on Aptos](/build/guides/build-e2e-dapp), you will be submitting data to the chain. So now we have an **Add new list** button that appears if the connected account hasn’t created a list yet. We still don't have a way for an account to create a list, so let’s add that functionality. 1. First, our wallet adapter provider has a `signAndSubmitTransaction` function; let’s extract it by updating the following: ```tsx filename="App.tsx" const { account, signAndSubmitTransaction } = useWallet(); ``` 2. Add an `onClick` event to the new list button: ```tsx filename="App.tsx" ``` 3. Update the import statement from `@aptos-labs/wallet-adapter-react` to also import the `InputTransactionData` type and ```tsx filename="App.tsx" import { useWallet, InputTransactionData, } from "@aptos-labs/wallet-adapter-react"; ``` 4. Add the `addNewList` function: ```tsx filename="App.tsx" const addNewList = async () => { if (!account) return []; const transaction:InputTransactionData = { data: { function:`${moduleAddress}::todolist::create_list`, functionArguments:[] } } try { // sign and submit transaction to chain const response = await signAndSubmitTransaction(transaction); // wait for transaction await aptosClient().waitForTransaction({transactionHash:response.hash}); setAccountHasList(true); } catch (error: any) { setAccountHasList(false); } }; ``` 5. Since our new function also uses `moduleAddress`, let’s get it out of the `fetchList` function scope to the global scope so it can be used globally. In our `fetchList` function, find the line: ```tsx filename="App.tsx" const moduleAddress = MODULE_ADDRESS; ``` And move it to outside of the main `App` function, so it can be globally accessed. **Let’s go over the `addNewList` function code.** First, we use the `account` property from our wallet provider to make sure there is an account connected to our app. Then we build our transaction data to be submitted to chain: ```tsx filename="App.tsx" const transaction:InputTransactionData = { data: { function:`${moduleAddress}::todolist::create_list`, functionArguments:[] } } ``` - `function`- is built from the module address, module name and the function name. - `functionArguments` - the arguments the function expects, in our case it doesn’t expect any arguments. Next, we submit the transaction payload and wait for its response. The response returned from the `signAndSubmitTransaction` function holds the transaction hash. Since it can take a bit for the transaction to be fully executed on chain and we also want to make sure it is executed successfully, we `waitForTransaction`. And only then we can set our local `accountHasList` state to `true`. 6. Before testing our app, let’s tweak our UI a bit and add a Spinner component to show up while we are waiting for the transaction. Add a local state to keep track whether a transaction is in progress: ```tsx filename="App.tsx" const [transactionInProgress, setTransactionInProgress] = useState(false); ``` 7. Update our `addNewList` function to update the local state: ```tsx filename="App.tsx" const addNewList = async () => { if (!account) return []; setTransactionInProgress(true); const transaction:InputTransactionData = { data: { function:`${moduleAddress}::todolist::create_list`, functionArguments:[] } } try { // sign and submit transaction to chain const response = await signAndSubmitTransaction(transaction); // wait for transaction await aptosClient().waitForTransaction({transactionHash:response.hash}); setAccountHasList(true); } catch (error: any) { setAccountHasList(false); } finally { setTransactionInProgress(false); } }; ``` 9. Update our UI with the following: ```tsx filename="App.tsx" return ( <> ... {!accountHasList && (
)} ); ``` Now you can head over to our app, and add a new list! Since you haven’t made the user interface able to handle cases where an account has created a list, you will do so next [handling tasks](/build/guides/build-e2e-dapp/5-handle-tasks) in chapter 5. # 5. Handle Tasks > Complete the dapp by implementing task management functionality to fetch existing tasks and add new ones. In the fifth and final chapter of the tutorial on [building an end-to-end dapp on Aptos](/build/guides/build-e2e-dapp), you will add functionality to the app so the user interface is able to handle cases where an account has created a list. We have covered how to [fetch data](/build/guides/build-e2e-dapp/3-fetch-data-from-chain) (an account’s todo list) from chain and how to [submit a transaction](/build/guides/build-e2e-dapp/4-submit-data-to-chain) (new todo list) to chain using Wallet. Let’s finish building our app by implementing fetch tasks and adding a task function. ## Fetch tasks 1. Create a local state `tasks` that will hold our tasks. It will be a state of a Task type (that has the same properties we set on our smart contract): ```typescript filename="App.tsx" type Task = { address: string; completed: boolean; content: string; task_id: string; }; function App() { const [tasks, setTasks] = useState([]); ... } ``` 2. Update our `fetchList` function to fetch the tasks in the account’s `TodoList` resource: ```typescript filename="App.tsx" const fetchList = async () => { if (!account) return []; try { const todoListResource = await aptosClient().getAccountResource({ accountAddress:account?.address, resourceType:`${moduleAddress}::todolist::TodoList` }); setAccountHasList(true); // tasks table handle const tableHandle = (todoListResource as any).tasks.handle; // tasks table counter const taskCounter = (todoListResource as any).task_counter; let tasks = []; let counter = 1; while (counter <= taskCounter) { const tableItem = { key_type: "u64", value_type: `${moduleAddress}::todolist::Task`, key: `${counter}`, }; const task = await aptosClient().getTableItem({handle:tableHandle, data:tableItem}); tasks.push(task); counter++; } // set tasks in local state setTasks(tasks); } catch (e: any) { setAccountHasList(false); } }; ``` **This part is a bit confusing, so stick with us!** Tasks are stored in a table (this is how we built our contract). To fetch a table item (i.e a task), we need that task's table handle. We also need the `task_counter` in that resource so we can loop over and fetch the task with the `task_id` that matches the `task_counter`. ```typescript filename="App.tsx" const tableHandle = (TodoListResource as any).data.tasks.handle; const taskCounter = (TodoListResource as any).data.task_counter; ``` Now that we have our tasks table handle and our `task_counter` variable, lets loop over the `taskCounter` . We define a `counter` and set it to 1 as the task\_counter / task\_id is never less than 1. We loop while the `counter` is less then the `taskCounter` and fetch the table item and push it to the tasks array: ```typescript filename="App.tsx" let tasks = []; let counter = 1; while (counter <= taskCounter) { const tableItem = { key_type: "u64", value_type: `${moduleAddress}::todolist::Task`, key: `${counter}`, }; const task = await aptosClient().getTableItem(tableHandle, tableItem); tasks.push(task); counter++; } ``` We build a `tableItem` object to fetch. If we take a look at our table structure from the contract: ```typescript filename="App.tsx" tasks: Table, ``` We see that it has a `key` type `u64` and a `value` of type `Task`. And whenever we create a new task, we assign the `key` to be the incremented task counter. ```move filename="todolist.move" // adds the new task into the tasks table table::upsert(&mut todo_list.tasks, counter, new_task); ``` So the object we built is: ```typescript filename="App.tsx" { key_type: "u64", value_type:`${moduleAddress}::todolist::Task`, key: `${taskCounter}`, } ``` Where `key_type` is the table `key` type, `key` is the key value we are looking for, and the `value_type` is the table `value` which is a `Task` struct. The Task struct uses the same format from our previous resource query: - The account address who holds that module = our profile account address - The module name the resource lives in = `todolist` - The struct name = `Task` The last thing we want to do is display the tasks we just fetched. 3. In our `App.tsx` file, update our UI with the following code: Import the `Input` using `import { Input } from "./components/ui/input";` ```tsx filename="App.tsx" {!accountHasList ? ( ) : (
{tasks && tasks.length > 0 && tasks.map((task) => (

{task.content}

))}
)} ``` That will display the **Add new list** button if account doesn’t have a list or instead the tasks if the account has a list. Go ahead and refresh your browser - see the magic! We haven’t added any tasks yet, so we dont see anything. Let’s add the option to add some tasks! ## Add task 1. Update our UI with an _add task_ input: ```tsx filename="App.tsx" {!accountHasList ? ( ... ) : (
// Add this!
...
... )} ``` We have added a text input to write the task and a button to add the task. 2. Create a new local state that holds the task content: ```tsx filename="App.tsx" function App() { ... const [newTask, setNewTask] = useState(""); ... } ``` 3. Find our `` component, add the `onChange` event to it, pass it our `onWriteTask` function and set the input value to be the `newTask` local state: ```tsx filename="App.tsx" setNewTask(e.target.value)} /> ``` Cool! Now we have a working flow that when the user types something on the Input component, a function will get fired and set our local state with that content. 4. Let’s also add a function that submits the typed task to chain! Find the ` ``` That adds an `onClickevent` that triggers an `onTaskAdded` function. When someones adds a new task we: - want to verify they are connected with a wallet. - build a transaction payload that would be submitted to chain. - submit it to chain using our wallet. - wait for the transaction. - update our UI with that new task (without the need to refresh the page). 5. Add an `onTaskAdded` function with: ```tsx filename="App.tsx" const onTaskAdded = async () => { // check for connected account if (!account) return; setTransactionInProgress(true); const transaction: InputTransactionData = { data: { function: `${moduleAddress}::todolist::create_task`, functionArguments: [newTask], }, }; // hold the latest task.task_id from our local state const latestId = tasks.length > 0 ? parseInt(tasks[tasks.length - 1].task_id) + 1 : 1; // build a newTaskToPush object into our local state const newTaskToPush: Task = { address: account.address.toString(), completed: false, content: newTask, task_id: latestId + "", }; try { // sign and submit transaction to chain const response = await signAndSubmitTransaction(transaction); // wait for transaction await aptosClient().waitForTransaction({ transactionHash: response.hash }); // Create a new array based on current state: let newTasks = [...tasks]; // Add item to the tasks array newTasks.push(newTaskToPush); // Set state setTasks(newTasks); // clear input text setNewTask(""); } catch (error: any) { console.log("error", error); } finally { setTransactionInProgress(false); } }; ``` **Let’s go over on what is happening.** First, note we use the `account` property from our wallet provider to make sure there is an account connected to our app. Then we build our transaction data to be submitted to chain: ```tsx filename="App.tsx" const transaction:InputTransactionData = { data:{ function:`${moduleAddress}::todolist::create_task`, functionArguments:[newTask] } } ``` - `function`- is built from the module address, module name and the function name. - `functionArguments` - the arguments the function expects, in our case the task content. Then, within our try/catch block, we use a wallet provider function to submit the transaction to chain and an SDK function to wait for that transaction. If all goes well, we want to find the current latest task ID so we can add it to our current tasks state array. We will also create a new task to push to the current tasks state array (so we can display the new task in our tasks list on the UI without the need to refresh the page). TRY IT! Type a new task in the text input, click **Add**, approve the transaction and see it being added to the tasks list. ## Mark task as completed Next, we can implement the `complete_task` function. We have the checkbox in our UI so users can mark a task as completed. 1. Update the `` component with an `onCheck` property that would call an `onCheckboxChange` function once it is checked: ```tsx filename="App.tsx" onCheckboxChange(event, task.task_id)} /> ``` 2. Create the `onCheckboxChange` function: ```tsx filename="App.tsx" const onCheckboxChange = async (event: React.ChangeEvent, taskId: string) => { if (!account) return; if (!event.target.checked) return; setTransactionInProgress(true); const transaction: InputTransactionData = { data: { function: `${moduleAddress}::todolist::complete_task`, functionArguments: [taskId], }, }; try { // sign and submit transaction to chain const response = await signAndSubmitTransaction(transaction); // wait for transaction await aptosClient().waitForTransaction({ transactionHash: response.hash }); setTasks((prevState) => { const newState = prevState.map((obj) => { // if task_id equals the checked taskId, update completed property if (obj.task_id === taskId) { return { ...obj, completed: true }; } // otherwise return object as is return obj; }); return newState; }); } catch (error: any) { console.log("error", error); } finally { setTransactionInProgress(false); } }; ``` Here we basically do the same thing we did when we created a new list or a new task. We make sure there is an account connected, set the transaction in progress state, build the transaction payload, submit the transaction, wait for it and update the task on the UI as completed. 3. Update the `Checkbox` component to be checked by default if a task has already marked as completed: ```tsx filename="App.tsx" ... actions={[
{task.completed ? ( ) : ( onCheckboxChange(event, task.task_id)} /> )}
, ]} ... ``` Try it! Check a task’s checkbox, approve the transaction and see the task marked as completed. You have now learned how to build a dapp on Aptos from end to end. Congratulations! Tell your friends. :-) # Exchange Integration Guide > Comprehensive guide for integrating Aptos and its assets into cryptocurrency exchanges with balance tracking and testing. import { Aside } from '@astrojs/starlight/components'; This describes how to integrate Aptos and Aptos assets into an exchange. It provides generic information for tracking balances, transferring assets, and testing the integration. ## Overview This document will guide you through the following tasks to integrate with Aptos: - Infrastructure - Address standards - Asset standards - Retrieving balances - Tracking balance changes - Transferring assets - Testing the integration ## Infrastructure It's suggested that you run your own [full node](/network/nodes/full-node) to interact with the Aptos blockchain. This will allow you to query the blockchain for the latest state and submit transactions. You can also use the [Indexer](/build/indexer) to query for on-chain data efficiently. ## Address Standards ### Addresses A single address can be represented in three ways. We recommend you show all leading zeros, and the `0x`. Here is an example of all three representations for the framework address `0x1`: - `0x00000000000000000000000000000001` - A full representation of 32-bytes in hex with a leading `0x`. This is preferred. - `0x1` - The short representation of the address with a leading `0x`. This is kept around for compatibility, but preferred with all leading 0s. - `00000000000000000000000000000001` - A full representation of 32-bytes in hex without a leading `0x`. This is kept around for compatibility, but preferred with leading 0x. For example SDKs will handle this parsing automatically, and we suggest you use the SDKs directly to handle it for you. ```typescript filename="example.ts" import { AccountAddress } from "@aptos-labs/ts-sdk"; const address = AccountAddress.from("0x1"); address.toStringLong(); // 0x00000000000000000000000000000001 ``` There is additionally, Aptos Name Service (ANS) for friendly .apt names. For more information about addresses and Aptos Names, see our page on [Accounts](/network/blockchain/accounts). ## Account Standards Accounts must exist prior to sending a transaction to the blockchain. This is done by creating an account resource, which can be created by simply calling `0x1::aptos_account::transfer` with a zero amount to the account you want to create. Optionally, `0x1::aptos_account::create_account` can be used to create an account with a zero balance. ```typescript filename="example.ts" import { Aptos, Ed25519Account, Ed25519PrivateKey } from "@aptos-labs/ts-sdk"; const aptos = new Aptos(); const account = new Ed25519Account({privateKey: new Ed25519PrivateKey("private key")}) const transaction = await aptos.transferCoinTransaction({sender: account.accountAddress, recipient: "receiver address", amount: 100000000}) const pendingTransaction = await aptos.transaction.signAndSubmitTransaction({signer: account, transaction}) const committedTransaction = await aptos.waitForTransaction({transactionHash: pendingTransaction.hash}); ``` ## Asset Standards Aptos provides two standards for fungible tokens, similar to ERC-20 tokens on Ethereum: - An earlier [Coin standard](/build/smart-contracts/aptos-coin) used by assets on Aptos. - A newer [Fungible Asset Standard](/build/smart-contracts/fungible-asset) which is more featured. Additionally, there is a migratory period for assets from Coin to Fungible Asset standards. We will call this from now on **migrated coins**. Migrated coins may have two forms, but either can be used interchangeably with Coin standards. This is important to note when querying balances, to use coin functions and not fungible asset functions. The FA standard can only deal with the FA form. ### Coin Standard (tl;dr) A **coin** has an associated contract that holds the on-chain struct that represents the coin. The coin is represented as a struct name e.g. `0x1::aptos_coin::AptosCoin` for `APT`. All coins are stored in an account resource called `0x1::coin::CoinStore`. Coins must be registered prior to using the `CoinStore`, but if using the proper functions e.g. `0x1::aptos_account::transfer` or `0x1::aptos_account::transfer_coins`, this will be done automatically. Coins can be _migrated_ to a fungible asset. In order to support a migrated asset, continue calling the coin functions as will be mentioned later. More info can be found at: [Coin Standard](/build/smart-contracts/aptos-coin) ### Fungible Asset Standard (tl;dr) A **fungible asset** has an associated metadata address that holds the metadata for the fungible asset. This is commonly called the fa metadata address. The asset is represented as an address e.g. `0xA` for `APT`. All fungible assets are stored in an `object`, which is called a `fungible asset store`. For exchanges, the most important store is `primary_fungible_store`, which is the default store for fungible assets. This is directly connected to an owner. From this point on in this guide, we will only talk about supporting `primary_fungible_store` for fungible assets. More info can be found at: [Fungible Asset Standard](/build/smart-contracts/fungible-asset) ## Retrieving Balances Retrieving current balances for assets are different for each standard. Integration is considered complete when it can handle both. Balances are always returned in their subunits. For example, `APT` is returned in `octas` (1e-8 APT). So, when an API returns a balance of `100000000`, this is `1 APT`. If it returns `100`, this is `0.000001 APT`. ### Coin (and migrated coins) Balances To retrieve the balance of a coin, or a coin that was migrated to a fungible asset, you can use the `0x1::coin::balance(account address)` view function. This will combine the coin and coin migrated to fungible asset balances. ```typescript filename="example.ts" import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); const coinType = "0x1::aptos_coin::AptosCoin"; const account = "0x00000000000000000000000000000001"; const [balanceStr] = await aptos.view<[string]>({ payload: { function: "0x1::coin::balance", typeArguments: [coinType], functionArguments: [account] } }); const balance = parseInt(balanceStr, 10); ``` A specific ledger version (transaction height) can be provided to get the balance at that point in time. The below example shows for ledger version `1,000,000`. ```typescript filename="example.ts" import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); const coinType = "0x1::aptos_coin::AptosCoin"; const account = "0x00000000000000000000000000000001"; const [balanceStr] = await aptos.view<[string]>({ payload: { function: "0x1::coin::balance", typeArguments: [coinType], functionArguments: [account], options: { ledgerVersion: 1_000_000 } } }); const balance = parseInt(balanceStr, 10); ``` ### Fungible Asset Balances To retrieve the balance of a fungible asset, you can use the `0x1::primary_fungible_store::balance<0x1::object::ObjectCore>(account address, fungible asset metadata address)` view function. Note, that this will not include the balance of coins if it's a migrated coin. ```typescript filename="example.ts" import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); const faMetadataAddress = "0xA"; const account = "0x00000000000000000000000000000001"; const [balanceStr] = await aptos.view<[string]>({ payload: { function: "0x1::primary_fungible_store::balance", typeArguments: ["0x1::object::ObjectCore"], functionArguments: [account, faMetadataAddress] } }); const balance = parseInt(balanceStr, 10); ``` A specific ledger version (transaction height) can be provided to get the balance at that point in time. The below example shows for ledger version `1,000,000`. ```typescript filename="example.ts" import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); const faMetadataAddress = "0xA"; const account = "0x00000000000000000000000000000001"; const [balanceStr] = await aptos.view<[string]>({ payload: { function: "0x1::primary_fungible_store::balance", typeArguments: ["0x1::object::ObjectCore"], functionArguments: [account, faMetadataAddress] }, options: { ledgerVersion: 1_000_000 } }); const balance = parseInt(balanceStr, 10); ``` Besides SDK, you can also directly use aptos node's [balance API endpoint](/build/apis/fullnode-rest-api#tag/accounts/GET/accounts/\{address}/balance/\{asset_type}) to get the balance of a migrated coin or fungible asset. ## Tracking Balance Changes Balance changes can be queried in one of two ways: 1. By watching for events that change the balance for each transaction. 2. By querying the indexer for indexed balance change events. In the past, it was able to use the `events` endpoint for an account to get the transactions that changed the balance. This is still possible, but will be deprecated in the future, and is not recommended for new integrations. ### Coin Balance Changes Coin balances are tracked as two items, write set changes, and events. Write set changes are end state of the coin balance, and events are the events that are emitted when a coin is withdrawn or deposited. Here is an [example of a coin transfer](https://explorer.aptoslabs.com/txn/1747361321?network=mainnet). The coin transfer can be tracked as an individual transaction [here](https://fullnode.mainnet.aptoslabs.com/v1/transactions/by_version/1747361321) from the REST API. We'll break it down into a few parts: 1. The general transaction details tell information about the transaction. The most important thing here is the transaction version is `1747361321`. This gives us total order of all transactions on the blockchain. Think of it like block height, but for transactions.
Transaction Details ```json { "version": "1747361321", "hash": "0x7c56ad56c7d02bb11887e535b9f1b221626d5b0d4cb5a1ffbadc358c1db515ea", "state_change_hash": "0xc901b5e9e0965201e8205977720d7dea8a3709ee0d818fd5ec752cac13eaf18a", "event_root_hash": "0x0077cb7df9db9ee7194c489db177fe9a325bcf3f1309ea99ed934085e5592041", "state_checkpoint_hash": null, "gas_used": "999", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "0xb531e918441ff0a37b49856e0f1b80c329146461582287cf9788964d25e31a68", } ```
2. The Write set `changes` are the end state of the transaction. It shows all resources that were modified by the transaction, and what it's final state was. In this case, we only care about coin store changes.
Coin Store Changes ```json "changes": [ { "address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "state_key_hash": "0xb2bfa7198457291a0e582b912be2bf8577feff08e352c9f16935a55ebd202dcc", "data": { "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": { "coin": { "value": "903837250" }, "deposit_events": { "counter": "10", "guid": { "id": { "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "creation_num": "2" } } }, "frozen": false, "withdraw_events": { "counter": "52485", "guid": { "id": { "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "creation_num": "3" } } } } }, "type": "write_resource" }, { "address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "state_key_hash": "0xa45b7cfe18cc0ef1d6588f0f548a6a6a260d5e6bbab174507ed40cd21b7bd082", "data": { "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": { "coin": { "value": "10" }, "deposit_events": { "counter": "1", "guid": { "id": { "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "creation_num": "2" } } }, "frozen": false, "withdraw_events": { "counter": "0", "guid": { "id": { "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "creation_num": "3" } } } } }, "type": "write_resource" }], ```
3. Events are the events that were emitted by the transaction. In this case, we only care about the `0x1::coin::Withdraw` and `0x1::coin::Deposit` events. The Coin withdraw event is emitted when coins are withdrawn from an account. The account's balance will decrease by that amount in the field `data.amount`. To determine the matching asset, you must match the `guid` in the `withdraw_events` to the `guid` in the `changes` section for a `CoinStore`. But if the `CoinStore` is not found in the `changes`, it means it got deleted, and a `CoinStoreDeleteEvent` must be present instead. Then you can match the `guid` with `deleted_withdraw_event_handle_creation_number` and `event_handle_creation_address`.
Coin Withdraw Event ```json { "events": [ { "guid": { "creation_number": "3", "account_address": "0xf8e25f6c8ce40a15107fb4b4d288ca03dd434d057392f2ccb5fde505a300a0bf" }, "sequence_number": "0", "type": "0x1::coin::WithdrawEvent", "data": { "amount": "100000" } }, ] } ```
Coin Store Deletion Event ```json { "events": [ { "guid": { "creation_number": "0", "account_address": "0x0" }, "sequence_number": "0", "type": "0x1::coin::CoinStoreDeletion", "data": { "coin_type": "0x1::aptos_coin::AptosCoin", "deleted_deposit_event_handle_creation_number": "2", "deleted_withdraw_event_handle_creation_number": "3", "event_handle_creation_address": "0xf8e25f6c8ce40a15107fb4b4d288ca03dd434d057392f2ccb5fde505a300a0bf" } } ] } ```
The Coin deposit event is emitted when coins are deposited into an account. The account's balance will increase by that amount in the field `data.amoount`. To determine the matching asset, you must match the `guid` in the `deposit_events` to the `guid` in the `changes` section for a `CoinStore`. Similarly, if the `CoinStore` is not found in the `changes`, it means it got deleted, and a `CoinStoreDeleteEvent` must be present instead. Then you can match the `guid` with `deleted_deposit_event_handle_creation_number` and `event_handle_creation_address`.
Coin Deposit Event ```json { "events": [{ "guid": { "creation_number": "2", "account_address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28" }, "sequence_number": "0", "type": "0x1::coin::DepositEvent", "data": { "amount": "10" } }] } ```
4. Gas usage only is tracked for APT. There is no direct event for tracking gas, but it can be calculated from the transaction. Using the `gas_used` field, and the `gas_unit_price` field, you can calculate the total gas used. In this case, the `gas_used` is `999` and the `gas_unit_price` is `100`, so the total gas deducted from the sender(`0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0`) is `999 * 100 = 99900 subunits` Remember that the subunits are used here. The value in the gas token `APT` is `0.00099900 APT`.
Gas Information ```json { "gas_used": "999", "max_gas_amount": "100000", "gas_unit_price": "100", "sender": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", } ```
5. Overall, you need both the events and the changes to determine the amount transferred of the account. The final balances will show in the changes alone. If you watch all of these events, you will be able to handle all possible transactions. Below is the full example of the transaction response.
Full Response ```json { "version": "1747361321", "hash": "0x7c56ad56c7d02bb11887e535b9f1b221626d5b0d4cb5a1ffbadc358c1db515ea", "state_change_hash": "0xc901b5e9e0965201e8205977720d7dea8a3709ee0d818fd5ec752cac13eaf18a", "event_root_hash": "0x0077cb7df9db9ee7194c489db177fe9a325bcf3f1309ea99ed934085e5592041", "state_checkpoint_hash": null, "gas_used": "999", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "0xb531e918441ff0a37b49856e0f1b80c329146461582287cf9788964d25e31a68", "changes": [ { "address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "state_key_hash": "0xb2bfa7198457291a0e582b912be2bf8577feff08e352c9f16935a55ebd202dcc", "data": { "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": { "coin": { "value": "903837250" }, "deposit_events": { "counter": "10", "guid": { "id": { "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "creation_num": "2" } } }, "frozen": false, "withdraw_events": { "counter": "52485", "guid": { "id": { "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "creation_num": "3" } } } } }, "type": "write_resource" }, { "address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "state_key_hash": "0xa3f2635d084b3cc01ae545c96ee15901549dab594363a46bf18e3d575c83102d", "data": { "type": "0x1::account::Account", "data": { "authentication_key": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "coin_register_events": { "counter": "1", "guid": { "id": { "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "creation_num": "0" } } }, "guid_creation_num": "4", "key_rotation_events": { "counter": "0", "guid": { "id": { "addr": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "creation_num": "1" } } }, "rotation_capability_offer": { "for": { "vec": [] } }, "sequence_number": "104628", "signer_capability_offer": { "for": { "vec": [] } } } }, "type": "write_resource" }, { "address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "state_key_hash": "0xa45b7cfe18cc0ef1d6588f0f548a6a6a260d5e6bbab174507ed40cd21b7bd082", "data": { "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": { "coin": { "value": "10" }, "deposit_events": { "counter": "1", "guid": { "id": { "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "creation_num": "2" } } }, "frozen": false, "withdraw_events": { "counter": "0", "guid": { "id": { "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "creation_num": "3" } } } } }, "type": "write_resource" }, { "address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "state_key_hash": "0xba04f5a13812778031f67322e9801be65a846224e46f1360a6008402fcd0e0e0", "data": { "type": "0x1::account::Account", "data": { "authentication_key": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "coin_register_events": { "counter": "1", "guid": { "id": { "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "creation_num": "0" } } }, "guid_creation_num": "4", "key_rotation_events": { "counter": "0", "guid": { "id": { "addr": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "creation_num": "1" } } }, "rotation_capability_offer": { "for": { "vec": [] } }, "sequence_number": "0", "signer_capability_offer": { "for": { "vec": [] } } } }, "type": "write_resource" }, { "state_key_hash": "0x6e4b28d40f98a106a65163530924c0dcb40c1349d3aa915d108b4d6cfc1ddb19", "handle": "0x1b854694ae746cdbd8d44186ca4929b2b337df21d1c74633be19b2710552fdca", "key": "0x0619dc29a0aac8fa146714058e8dd6d2d0f3bdf5f6331907bf91f3acd81e6935", "value": "0x9f9835f429758d010000000000000000", "data": null, "type": "write_table_item" } ], "sender": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0", "sequence_number": "104627", "max_gas_amount": "100000", "gas_unit_price": "100", "expiration_timestamp_secs": "1727826277", "payload": { "function": "0x1::aptos_account::transfer", "type_arguments": [], "arguments": [ "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28", "10" ], "type": "entry_function_payload" }, "signature": { "public_key": "0xfd448fada2bac29c5f3213277e001ca8851d5644578e79484b0426c41357a457", "signature": "0x40d8a6ee9150aa5736bee23ce1b1b851790bc0aa7e2485c0760d5808027040a2ef4170b88962867b045197576c5e89a4c640bf43586e6b3ead2b510b59acc20a", "type": "ed25519_signature" }, "events": [ { "guid": { "creation_number": "0", "account_address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28" }, "sequence_number": "0", "type": "0x1::account::CoinRegisterEvent", "data": { "type_info": { "account_address": "0x1", "module_name": "0x6170746f735f636f696e", "struct_name": "0x4170746f73436f696e" } } }, { "guid": { "creation_number": "3", "account_address": "0x559d4f690c683fca7c539237aa8dc4c6ec09886b7016bf66f2cdeffef55468f0" }, "sequence_number": "52484", "type": "0x1::coin::WithdrawEvent", "data": { "amount": "10" } }, { "guid": { "creation_number": "2", "account_address": "0x5d6233bb8d7f8bd714af196339e9fb3104c9d66f38867b2a0585c4f7b9d04d28" }, "sequence_number": "0", "type": "0x1::coin::DepositEvent", "data": { "amount": "10" } }, { "guid": { "creation_number": "0", "account_address": "0x0" }, "sequence_number": "0", "type": "0x1::transaction_fee::FeeStatement", "data": { "execution_gas_units": "6", "io_gas_units": "5", "storage_fee_octas": "98800", "storage_fee_refund_octas": "0", "total_charge_gas_units": "999" } } ], "timestamp": "1727825677775812", "type": "user_transaction" } ```
### Fungible Asset Balance Changes For fungible assets, the balance changes are tracked in the `primary_fungible_store`. The primary fungible store address is deterministic, and will always be tracked by the owner of the store. An example: [https://api.mainnet.aptoslabs.com/v1/transactions/by\\\_version/1750174030](https://api.mainnet.aptoslabs.com/v1/transactions/by\\_version/1750174030) There are a few steps when tracking fungible assets: 1. There will be two types of events for fungible assets. `0x1::fungible_asset::Deposit` and `0x1::fungible_asset::Withdraw`. `Withdraw` events are similar to the coin events, where the balance will decrease by the amount in the `data.amount` field. And similarly `Deposit` events will increase the balance by the amount in the `data.amount` field. Note that, I've omitted the sequence number, and GUID fields, as they do not apply to module events. Each event has a `store` field, which in this case is `0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a`. This is the address of the `FungibleStore` for the asset, where the balance is stored. Note this, for the next step.
Fungible Asset Events ```json { "events": [ { "type": "0x1::fungible_asset::Withdraw", "data": { "amount": "1", "store": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a" } }, { "type": "0x1::fungible_asset::Deposit", "data": { "amount": "1", "store": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a" } } ] } ```
2. Next, we take a look at the `0x1::fungible_asset::FungibleStore` changes. This will show the end state of the balance for the fungible asset. The balance is in the `data.balance` field. The `address` field will match the `store` field from the events. The identifier of the fungible asset, is the `metadata` field. It is the address of the `metadata` for the fungible asset. Additionally, to figure out the actual owner of the assets, you will need to look at the owner of the store. In this case, you will need the `0x1::object::ObjectCore`, where the `address` field matches the `store` field from the events. The `owner` field will show the asset owner's address. similar to the coin events, if the `ObjectCore` is not found in the `changes`, it means it got deleted, and a `FungibleStoreDeletion` event must be present instead. Then you can match the `store` fields between the `Withdraw`/`Deposit` events and the `FungibleStoreDeletion` event.
Fungible Asset Changes ```json { "changes":[ { "address": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a", "state_key_hash": "0x5b587931247dd5b43874ab29c3305c0ee7d26e7571fed3aea409375530e3a62c", "data": { "type": "0x1::fungible_asset::FungibleStore", "data": { "balance": "126691270443", "frozen": false, "metadata": { "inner": "0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12" } } }, "type": "write_resource" }, { "address": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a", "state_key_hash": "0x5b587931247dd5b43874ab29c3305c0ee7d26e7571fed3aea409375530e3a62c", "data": { "type": "0x1::object::ObjectCore", "data": { "allow_ungated_transfer": false, "guid_creation_num": "1125899906842628", "owner": "0xc67545d6f3d36ed01efc9b28cbfd0c1ae326d5d262dd077a29539bcee0edce9e", "transfer_events": { "counter": "0", "guid": { "id": { "addr": "0x8a9d57692a9d4deb1680eaf107b83c152436e10f7bb521143fa403fa95ef76a", "creation_num": "1125899906842624" } } } } }, "type": "write_resource" } ] } ```
FungibleStore Deletion Event ```json { "guid": { "creation_number": "0", "account_address": "0x0" }, "sequence_number": "0", "type": "0x1::fungible_asset::FungibleStoreDeletion", "data": { "metadata": "0x2ebb2ccac5e027a87fa0e2e5f656a3a4238d6a48d93ec9b610d570fc0aa0df12", "owner": "0xcf3906e2c9bc7e489c3b09d5ed5d90d8d403a68a50fe52932116b26e5878af26", "store": "0xa6ab8518e5f28a5f27247a895aa8b3de4a917209c6841b16187e8d64a67de242" } } ```
### Coins migrated to Fungible Asset Balance Changes For coins migrated to fungible assets, it is just simply tracking of the two above. A coin migrated to a fungible asset will have both the coin store changes and the primary fungible asset store changes. The amounts would need to be aggregated together, and otherwise, handled as a coin. The Fungible asset metadata address is the hash of the coin type and 0xA ``` address = sha3_256(0xA | coin_type | 0xFE) ``` Here is an example of a migrated coin with APT: [https://api.mainnet.aptoslabs.com/v1/transactions/by\\\_version/1642580695](https://api.mainnet.aptoslabs.com/v1/transactions/by\\_version/1642580695)
Full response ```json { "version": "1642580695", "hash": "0xe67ba1c4242d5c1de42eb8419558c4edf2318e185a3940a00f4150b519d06508", "state_change_hash": "0x07c5ec97afdf731c2778fccb37fe209369b28dcf6dcf11c3cf13b83c962f7f96", "event_root_hash": "0xad349cbea90bef601dfae9df822f5698af296951fc5f94359fcacc1e69e9fa3d", "state_checkpoint_hash": null, "gas_used": "545", "success": true, "vm_status": "Executed successfully", "accumulator_root_hash": "0x88e81bde70f32a86e46b288a917a44b2868a46973fac7fad16b5e780f48b0e67", "changes": [ { "address": "0xa", "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", "data": { "type": "0x1::coin::PairedCoinType", "data": { "type": { "account_address": "0x1", "module_name": "0x6170746f735f636f696e", "struct_name": "0x4170746f73436f696e" } } }, "type": "write_resource" }, { "address": "0xa", "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", "data": { "type": "0x1::coin::PairedFungibleAssetRefs", "data": { "burn_ref_opt": { "vec": [ { "metadata": { "inner": "0xa" } } ] }, "mint_ref_opt": { "vec": [ { "metadata": { "inner": "0xa" } } ] }, "transfer_ref_opt": { "vec": [ { "metadata": { "inner": "0xa" } } ] } } }, "type": "write_resource" }, { "address": "0xa", "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", "data": { "type": "0x1::fungible_asset::ConcurrentSupply", "data": { "current": { "max_value": "340282366920938463463374607431768211455", "value": "47948384" } } }, "type": "write_resource" }, { "address": "0xa", "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", "data": { "type": "0x1::fungible_asset::Metadata", "data": { "decimals": 8, "icon_uri": "", "name": "Aptos Coin", "project_uri": "", "symbol": "APT" } }, "type": "write_resource" }, { "address": "0xa", "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", "data": { "type": "0x1::object::ObjectCore", "data": { "allow_ungated_transfer": true, "guid_creation_num": "1125899906842625", "owner": "0x1", "transfer_events": { "counter": "0", "guid": { "id": { "addr": "0xa", "creation_num": "1125899906842624" } } } } }, "type": "write_resource" }, { "address": "0xa", "state_key_hash": "0x1db5441d8fa4229c5844f73fd66da4ad8176cb8793d8b3a7f6ca858722030043", "data": { "type": "0x1::primary_fungible_store::DeriveRefPod", "data": { "metadata_derive_ref": { "self": "0xa" } } }, "type": "write_resource" }, { "address": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188", "state_key_hash": "0x5ce89e323a23fb5570694dfb687d474d44563638c5ef774a2364d8347f5732b8", "data": { "type": "0x1::coin::MigrationFlag", "data": { "dummy_field": false } }, "type": "write_resource" }, { "address": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188", "state_key_hash": "0x5ce89e323a23fb5570694dfb687d474d44563638c5ef774a2364d8347f5732b8", "data": { "type": "0x1::fungible_asset::FungibleStore", "data": { "balance": "37949184", "frozen": false, "metadata": { "inner": "0xa" } } }, "type": "write_resource" }, { "address": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188", "state_key_hash": "0x5ce89e323a23fb5570694dfb687d474d44563638c5ef774a2364d8347f5732b8", "data": { "type": "0x1::object::ObjectCore", "data": { "allow_ungated_transfer": false, "guid_creation_num": "1125899906842625", "owner": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", "transfer_events": { "counter": "0", "guid": { "id": { "addr": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188", "creation_num": "1125899906842624" } } } } }, "type": "write_resource" }, { "address": "0x8a4613c356c21a45045e06dcc404bfee363aabd65a774d4d43defd71289239b2", "state_key_hash": "0x7c2d6e31d4ac5bbf93e19412437c0c288766b240674f71f457b9e3ef68be5003", "data": { "type": "0x1::fungible_asset::FungibleStore", "data": { "balance": "10000", "frozen": false, "metadata": { "inner": "0xa" } } }, "type": "write_resource" }, { "address": "0x8a4613c356c21a45045e06dcc404bfee363aabd65a774d4d43defd71289239b2", "state_key_hash": "0x7c2d6e31d4ac5bbf93e19412437c0c288766b240674f71f457b9e3ef68be5003", "data": { "type": "0x1::object::ObjectCore", "data": { "allow_ungated_transfer": false, "guid_creation_num": "1125899906842625", "owner": "0x5", "transfer_events": { "counter": "0", "guid": { "id": { "addr": "0x8a4613c356c21a45045e06dcc404bfee363aabd65a774d4d43defd71289239b2", "creation_num": "1125899906842624" } } } } }, "type": "write_resource" }, { "address": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", "state_key_hash": "0xfb7c1f2762da89f00a222f93bd771b478edb4361475c4a518178564be8616dd6", "data": { "type": "0x1::account::Account", "data": { "authentication_key": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", "coin_register_events": { "counter": "14", "guid": { "id": { "addr": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", "creation_num": "0" } } }, "guid_creation_num": "44", "key_rotation_events": { "counter": "0", "guid": { "id": { "addr": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", "creation_num": "1" } } }, "rotation_capability_offer": { "for": { "vec": [] } }, "sequence_number": "52", "signer_capability_offer": { "for": { "vec": [] } } } }, "type": "write_resource" } ], "sender": "0xa746e980ae21949a4f084db7403430f00bce3c9a1da4101ffcf0bf45ebd35e7e", "sequence_number": "51", "max_gas_amount": "817", "gas_unit_price": "100", "expiration_timestamp_secs": "1724196316", "payload": { "function": "0x1::primary_fungible_store::transfer", "type_arguments": [ "0x1::fungible_asset::Metadata" ], "arguments": [ { "inner": "0xa" }, "0x5", "10000" ], "type": "entry_function_payload" }, "signature": { "public_key": "0x330e75a102e37270b788caee8dd819e5badedd5fa17fe9f72017732e9bb98c60", "signature": "0xd4666df2887cf2d8192230e4a03d842ea75a86ffbc46a9a16a9baede6ff646c6b2bcafc524d3a4a7a66c223b5db576beb5cfefbd549620e69097c0a364c7a800", "type": "ed25519_signature" }, "events": [ { "guid": { "creation_number": "0", "account_address": "0x0" }, "sequence_number": "0", "type": "0x1::fungible_asset::Withdraw", "data": { "amount": "10000", "store": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188" } }, { "guid": { "creation_number": "0", "account_address": "0x0" }, "sequence_number": "0", "type": "0x1::fungible_asset::Deposit", "data": { "amount": "10000", "store": "0x8a4613c356c21a45045e06dcc404bfee363aabd65a774d4d43defd71289239b2" } }, { "guid": { "creation_number": "0", "account_address": "0x0" }, "sequence_number": "0", "type": "0x1::fungible_asset::Withdraw", "data": { "amount": "54500", "store": "0x7ed92ce166e251fc133f6b4d46a6b41307962e3b6864c2231110b3808648188" } }, { "guid": { "creation_number": "0", "account_address": "0x0" }, "sequence_number": "0", "type": "0x1::transaction_fee::FeeStatement", "data": { "execution_gas_units": "6", "io_gas_units": "7", "storage_fee_octas": "53240", "storage_fee_refund_octas": "0", "total_charge_gas_units": "545" } } ], "timestamp": "1724196287102837", "type": "user_transaction" } ```
## Transferring Assets ### Coin (or migrated coin) Transfers We suggest you use `0x1::aptos_account::transfer_coins(receiver address, amount)` for transferring coins. It will register the coin if it hasn't been registered yet, and create the associated account if it hasn't been created yet. This will continue to work with any coins that were migrated to a fungible asset, including APT. Coins can be transferred in the following ways: - [`0x1::aptos_account::transfer_coins(receiver address, amount)`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/aptos_account.move#L108-L112) - Transfer a coin to another account. - [`0x1::aptos_account::batch_transfer_coins(receiver addresses, amounts)`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/aptos_account.move#L93-L106) - Transfer a coin to multiple accounts. - [`0x1::aptos_account::transfer(receiver address, amount)`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/aptos_account.move#L74-L91) - Transfer specifically APT to another account. {/* TODO examples */} ### Fungible Asset Transfers We suggest you use `0x1::primary_fungible_store::transfer<0x1::object::ObjectCore>(receiver address, amount)` for transferring fungible assets. It will send the associated fungible asset, and create a primary store for the asset if it hasn't been created yet. {/* TODO examples */} { /* TODO Staking some other day */ } ## Testing In order to check that everything is working correctly, we've provided these checks. ### Balance Checks To test balance checks, you can check the balance for the account `0x5` for the asset `0x1::aptos_coin::AptosCoin`. The balance should show `0.002 APT`, where 0.001 APT is a coin, and 0.001 APT is a migrated coin (fungible asset). If your balance is not correct, see [Coin and Migrated Coin Balances](#coin-and-migrated-coins-balances) for more information. ### Balance Change / Transfer Checks #### Check Coin Transfer To test a transfer, create a transaction to transfer 0.001 APT to another account. The transaction should be successful, and the balance should be updated, where the balance is 0.001 APT smaller and minus the gas cost associated. #### Check Fungible Asset Transfer To test a transfer, you can fund an account with the fungible asset here [https://test-token-faucet.vercel.app/](https://test-token-faucet.vercel.app/) and then transfer the fungible asset to another account. The balance should be updated according to the change, and you should be able to track the mint on the website. ## Stablecoin Addresses | Token Name | Token Symbol | Token Address | Source of Address | | -------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | Tether USD | USDt | [0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b](https://explorer.aptoslabs.com/fungible_asset/0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b?network=mainnet) | [Aptos Foundation](https://aptosnetwork.com/currents/global-finance-moves-faster-on-aptos) | | USDC | USDC | [0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b](https://explorer.aptoslabs.com/fungible_asset/0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b?network=mainnet) | [Circle](https://developers.circle.com/stablecoins/usdc-on-main-networks) | | Ondo US Dollar Yield | USDY | [0xcfea864b32833f157f042618bd845145256b1bf4c0da34a7013b76e42daa53cc::usdy::USDY](https://explorer.aptoslabs.com/coin/0xcfea864b32833f157f042618bd845145256b1bf4c0da34a7013b76e42daa53cc::usdy::USDY?network=mainnet) | [Ondo Finance](https://ondo.finance/usdy) | ## FAQ ### What is the finality of a transaction? Aptos uses a BFT consensus algorithm, so transactions are finalized immediately after committing to the blockchain. ### What is the transaction fee on a transaction? Transaction fees are variable, but for most cases here are fixed. Check out [simulating transactions](/network/blockchain/gas-txn-fee#estimating-gas-consumption-via-simulation) to get an idea of the fee. # Your First Coin > Create, deploy, and mint your own cryptocurrency (MoonCoin) on Aptos using Move smart contracts and multiple SDK options. import { Aside, TabItem, Tabs } from '@astrojs/starlight/components'; This tutorial introduces how you can compile, deploy, and mint your own coin (as defined [here](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move)), named [MoonCoin](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/moon_coin). ## Step 1: Pick an SDK Install your preferred SDK from the below list: - [TypeScript SDK](/build/sdks/ts-sdk) - [Python SDK](/build/sdks/python-sdk) *** ## Step 2: Install the CLI [Install the precompiled binary for the Aptos CLI](/build/cli). *** ## Step 3: Run the example Clone the `aptos-ts-sdk` repo and build it: ```shellscript filename="Terminal" git clone https://github.com/aptos-labs/aptos-ts-sdk.git cd aptos-ts-sdk pnpm install pnpm build ``` Navigate to the TypeScript examples directory: ```shellscript filename="Terminal" cd examples/typescript/ ``` Install the necessary dependencies: ```shellscript filename="Terminal" pnpm install ``` Run the TypeScript [`your_coin`](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/your_coin.ts) example: ```shellscript filename="Terminal" pnpm run your_coin ``` The application will complete, printing: ```shellscript filename="Terminal" Bob's initial MoonCoin balance: 0. Alice mints herself 100 MoonCoin. Alice transfers 100 MoonCoin to Bob. Bob's updated MoonCoin balance: 100. ``` Clone the `aptos-core` repo: ```shellscript filename="Terminal" git clone https://github.com/aptos-labs/aptos-core ``` Navigate to the Python SDK directory: ```shellscript filename="Terminal" cd aptos-core/ecosystem/python/sdk ``` Install the necessary dependencies: ```shellscript filename="Terminal" curl -sSL https://install.python-poetry.org | python3 poetry install ``` Run the Python [`your_coin`](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/your_coin.py) example: ```shellscript filename="Terminal" poetry run python -m examples.your_coin ~/aptos-core/aptos-move/move-examples/moon_coin ``` ### Step 3.1: Build the package The example run will pause with the following output: ```shellscript filename="Terminal" === Addresses === Alice: 0x5e603a89cf690d7134cf2f24fdb16ba90c4f5686333721c12e835fb6c76bc7ba Bob: 0xc8421fa4a99153f955e50f1de2a6acff2f3fd0bb33aa17ba1f5b32b699f6c825 Update the package with Alice's address, compile, and press enter. ``` At this point, open another terminal and change directories to the MoonCoin package's directory: ```shellscript filename="Terminal" cd ~/aptos-core/aptos-move/move-examples/moon_coin ``` Next, build the package using the CLI: ```shellscript filename="Terminal" aptos move compile --named-addresses MoonCoin=0x5e603a89cf690d7134cf2f24fdb16ba90c4f5686333721c12e835fb6c76bc7ba --save-metadata ``` The `--named-addresses` is a list of address mappings that must be translated in order for the package to be compiled to be stored in Alice's account. Notice how `MoonCoin` is set to Alice's address printed above. Also `--save-metadata` is required to publish the package. *** ### Step 3.2: Completing the example Returning to the previous prompt, press ENTER as the package is now ready to be published. The application will complete, printing: ```shellscript filename="Terminal" Publishing MoonCoin package. Bob registers the newly created coin so he can receive it from Alice. Bob's initial MoonCoin balance: 0. Alice mints Bob some of the new coin. Bob's updated MoonCoin balance: 100. ``` *** ## Step 4: MoonCoin in depth ### Step 4.1: Building and publishing the MoonCoin package Move contracts are effectively a set of Move modules known as a package. When deploying or upgrading a new package, the compiler must be invoked with `--save-metadata` to publish the package. In the case of MoonCoin, the following output files are critical: - `build/Examples/package-metadata.bcs`: Contains the metadata associated with the package. - `build/Examples/bytecode_modules/moon_coin.mv`: Contains the bytecode for the `moon_coin.move` module. These are read by the example and published to the Aptos blockchain: In the TypeScript example, we use `aptos move build-publish-payload` command to compile and build the module. That command builds the `build` folder that contains the `package-metadata.bcs` and the bytecode for the `moon_coin.mv` module. The command also builds a publication transaction payload and stores it in a JSON output file that we can later read from to get the `metadataBytes` and `byteCode` to publish the contract to chain with. Compile the package: ```typescript filename="example.ts" export function compilePackage( packageDir: string, outputFile: string, namedAddresses: Array<{ name: string; address: AccountAddress }>, ) { const addressArg = namedAddresses .map(({ name, address }) => `${name}=${address}`) .join(" "); // Assume-yes automatically overwrites the previous compiled version, only do this if you are sure you want to overwrite the previous version. const compileCommand = `aptos move build-publish-payload --json-output-file ${outputFile} --package-dir ${packageDir} --named-addresses ${addressArg} --assume-yes`; execSync(compileCommand); } compilePackage("move/moonCoin", "move/moonCoin/moonCoin.json", [ { name: "MoonCoin", address: alice.accountAddress }, ]); ``` Publish the package to chain: ```typescript filename="example.ts" export function getPackageBytesToPublish(filePath: string) { // current working directory - the root folder of this repo const cwd = process.cwd(); // target directory - current working directory + filePath (filePath JSON file is generated with the previous, compilePackage, CLI command) const modulePath = path.join(cwd, filePath); const jsonData = JSON.parse(fs.readFileSync(modulePath, "utf8")); const metadataBytes = jsonData.args[0].value; const byteCode = jsonData.args[1].value; return { metadataBytes, byteCode }; } const { metadataBytes, byteCode } = getPackageBytesToPublish( "move/moonCoin/moonCoin.json", ); // Publish MoonCoin package to chain const transaction = await aptos.publishPackageTransaction({ account: alice.accountAddress, metadataBytes, moduleBytecode: byteCode, }); const pendingTransaction = await aptos.signAndSubmitTransaction({ signer: alice, transaction, }); await aptos.waitForTransaction({ transactionHash: pendingTransaction.hash }); ``` ```python filename="example.py" module_path = os.path.join( moon_coin_path, "build", "Examples", "bytecode_modules", "moon_coin.mv" ) with open(module_path, "rb") as f: module = f.read() metadata_path = os.path.join( moon_coin_path, "build", "Examples", "package-metadata.bcs" ) with open(metadata_path, "rb") as f: metadata = f.read() print("\nPublishing MoonCoin package.") package_publisher = PackagePublisher(rest_client) txn_hash = await package_publisher.publish_package(alice, metadata, [module]) await rest_client.wait_for_transaction(txn_hash) ``` *** ### Step 4.2: Understanding the MoonCoin module The MoonCoin module defines the `MoonCoin` struct, or the distinct type of coin type. In addition, it contains a function called `init_module`. The `init_module` function is called when the module is published. In this case, MoonCoin initializes the `MoonCoin` coin type as a `ManagedCoin`, which is maintained by the owner of the account. ```move filename="moon_coin.mv" module MoonCoin::moon_coin { struct MoonCoin {} fun init_module(sender: &signer) { aptos_framework::managed_coin::initialize( sender, b"Moon Coin", b"MOON", 6, false, ); } } ``` *** ### Step 4.3: Understanding coins Coins have several primitives: - **Minting**: Creating new coins. - **Burning**: Deleting coins. - **Freezing**: Preventing an account from storing coins in `CoinStore`. - **Registering**: Creating a `CoinStore` resource on an account for storing coins. - **Transferring**: Withdrawing and depositing coins into `CoinStore`. *** #### Step 4.3.1: Initializing a coin Once a coin type has been published to the Aptos blockchain, the entity that published that coin type can initialize it: ```move module 0x1::coin { public fun initialize( account: &signer, name: string::String, symbol: string::String, decimals: u8, monitor_supply: bool, ): (BurnCapability, FreezeCapability, MintCapability) { let account_addr = signer::address_of(account); assert!( coin_address() == account_addr, error::invalid_argument(ECOIN_INFO_ADDRESS_MISMATCH), ); assert!( !exists>(account_addr), error::already_exists(ECOIN_INFO_ALREADY_PUBLISHED), ); let coin_info = CoinInfo { name, symbol, decimals, supply: if (monitor_supply) { option::some(optional_aggregator::new(MAX_U128, false)) } else { option::none() }, }; move_to(account, coin_info); (BurnCapability{ }, FreezeCapability{ }, MintCapability{ }) } } ``` This ensures that this coin type has never been initialized before. Notice the check on lines 10 and 15 to ensure that the caller to `initialize` is the same one that actually published this module, and that there is no `CoinInfo` stored on their account. If both those conditions check, then a `CoinInfo` is stored and the caller obtains capabilities for burning, freezing, and minting. *** #### Step 4.3.2: Registering a coin To use a coin, an entity must register a `CoinStore` for it on their account: ```move public entry fun registerCoinType(account: &signer) { ``` MoonCoin uses `ManagedCoin` that provides an entry function wrapper: `managed_coin::register`. Here is an example script for registration: ```move script { fun register(account: &signer) { aptos_framework::managed_coin::register(account) } } ``` *** #### Step 4.3.3: Minting a coin Minting coins requires the mint capability that was produced during initialization. the function `mint` (see below) takes in that capability and an amount, and returns back a `Coin` struct containing that amount of coins. If the coin tracks supply, it will be updated. ```move module 0x1::coin { public fun mint( amount: u64, _cap: &MintCapability, ): Coin acquires CoinInfo { if (amount == 0) { return zero() }; let maybe_supply = &mut borrow_global_mut>(coin_address()).supply; if (option::is_some(maybe_supply)) { let supply = option::borrow_mut(maybe_supply); optional_aggregator::add(supply, (amount as u128)); }; Coin { value: amount } } } ``` `ManagedCoin` makes this easier by providing an entry function `managed_coin::mint`. *** #### Step 4.3.4: Transferring a coin Aptos provides several building blocks to support coin transfers: - `coin::deposit`: Allows any entity to deposit a coin into an account that has already called `coin::register`. - `coin::withdraw`: Allows any entity to extract a coin amount from their account. - `aptos_account::transfer_coins`: Transfer coins of specific CoinType to a receiver. ## Supporting documentation - [Aptos CLI](/build/cli) - [TypeScript SDK](/build/sdks/ts-sdk) - [Python SDK](/build/sdks/python-sdk) - [REST API specification](/rest-api) # Your First Fungible Asset > Build and deploy FACoin using the Fungible Asset Standard with built-in minting, transferring, and balance tracking capabilities. import { Aside, Steps } from '@astrojs/starlight/components'; This tutorial will teach you how to create your own Fungible Asset (FA) named [FACoin](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/fungible_asset/fa_coin). The [Fungible Asset Standard](/build/smart-contracts/fungible-asset) provides built-in support for minting, transferring, burning, and tracking account balances, so is useful for representing fungible assets. We will use the [TypeScript SDK](/build/sdks/ts-sdk) to deploy the contract and test it once it is on-chain. At a high level, the Fungible Asset Standard works through two main Objects: 1. A `Metadata` Object to store information about the fungible asset. 2. `FungibleStore`s for each account that has the fungible asset to track their current account balance. Sending a fungible asset to someone will cause them to receive a `FungibleStore` and update the balances in both accounts accordingly. ## Seeing Fungible Assets In Action Here we will modify, deploy, and test the example [FACoin](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/move/facoin/sources/fa_coin.move) contract to see how the Fungible Asset Standard works. If you are writing your own fungible asset contract, you may also want to reference the Stablecoin example contract [here](https://learn.aptoslabs.com/en/code-examples/stablecoin). 1. Install the [Aptos CLI](/build/cli) This will be used by the deploy scripts to publish the `FACoin` contract onchain. 2. Clone the TypeScript SDK repo. This repo contains the Fungible Asset example code. ```shellscript filename="Terminal" git clone https://github.com/aptos-labs/aptos-ts-sdk.git ``` 3. Navigate to the top-level of the cloned repository. ```shellscript filename="Terminal" cd aptos-ts-sdk ``` 4. Install the SDKs dependencies. ```shellscript filename="Terminal" pnpm install ``` 5. Build the TypeScript SDK. The example requires the local build of the TypeScript SDK. ```shellscript filename="Terminal" pnpm build ``` 6. Open fa\_coin.move in an editor. You can find `fa_coin.move` at `examples/typescript/move/facoin/sources/fa_coin.move`. This is the Move file which contains the bulk of the contract logic. We will dive into the details of how this contract works after showing you an example of it in action. 7. Edit the ASSET\_NAME to be the name of your new fungible asset. Ex. β€œTutorial Token”. The values you set here will show up in the deployed contract and when we are testing how things work. 8. Navigate to examples/typescript. ```shellscript filename="Terminal" cd examples/typescript ``` 9. Install the dependencies for the examples. ```shellscript filename="Terminal" pnpm install ``` 10. Run your\_fungible\_asset. ```shellscript filename="Terminal" pnpm run your_fungible_asset ``` You should see an output demonstrating how the fungible assets are created and transferred that looks like this: ```shellscript filename="Terminal" === Addresses === Alice: 0xca2f64c81ea9ab92c1d8686950aaef0fd5a050b7c7d3bd48f63739b9c0ff565f Bob: 0x66f8bbe6c76ce6eadf0b4544b8fd9bbf5f44b2f3905ee4edeab41e4b07cfc74c Charlie: 0xc25829d44511842b5f60bbf3f198c847fbad731a05e6125aa876f8f91e5d042b === Compiling FACoin package locally === In order to run compilation, you must have the `aptos` CLI installed. Running the compilation locally, in a real situation you may want to compile this ahead of time. aptos move build-publish-payload --json-output-file move/facoin/facoin.json --package-dir move/facoin --named-addresses FACoin=0xca2f64c81ea9ab92c1d8686950aaef0fd5a050b7c7d3bd48f63739b9c0ff565f --assume-yes Compiling, may take a little while to download git dependencies... UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-core.git INCLUDING DEPENDENCY AptosFramework INCLUDING DEPENDENCY AptosStdlib INCLUDING DEPENDENCY MoveStdlib BUILDING facoin ===Publishing FACoin package=== Transaction hash: 0xacd2af8920731caa0e9873c25d380ecc1f289193b407fea8f42313d28cf01df2 metadata address: 0xa0104ba8146b45bdaf1692c4e28aa7189cbb9ffb41523e025aab1a1600f4e331 All the balances in this example refer to balance in primary fungible stores of each account. Alice's initial FACoin balance: 0 Bob's initial FACoin balance: 0 Charlie's initial balance: 0 Alice mints Charlie 100 coins. Charlie's updated FACoin primary fungible store balance: 100 Alice freezes Bob's account. Alice as the admin forcefully transfers the newly minted coins of Charlie to Bob ignoring that Bob's account is frozen. Bob's updated FACoin balance: 100 Alice unfreezes Bob's account. Alice burns 50 coins from Bob. Bob's updated FACoin balance: 50 Bob transfers 10 coins to Alice as the owner. Alice's updated FACoin balance: 10 Bob's updated FACoin balance: 40 done. ``` ## Understanding the `fa_coin.move` Example Contract The full contract for FACoin.move can be found [here](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/move/facoin/sources/fa_coin.move). Let’s go step by step through how this contract is written. 1. Move.toml The Move.toml file allows Move to import dependencies, determine which addresses to use, and includes metadata about the contract. Regardless of which features you add to your fungible asset, your Move.toml will likely have similar fields to this at a minimum. In this case, we have the primary contract address `FACoin` that needs specifying at deploy time (indicated by leaving the value as β€œ\_”). It also includes the GitHub dependency to import the Fungible Asset standard from β€œAptosFramework”. ```toml filename="Move.toml" [package] name = "facoin" version = "1.0.0" authors = [] [addresses] FACoin = "_" [dependencies.AptosFramework] git = "https://github.com/aptos-labs/aptos-core.git" rev = "mainnet" subdir = "aptos-move/framework/aptos-framework" ``` 2. Imports The FACoin module uses several important modules: 1. `fungible_asset` contains the logic for granting permission to mint, transfer, burn, and create your FungibleAsset. 2. `object` allows for creating Aptos Objects. 3. `primary_fungible_store` contains the logic to track account balances for the new Fungible Asset. ```move filename="fa_coin.move" module FACoin::fa_coin { use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleAsset}; use aptos_framework::object::{Self, Object}; use aptos_framework::primary_fungible_store; use std::error; use std::signer; use std::string::utf8; use std::option; use std::string; //... } ``` These imports are defined in the `Move.toml` file as GitHub dependencies. 3. init\_module This function is called when the module is initially published in order to set up the proper permissions and Objects. For FACoin, this is used to initialize the asset’s `MetaData` Object (which contains things like the asset’s name and symbol), as well as getting the relevant ref’s for how our fungible asset will be used. The `ManagedFungibleAsset` standard helps keep track of which permissions this Module is allowed to use. ```move filename="fa_coin.move" fun init_module(admin: &signer) { let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL); primary_fungible_store::create_primary_store_enabled_fungible_asset( constructor_ref, option::none(), utf8(ASSET_NAME), /* name */ utf8(ASSET_SYMBOL), /* symbol */ 8, /* decimals */ utf8(b"http://example.com/favicon.ico"), /* icon */ utf8(b"http://example.com"), /* project */ ); let mint_ref = fungible_asset::generate_mint_ref(constructor_ref); let burn_ref = fungible_asset::generate_burn_ref(constructor_ref); let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref); let metadata_object_signer = object::generate_signer(constructor_ref); move_to( &metadata_object_signer, ManagedFungibleAsset { mint_ref, transfer_ref, burn_ref } ) } ``` 4. View Functions When creating your own fungible asset, it can be helpful to add view functions for any data that is needed later on. In this case, we wanted to see the name of the asset in order to report which asset was being traded in our example scenario. ```move filename="fa_coin.move" #[view] public fun get_metadata(): Object { let asset_address = object::create_object_address(&@FACoin, ASSET_SYMBOL); object::address_to_object(asset_address) } #[view] public fun get_name(): string::String { let metadata = get_metadata(); fungible_asset::name(metadata) } ``` 5. Entry Functions Every fungible asset has a similar interface (mint, transfer, burn, freeze, unfreeze, deposit, and withdraw). Here’s an example of a minimal mint function, which mints and transfers the funds to the proper recipient: ```move filename="fa_coin.move" public entry fun mint(admin: &signer, to: address, amount: u64) acquires ManagedFungibleAsset { let asset = get_metadata(); let managed_fungible_asset = authorized_borrow_refs(admin, asset); let to_wallet = primary_fungible_store::ensure_primary_store_exists(to, asset); let fa = fungible_asset::mint(&managed_fungible_asset.mint_ref, amount); fungible_asset::deposit_with_ref(&managed_fungible_asset.transfer_ref, to_wallet, fa); } ``` ## Summary If you want to build your own Fungible Asset, you can use [`fa_coin.move`](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/examples/typescript/move/facoin) as a starting point, or look to other code examples [here](https://learn.aptoslabs.com/en/code-examples). Regardless, the Fungible Asset Standard will help you mint, transfer, burn, and keep track of balances automatically for whichever fungible assets you want to represent on-chain. You can find the [Move reference for Fungible Assets](/move-reference/mainnet/aptos-framework/fungible_asset) for more details on the function signatures and implementation details. # Your First Move Module > Learn to compile, test, publish, and interact with Move smart contracts on Aptos blockchain from setup to deployment. import { Aside, Steps } from '@astrojs/starlight/components'; The Aptos blockchain allows developers to write Turing complete smart contracts (called β€œmodules”) with the secure-by-design Move language. Smart contracts enable users to send money with the blockchain, but also write arbitrary code, even games! It all starts with the Aptos CLI creating an account which will store the deployed (”published”) Move module. This tutorial will help you understand Move Modules by guiding you through setting up a minimal Aptos environment, then how to compile, test, publish and interact with Move modules on the Aptos Blockchain. You will learn how to: 1. Setup your environment, install the CLI 2. Create a devnet account and fund it 3. Compile and test a Move module 4. Publish (or "deploy") a Move module to the Aptos blockchain 5. Interact with the module 6. Keep building with Aptos (next steps) ## 1. Setup Changes to the blockchain are called β€œtransactions”, and they require an account to pay the network fee (”gas fee”). We will need to create an account with some APT to pay that fee and own the published contract. In order to do that, we will need to use the Aptos CLI. 1. Install the Aptos CLI [Install the Aptos CLI](/build/cli) (if you haven't already). 2. Open a new terminal Open a new terminal window or tab. 3. Verify the installation Run `aptos --version` to verify you have it installed. ```shellscript filename="Terminal" aptos --version ``` You should see a response like `aptos 4.6.1`. 4. Create a project folder Create a new folder for this tutorial by running: ```shellscript filename="Terminal" mkdir my-first-module ``` 5. Navigate to the project folder Run `cd my-first-module` to go into your new folder. 6. Initialize your account Run `aptos init` and press 'enter' for each step of setup to create a test account on `devnet`. You should see a success message like this: ```shellscript filename="Terminal" --- Aptos CLI is now set up for account 0x9ec1cfa30b885a5c9d595f32f3381ec16d208734913b587be9e210f60be9f9ba as profile default! { "Result": "Success" } ``` ## 2. (Optional) Explore What You Just Did On-Chain 1. Copy your account address Copy the address from the command line for your new account. The address looks like this `0x9ec1cfa30b885a5c9d595f32f3381ec16d208734913b587be9e210f60be9f9ba` and you can find it in the line: ```shellscript filename="Terminal" Aptos CLI is now set up for account 0x9ec1cfa30b885a5c9d595f32f3381ec16d208734913b587be9e210f60be9f9ba as profile default! ``` 2. Open the Aptos Explorer Go to the [Aptos Explorer](https://explorer.aptoslabs.com/?network=devnet). This is the primary way to quickly check what is happening on devnet, testnet, or mainnet. We will use it later on to view our deployed contracts. 3. Ensure you are on Devnet network. Look for β€œDevnet" in the top right corner, or switch networks by clicking the β€œMainnet” dropdown and selecting Devnet ![Switching to Devnet network in Aptos Explorer](~/images/screenshots/explorer_devnet.png) 4. Search for your account Paste your newly created address into the search bar. 5. View the search results Wait for the results to appear, then click the top result. 6. Check the transaction You should see your newly created account and a transaction with the faucet function, funding it with devnet tokens. ![Viewing Account in Aptos Explorer](~/images/screenshots/explorer_account.png) 7. Verify your balance Click the "Coins" tab to see that you have 1 APT of the Aptos Coin. This will allow you to publish and interact with smart contracts on the aptos devnet. ## 3. Writing and Compiling Your First Module Now that we have our environment set up and an account created, let's write and compile our first Move module. Unlike Ethereum where contracts exist independently, Move ties everything to accounts - both modules and their resources. Let's start with a simple example to understand the core concepts. ![Move Blockchain Diagram](~/images/screenshots/move_blockchain.png) This diagram illustrates the relationship between module ownership, token ownership, and the Move blockchain state. It helps visualize how modules and resources are tied to accounts, emphasizing the unique aspects of Move's design compared to other blockchain platforms. ### What is a Move Module? Move modules are similar to smart contracts in other blockchains, with some key differences: - **Resources:** Unlike Solidity where state is stored in contract variables, Move uses "resources" - special data types that can only exist in one place at a time and are always tied to an account - **Module-based**: Rather than deploying entire contracts as independent units like in Solidity, Move code is organized into reusable modules that can share and handle resources across boundaries. Modules are more like standard library packages that can be published together or separately, offering finer-grained control over code organization. - **Safety by design:** Move's type system and resource semantics help prevent common smart contract vulnerabilities ### Your First Move Module Before we start, go to your VSCode (or Cursor) and install the [Move On Aptos](/build/smart-contracts/move-vscode-extension) VSCode extension. 1. Open VSCode (or Cursor) and navigate to the Extensions tab. 2. Search for `Move On Aptos` published by `aptoslabs` and install the extension. This extension will help us with the syntax highlighting, auto-completion, and other features that will make our development experience easier. Our first module will be a simple message storage system that allows accounts to store and retrieve messages. Let's create a new move project within our `my-first-module` folder: 1. Initialize the project Initialize a new move project with `aptos move init --name my_first_module` This creates a project structure with a `sources` directory and a `Move.toml` file. 2. Create the module file Create a new file `sources/message.move` with our module code: ```move filename="message.move" module my_first_module::message { use std::string; use std::signer; struct MessageHolder has key, store, drop { message: string::String, } public entry fun set_message(account: &signer, message: string::String) acquires MessageHolder { let account_addr = signer::address_of(account); if (exists(account_addr)) { move_from(account_addr); }; move_to(account, MessageHolder { message }); } public fun get_message(account_addr: address): string::String acquires MessageHolder { assert!(exists(account_addr), 0); let message_holder = borrow_global(account_addr); message_holder.message } } ``` Let's break down this module: - We define a `MessageHolder` resource type that can store a string message - `set_message` allows an account to store a message - `get_message` allows anyone to retrieve a stored message - The `acquires` keyword indicates which resources the functions need access to (MessageHolder, in this case) - `move_to` and `move_from` handle the storage of resources under accounts 3. Compile the module Compile the Move module we just created with `aptos move compile --named-addresses my_first_module=default` You should see a message like this if it succeeded: ```shellscript filename="Terminal" ❯ aptos move compile --named-addresses my_first_module=default Compiling, may take a little while to download git dependencies... UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-framework.git INCLUDING DEPENDENCY AptosFramework INCLUDING DEPENDENCY AptosStdlib INCLUDING DEPENDENCY MoveStdlib BUILDING my_first_module { "Result": [ "9ec1cfa30b885a5c9d595f32f3381ec16d208734913b587be9e210f60be9f9ba::message" ] } ``` Great job! We are now ready to test and debug. ## 4. Testing and Debugging Testing and debugging are crucial parts of Move module development. Move has built-in support for unit testing and debug printing. 1. Add debug prints First, let's modify our message module to add some debug prints. Update your `sources/message.move`: ```move filename="message.move" module my_first_module::message { use std::string; use std::signer; use std::debug; // Add this for debug prints struct MessageHolder has key, store, drop { message: string::String, } public entry fun set_message(account: &signer, message: string::String) acquires MessageHolder { let account_addr = signer::address_of(account); debug::print(&message); // Print the message being set if (exists(account_addr)) { debug::print(&string::utf8(b"Updating existing message")); // Print debug info move_from(account_addr); } else { debug::print(&string::utf8(b"Creating new message")); // Print when creating new }; move_to(account, MessageHolder { message }); } public fun get_message(account_addr: address): string::String acquires MessageHolder { assert!(exists(account_addr), 0); let message_holder = borrow_global(account_addr); debug::print(&message_holder.message); // Print the retrieved message message_holder.message } } ``` 2. Create test file Create our tests: a new file `sources/message_tests.move` with: ```move filename="message_tests.move" #[test_only] module my_first_module::message_tests { use std::string; use std::signer; use my_first_module::message; #[test(sender= @my_first_module)] fun test_set_and_get_message(sender: &signer) { // Test setting a message message::set_message(sender, string::utf8(b"Hello World")); // Verify the message was set correctly let stored_message = message::get_message(signer::address_of(sender)); assert!(stored_message == string::utf8(b"Hello World"), 0) } #[test(sender=@my_first_module)] fun test_update_message(sender: &signer) { // Test setting a message message::set_message(sender, string::utf8(b"Hello World")); // Test updating the message message::set_message(sender, string::utf8(b"Hello Aptos")); // Verify the message was updated correctly let stored_message = message::get_message(signer::address_of(sender)); assert!(stored_message == string::utf8(b"Hello Aptos"), 0) } } ``` 3. Run the tests Now run the tests with `aptos move test --named-addresses my_first_module=default` You should see output if the tests pass: (See below for how to handle errors) ```shellscript filename="Terminal" INCLUDING DEPENDENCY AptosFramework INCLUDING DEPENDENCY AptosStdlib INCLUDING DEPENDENCY MoveStdlib BUILDING my_first_module Running Move unit tests [debug] "Hello World" [debug] "Creating new message" [debug] "Hello World" [ PASS ] 0x852a264419a80b27771f072b5cae8c8b358d4450e135e134e065247376a4357a::message_tests::test_set_and_get_message [debug] "Hello World" [debug] "Creating new message" [debug] "Hello Aptos" [debug] "Updating existing message" [debug] "Hello Aptos" [ PASS ] 0x852a264419a80b27771f072b5cae8c8b358d4450e135e134e065247376a4357a::message_tests::test_update_message Test result: OK. Total tests: 2; passed: 2; failed: 0 { "Result": "Success" } ``` **If you encounter errors while testing, here are some common issues and solutions:** - Make sure all module dependencies are properly imported - Check that your account address matches in the `-named-addresses` parameter - Verify that test functions have the `#[test]` attribute - Ensure string literals are properly encoded ## 5. Publishing Your Module After successfully compiling and testing your module, you can publish it to the Aptos blockchain. This process deploys your code so that it's accessible on-chain. 1. Publish the module Publish your module with `aptos move publish --named-addresses my_first_module=default` You'll see output showing the compilation process and then a prompt asking about gas fees: ```shellscript filename="Terminal" Compiling, may take a little while to download git dependencies... UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-framework.git INCLUDING DEPENDENCY AptosFramework INCLUDING DEPENDENCY AptosStdlib INCLUDING DEPENDENCY MoveStdlib BUILDING my_first_module package size 1271 bytes Do you want to submit a transaction for a range of [141300 - 211900] Octas at a gas unit price of 100 Octas? [yes/no] > ``` 2. Confirm the transaction Type `y` and press Enter to confirm the transaction. After confirmation, you'll receive a response showing the transaction details: ```shellscript filename="Terminal" { "Result": { "transaction_hash": "0x95fce7344b066abda10c07dbf1ffa83e0d9c7bd400e2b143682a6c8a5f179dc2", "gas_used": 1413, "gas_unit_price": 100, "sender": "9ec1cfa30b885a5c9d595f32f3381ec16d208734913b587be9e210f60be9f9ba", "sequence_number": 0, "success": true, "timestamp_us": 1735351260227638, "version": 273029731, "vm_status": "Executed successfully" } } ``` ### (Optional) Seeing Your Contract On-Chain After successful publication, you can verify your module is on-chain by following these steps: 1. Open the Explorer Go to the [Aptos Explorer](https://explorer.aptoslabs.com/?network=devnet) 2. Check the transaction Search for your account address. You should notice that there is a new transaction in your account, the `code::publish_package_txn` function. 3. View your balance Click the "Coins" tab to see that you now have less than 1 APT of the Aptos Coin. ![Explorer Coins View](~/images/screenshots/explorer_coins.png) You have spent a small amount on gas to deploy the contract so should have around `0.99855 APT` remaining. 4. Find your module Look under the "Modules" tab ![Exporer Modules View](~/images/screenshots/explorer_modules.png) 5. Verify the module You should see your "message" module listed ## 6. Interacting with Your Module Now that your module is published, you can interact with it through the Aptos CLI: 1. Set a message Set a message using the CLI: ```shellscript filename="Terminal" aptos move run --function-id 'default::message::set_message' --args 'string:Hello, Aptos!' ``` You'll see a gas fee prompt similar to what you saw during publishing. 2. Confirm the transaction After confirming with `y`, you should get a success response like: ```shellscript filename="Terminal" Transaction submitted: https://explorer.aptoslabs.com/txn/0x0c0b1e56a31d037280278327eb8fdfcc469a20213e5e65accf6e7c56af574449?network=devnet { "Result": { "transaction_hash": "0x0c0b1e56a31d037280278327eb8fdfcc469a20213e5e65accf6e7c56af574449", "gas_used": 445, "gas_unit_price": 100, "sender": "9ec1cfa30b885a5c9d595f32f3381ec16d208734913b587be9e210f60be9f9ba", "sequence_number": 1, "success": true, "timestamp_us": 1735351754495208, "version": 273137362, "vm_status": "Executed successfully" } } ``` 3. View your message View your stored message by checking under Resources on the Explorer. 4. Celebrate! We did it! ## Next Steps Congratulations! You've successfully: 1. Compiled your first Move module 2. Added tests to help debug 3. Published your module on-chain 4. Used your contract through the CLI Now your published Move module can be connected to just like an API via one of our [many Official SDKs](/build/sdks)! Here are some **suggested next steps to get a deeper understanding of Move modules**: 1. Try modifying the module to add a new feature. You can use the [Move Book](/build/smart-contracts/book) to build your understanding of writing Move modules. 2. To understand how Move works on-chain, you can learn about Move's [resource system](/network/blockchain/resources). 3. If you're building an application to interact with contracts or look up data from on-chain, learn how to use the SDKs [here](/build/sdks). 4. Join the [Aptos Discord](https://discord.gg/aptoslabs) to connect with other developers. ## Supporting documentation - [Account basics](/network/blockchain/accounts) - [TypeScript SDK](/build/sdks/ts-sdk) - [Python SDK](/build/sdks/python-sdk) - [REST API specification](/rest-api) # Your First Aptos Multisig (Python SDK) > Create and manage multisig accounts requiring multiple approvals for transactions using Python SDK with practical examples. import { Aside, Steps, TabItem, Tabs } from '@astrojs/starlight/components'; In this tutorial, you'll learn how to create and manage a multisig account that requires 2 out of 3 key holders to approve any transaction. You'll learn how to: 1. Set up a development environment for Aptos 2. Create multiple accounts to act as key holders 3. Configure a multisig account requiring 2-of-3 signatures 4. Fund accounts and verify balances 5. Create and execute multisig transactions Conceptually, a multisig (multi-signature) account works like a bank vault requiring multiple key holders to authorize access. In Aptos, this is implemented with digital signatures rather than physical keys, with each authorized signer providing their cryptographic approval. ## Setup First, let's prepare our development environment. We'll create an isolated workspace and install all necessary dependencies. 1. Open a terminal Open a new terminal window. 2. Verify Python installation Run this command to check your Python version: ```shellscript filename="Terminal" python3 --version ``` ```shellscript filename="Terminal" python --version ``` You should see something like "Python 3.7" or higher. 3. Create project directory Create a new folder for our project: ```shellscript filename="Terminal" mkdir my-first-multisig ``` 4. Navigate to project directory Move into this new folder: ```shellscript filename="Terminal" cd my-first-multisig ``` 5. Create virtual environment Set up an isolated Python environment: ```shellscript filename="Terminal" python3 -m venv venv ``` ```shellscript filename="Terminal" python -m venv venv ``` This command: - Creates an isolated Python environment - Installs a fresh Python instance - Keeps project dependencies separate from your system Python - Creates a `venv` folder (you can view but don't modify its contents!) 6. Activate virtual environment ```shellscript filename="Terminal" source venv/bin/activate ``` ```shellscript filename="Terminal" .\venv\Scripts\activate ``` This command: - Modifies your terminal's environment variables - Makes your terminal use the Python from `venv` instead of your system Python - You'll see `(venv)` appear at the start of your terminal line - To deactivate later, simply type `deactivate` 7. Install Aptos SDK Install the required SDK: ```shellscript filename="Terminal" pip install aptos-sdk ``` This command: - Downloads the Aptos SDK package from PyPI (Python Package Index) - Installs it inside your `venv` folder - Creates files in `venv/lib/python3.x/site-packages/aptos_sdk` - You can view these files by navigating to that directory ## Creating the Foundation Let's start building our multisig implementation. First, we'll set up our imports, main loop, and base configuration. 1. Create Python script Create an empty Python script file: ```shellscript filename="Terminal" touch multisig.py ``` ```shellscript filename="Terminal" echo "" > multisig.py ``` 2. Add base code Open `multisig.py` in your IDE (we recommend VSCode or JetBrains) and add the following code: ```python filename="multisig.py" # Copyright Β© Aptos Foundation # SPDX-License-Identifier: Apache-2.0 import asyncio import subprocess import time from aptos_sdk.account import Account, RotationProofChallenge from aptos_sdk.account_address import AccountAddress from aptos_sdk.async_client import FaucetClient, RestClient from aptos_sdk.authenticator import Authenticator, MultiEd25519Authenticator from aptos_sdk.bcs import Serializer from aptos_sdk.ed25519 import MultiPublicKey, MultiSignature from aptos_sdk.transactions import ( EntryFunction, RawTransaction, Script, ScriptArgument, SignedTransaction, TransactionArgument, TransactionPayload, ) from aptos_sdk.type_tag import StructTag, TypeTag # Network configuration - using devnet for testing. Check current urls at: # https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/common.py NODE_URL = "https://fullnode.devnet.aptoslabs.com/v1" FAUCET_URL = "https://faucet.devnet.aptoslabs.com" should_wait = True # "wait" is used to make the terminal more interactive, so it's easier to follow what is happening. def wait(): """Wait for user to press Enter before starting next section.""" if should_wait: input("\nPress Enter to continue...") # Now we define our main function which calls everything else. # We will add all future additions inside this function. async def main(should_wait_input=True): # This is just used for this tutorial. global should_wait should_wait = should_wait_input # Initialize our blockchain clients rest_client = RestClient(NODE_URL) faucet_client = FaucetClient(FAUCET_URL, rest_client) ############# Add additional code here ############### ###################################################### if __name__ == "__main__": asyncio.run(main()) ``` This code imports all the necessary modules from the Aptos SDK. The `aptos_sdk.account` module provides essential functionality for managing accounts and signatures, while `aptos_sdk.transactions` gives us the tools to create and submit blockchain transactions. ## Creating Our Key Holders Just like a bank vault needs designated key holders, our multisig needs authorized signers. Let's create the accounts for our key holders. 1. Create key holder accounts Add the following code after `############# Add additional code here ###############`: ```python filename="multisig.py" # Create three accounts to act as our key holders alice = Account.generate() bob = Account.generate() chad = Account.generate() ``` The `Account.generate()` function creates a new Aptos account with a fresh keypair. Each account will have its own private key (for signing) and public key (for verification). In our multisig setup, these accounts represent the key holders who will have authorization to sign transactions, similar to how each bank vault key holder would have their own unique physical key. 2. Add account information display Add this code below `chad = Account.generate()`: ```python filename="multisig.py" print("\n=== Account addresses ===") print(f"Alice: {alice.address()}") print(f"Bob: {bob.address()}") print(f"Chad: {chad.address()}") print("\n=== Authentication keys ===") print(f"Alice: {alice.auth_key()}") print(f"Bob: {bob.auth_key()}") print(f"Chad: {chad.auth_key()}") print("\n=== Public keys ===") print(f"Alice: {alice.public_key()}") print(f"Bob: {bob.public_key()}") print(f"Chad: {chad.public_key()}") wait() # Add additional code below this wait() ``` 3. Run the script Run our `multisig.py` from your terminal: ```shellscript filename="Terminal" python3 multisig.py ``` ```shellscript filename="Terminal" python multisig.py ``` You should see output showing the addresses, authentication keys, and public keys for each account. For example: ```shellscript filename="Terminal" === Account addresses === Alice: 0x5323a06f21b04af53fc57367b50d3bbb5675c655bc9bc62f33b5e083d5d06b8b Bob: 0x9f3e94fc92e0076336c122a576304c0b9fa8def13a98c469dce05e0836b9fe5b Chad: 0x1d0e7b790493dcf7bc7ce60bf4ccdcca1d38ce0d7f8dd26d2791a6d3ff6da708 === Authentication keys === Alice: 0x5323a06f21b04af53fc57367b50d3bbb5675c655bc9bc62f33b5e083d5d06b8b Bob: 0x9f3e94fc92e0076336c122a576304c0b9fa8def13a98c469dce05e0836b9fe5b Chad: 0x1d0e7b790493dcf7bc7ce60bf4ccdcca1d38ce0d7f8dd26d2791a6d3ff6da708 === Public keys === Alice: 0x730264a36d4ec90af2e28e1cf9c4d686440598317123469a7c827d4fcdf74715 Bob: 0xcf21e85337a313bdac33d068960a3e52d22ce0e6190e9acc03a1c9930e1eaf3e Chad: 0xa1a2aef8525eb20655387d3ed50b9a3ea1531ef6117f579d0da4bcf5a2e1f76d ``` ## Configuring the Multisig Vault Now that we have our key holders (Alice, Bob, and Chad), let's set up our multisig configuration. 1. Configure multisig account Add code to configure a 2-of-3 multisig account: ```python filename="multisig.py" # Configure a 2-of-3 multisig account threshold = 2 multisig_public_key = MultiPublicKey( [alice.public_key(), bob.public_key(), chad.public_key()], threshold ) multisig_address = AccountAddress.from_key(multisig_public_key) ``` The `threshold = 2` sets our requirement for two signatures out of three possible signers. The `MultiPublicKey` combines all three public keys into a single multisig configuration. 2. Display multisig information Print the multisig account information by adding this code below our newly defined `multisig_address`: ```python filename="multisig.py" print("\n=== 2-of-3 Multisig account ===") print(f"Account public key: {multisig_public_key}") print(f"Account address: {multisig_address}") wait() # Add additional code here ``` 3. Run the script Verify the output: ```shellscript filename="Terminal" python3 multisig.py ``` ```shellscript filename="Terminal" python multisig.py ``` You should see output showing your multisig account's public key type and its unique address on the Aptos blockchain. For example: ```shellscript filename="Terminal" === 2-of-3 Multisig account === Account public key: 2-of-3 Multi-Ed25519 public key Account address: 0x08cac3b7b7ce4fbc5b18bc039279d7854e4c898cbf82518ac2650b565ad4d364 ``` ## Funding Our Accounts Just like new bank accounts need initial deposits, our blockchain accounts need funds to operate. 1. Add funding code Add code to fund all accounts: ```python filename="multisig.py" print("\n=== Funding accounts ===") alice_start = 10_000_000 bob_start = 20_000_000 chad_start = 30_000_000 multisig_start = 40_000_000 # Fund all accounts concurrently alice_fund = faucet_client.fund_account(alice.address(), alice_start) bob_fund = faucet_client.fund_account(bob.address(), bob_start) chad_fund = faucet_client.fund_account(chad.address(), chad_start) multisig_fund = faucet_client.fund_account(multisig_address, multisig_start) await asyncio.gather(*[alice_fund, bob_fund, chad_fund, multisig_fund]) ``` The `fund_account()` function requests test tokens from the Aptos faucet to let us experiment without using real APT. We fund all accounts simultaneously rather than one at a time by first initializing them as `[name]_fund` and then awaiting the async function call that gathers them: `asyncio.gather()`. 2. Check balances Add code to check all balances and print them out: ```python filename="multisig.py" # Check all balances alice_balance = rest_client.account_balance(alice.address()) bob_balance = rest_client.account_balance(bob.address()) chad_balance = rest_client.account_balance(chad.address()) multisig_balance = rest_client.account_balance(multisig_address) [alice_balance, bob_balance, chad_balance, multisig_balance] = await asyncio.gather( *[alice_balance, bob_balance, chad_balance, multisig_balance] ) print(f"Alice's balance: {alice_balance}") print(f"Bob's balance: {bob_balance}") print(f"Chad's balance: {chad_balance}") print(f"Multisig balance: {multisig_balance}") wait() ``` The `account_balance()` function queries the blockchain for each account's current balance. Again, we use `asyncio.gather()` to make all these queries efficiently in parallel. 3. Run the script Verify funding success by running: ```shellscript filename="Terminal" python3 multisig.py ``` ```shellscript filename="Terminal" python multisig.py ``` The output should show each account with its respective balance. For example: ```shellscript filename="Terminal" === Funding accounts === Alice's balance: 10000000 Bob's balance: 20000000 Chad's balance: 30000000 Multisig balance: 40000000 ``` ## Creating Our First Multisig Transaction Now let's create a transaction that requires multiple signatures. We'll transfer 100 octas from the multisig account to Chad, similar to how a bank transfer would require two managers to approve a large withdrawal. 1. Create transfer transaction Create the transfer transaction by defining its parameters: ```python filename="multisig.py" # Create the transfer transaction entry_function = EntryFunction.natural( module="0x1::coin", function="transfer", ty_args=[TypeTag(StructTag.from_str("0x1::aptos_coin::AptosCoin"))], args=[ TransactionArgument(chad.address(), Serializer.struct), TransactionArgument(100, Serializer.u64), ], ) # Build the raw transaction chain_id = await rest_client.chain_id() raw_transaction = RawTransaction( sender=multisig_address, sequence_number=0, payload=TransactionPayload(entry_function), max_gas_amount=2000, gas_unit_price=100, expiration_timestamps_secs=int(time.time()) + 600, chain_id=chain_id, ) ``` The code above: - Uses `EntryFunction.natural()` to create a transfer of 100 octas (APT's smallest unit) to Chad's address - Sets up transaction parameters like gas limits and expiration time - Creates a raw transaction that still needs signatures before it can be submitted 2. Get signatures Get signatures from Alice and Bob: ```python filename="multisig.py" alice_signature = alice.sign(raw_transaction.keyed()) bob_signature = bob.sign(raw_transaction.keyed()) print("\n=== Individual signatures ===") print(f"Alice: {alice_signature}") print(f"Bob: {bob_signature}") wait() ``` The above code: - Has Alice sign the transaction with her private key - Has Bob sign the same transaction with his private key - Prints the signatures to verify they were created successfully 3. Run the script After you add the code for creating the transaction and getting signatures, run the script: ```shellscript filename="Terminal" python3 multisig.py ``` ```shellscript filename="Terminal" python multisig.py ``` You should see something like: ```shellscript filename="Terminal" === Individual signatures === Alice: 0x360e66c75b1ba787ec7b05998cbc14276d7fc0c006fb10c33d5cc3c4cc2ec4f53a8c0996b8e746fd6d86b09b4f8bb128cbf62d8b375f5b974faae040e889ac0d Bob: 0xdcfd1965e531deb79de9d8daf7f28f46023107ce4f11612ce76da33e808486a0a368b34563d4f89d6179a3957a266c1e8809691fddabba3c2a3d8be14d6f2f0c ``` This shows that both Alice and Bob have signed the transaction. Each signature is a unique hash that proves they authorized the transaction with their private keys. ## Submitting the Multisig Transaction Now we'll combine the signatures and submit the transaction. This is similar to gathering all the signed papers from bank managers and submitting them to process a large transfer. 1. Combine signatures Combine the signatures into a multisig authenticator: ```python filename="multisig.py" # Combine the signatures (map from signatory public key index to signature) sig_map = [(0, alice_signature), (1, bob_signature)] multisig_signature = MultiSignature(sig_map) # Create the authenticator with our multisig configuration authenticator = Authenticator( MultiEd25519Authenticator(multisig_public_key, multisig_signature) ) ``` The `sig_map` links each signer's public key to their signature, proving that both Alice and Bob have approved this transaction. The `MultiSignature` and `Authenticator` objects package these signatures in a format the blockchain can verify. 2. Submit transaction Create and submit the signed transaction: ```python filename="multisig.py" # Create and submit the signed transaction signed_transaction = SignedTransaction(raw_transaction, authenticator) print("\n=== Submitting transfer transaction ===") tx_hash = await rest_client.submit_bcs_transaction(signed_transaction) await rest_client.wait_for_transaction(tx_hash) print(f"Transaction hash: {tx_hash}") ``` The `SignedTransaction` combines the original transaction data with the authenticator proving both required signatures are present. We then submit this to the blockchain using `submit_bcs_transaction()` and wait for confirmation. 3. Check new balances Check the new account balances after transaction: ```python filename="multisig.py" print("\n=== New account balances ===") [alice_balance, bob_balance, chad_balance, multisig_balance] = await asyncio.gather( *[ rest_client.account_balance(alice.address()), rest_client.account_balance(bob.address()), rest_client.account_balance(chad.address()), rest_client.account_balance(multisig_address), ] ) print(f"Alice's balance: {alice_balance}") print(f"Bob's balance: {bob_balance}") print(f"Chad's balance: {chad_balance}") print(f"Multisig balance: {multisig_balance}") ``` 4. Run the script To see the transaction results, run: ```shellscript filename="Terminal" python3 multisig.py ``` ```shellscript filename="Terminal" python multisig.py ``` You should see something like: ```shellscript filename="Terminal" === Submitting transfer transaction === Transaction hash: 0x2f0b7fc8e69213f0c7e720e660f789b6e3d3564729a298f2b4f6794245833f2d === New account balances === Alice's balance: 10000000 Bob's balance: 20000000 Chad's balance: 30000100 # Increased by 100 octas Multisig balance: 39999200 # Decreased by 100 octas plus gas fees ``` Notice how: - Chad's balance increased by exactly 100 octas, but Alice and Bob's balances didn't change since they only signed - The multisig account paid for both the transfer amount and the gas fees ## Going Further: Advanced Features You've completed the basics of Aptos multisig - creating a "vault" (multisig account), adding "key holders" (signers), and making a simple transfer that requires multiple approvals. But just like modern banking, there's much more we can do: ### Vanity Addresses Like having a custom bank account number, Aptos lets you create "vanity" addresses that start with specific characters. Imagine being able to choose a memorable account number like "0xdd..." for your company "Digital Dynamics"! ### Account Rotation Banks let you update your security credentials without changing your account number. Similarly, Aptos multisig accounts can "rotate" their authentication keys while keeping the same address - perfect for updating security without disrupting existing payment setups. ### Governance & Smart Contracts Just as banks have complex approval systems for large corporate accounts, Aptos multisig can interact with smart contracts and governance systems. Imagine setting up automated rules like: - Required approvals based on transaction size - Time-locked transactions - Integration with DAO voting systems ## Next Steps 1. Review the [complete code example](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/multisig.py) which include all the Advanced Features (see above). 2. Learn about [multisig governance in this tutorial](/build/cli/working-with-move-contracts/multi-signature-tutorial). 3. Explore [account abstraction in Aptos](/network/blockchain/accounts). 4. Join the [Aptos Discord](https://discord.gg/aptoslabs) for developer support. # Your First Transaction > Create and submit your first transaction on Aptos blockchain - transfer coins between accounts with TypeScript and Python examples. import { Aside, Steps, TabItem, Tabs } from '@astrojs/starlight/components'; Transactions are the fundamental way to change data on the Aptos blockchain. Think of them like sending a package: you need to specify what you're sending, who it's going to, and then track it until delivery is confirmed. In blockchain terms, transactions allow you to transfer coins, call smart contract functions, and update on-chain state. This tutorial will guide you through creating and submitting your first transaction on the Aptos blockchain. You'll learn how to: 1. Set up your development environment 2. Create test accounts and fund them 3. Build a transaction to transfer coins 4. Simulate the transaction to estimate costs 5. Sign and submit the transaction 6. Verify the transaction was executed successfully ## 1. Setting Up Your Environment Before we can create transactions, we need to set up our development environment with the necessary tools and SDKs. 1. Install the TypeScript SDK Install the TypeScript SDK using your preferred package manager: ```shellscript filename="Terminal" npm install @aptos-labs/ts-sdk ``` ```shellscript filename="Terminal" yarn add @aptos-labs/ts-sdk ``` ```shellscript filename="Terminal" pnpm add @aptos-labs/ts-sdk ``` 2. Create a project directory Create a new directory for your project: ```shellscript filename="Terminal" mkdir my-first-transaction cd my-first-transaction ``` 3. Create a new file Create a new file named `transaction.ts`: ```shellscript filename="Terminal" touch transaction.ts ``` ```shellscript filename="Terminal" type nul > transaction.ts ``` Before we can create transactions, we need to set up our development environment with the necessary tools and SDKs. 1. Install the Python SDK Install the Python SDK using pip: ```shellscript filename="Terminal" pip install aptos-sdk ``` 2. Create a project directory Create a new directory for your project: ```shellscript filename="Terminal" mkdir my-first-transaction cd my-first-transaction ``` 3. Create a new file Create a new file named `transaction.py`: ```shellscript filename="Terminal" touch transaction.py ``` ```shellscript filename="Terminal" type nul > transaction.py ``` ## 2. Creating Test Accounts In blockchain, all transactions must come from an account. Let's create two test accounts: one to send coins (Alice) and one to receive them (Bob). 1. Set up the client First, we need to initialize the Aptos client that will connect to the blockchain. Open `transaction.ts` in your editor and add: ```typescript filename="transaction.ts" import { Account, Aptos, AptosConfig, Network, } from "@aptos-labs/ts-sdk"; async function main() { // Initialize the Aptos client const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); console.log("Connected to Aptos devnet"); // More code will go here } main().catch(console.error); ``` 2. Generate accounts Add this code inside your `main()` function to create two accounts - Alice (sender) and Bob (receiver): ```typescript filename="transaction.ts" // Generate two accounts const alice = Account.generate(); const bob = Account.generate(); console.log("=== Addresses ==="); console.log(`Alice's address: ${alice.accountAddress}`); console.log(`Bob's address: ${bob.accountAddress}`); ``` 3. Fund the accounts Add this code after generating the accounts to get test coins from the faucet: ```typescript filename="transaction.ts" // Fund the accounts with test APT from the devnet faucet console.log("\n=== Funding accounts ==="); await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 100_000_000, // 1 APT = 100,000,000 octas }); console.log("Accounts funded successfully"); // Check initial balances const aliceBalance = await aptos.getAccountAPTAmount({ accountAddress: alice.accountAddress, }); const bobBalance = await aptos.getAccountAPTAmount({ accountAddress: bob.accountAddress, }); console.log("\n=== Initial Balances ==="); console.log(`Alice: ${aliceBalance} octas`); console.log(`Bob: ${bobBalance} octas`); ``` 4. Run the code Let's test our code so far: ```shellscript filename="Terminal" npx ts-node transaction.ts ``` You should see output similar to: ``` Connected to Aptos devnet === Addresses === Alice's address: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa Bob's address: 0x7af2d6c93a2feafc9b69b5e8ad9d6b513b260f62f23f3a384a3a2e4a84694a9b === Funding accounts === Accounts funded successfully === Initial Balances === Alice: 100000000 octas Bob: 0 octas ``` In blockchain, all transactions must come from an account. Let's create two test accounts: one to send coins (Alice) and one to receive them (Bob). 1. Set up the client First, we need to initialize the Aptos client that will connect to the blockchain. Open `transaction.py` in your editor and add: ```python filename="transaction.py" import asyncio from aptos_sdk.account import Account from aptos_sdk.async_client import FaucetClient, RestClient from aptos_sdk.transactions import EntryFunction, TransactionPayload, TransactionArgument, RawTransaction from aptos_sdk.bcs import Serializer import time # Network configuration NODE_URL = "https://fullnode.devnet.aptoslabs.com/v1" FAUCET_URL = "https://faucet.devnet.aptoslabs.com" async def main(): # Initialize the clients rest_client = RestClient(NODE_URL) faucet_client = FaucetClient(FAUCET_URL, rest_client) print("Connected to Aptos devnet") # More code will go here if __name__ == "__main__": asyncio.run(main()) ``` 2. Generate accounts Add this code inside your `main()` function to create two accounts - Alice (sender) and Bob (receiver): ```python filename="transaction.py" # Generate two accounts alice = Account.generate() bob = Account.generate() print("=== Addresses ===") print(f"Alice's address: {alice.address()}") print(f"Bob's address: {bob.address()}") ``` 3. Fund the accounts Add this code after generating the accounts to get test coins from the faucet: ```python filename="transaction.py" # Fund the accounts with test APT from the devnet faucet print("\n=== Funding accounts ===") alice_amount = 100_000_000 # 1 APT = 100,000,000 octas bob_amount = 0 # Bob starts with 0 APT await faucet_client.fund_account(alice.address(), alice_amount) print("Account funded successfully") # Check initial balances alice_balance = await rest_client.account_balance(alice.address()) bob_balance = await rest_client.account_balance(bob.address()) print("\n=== Initial Balances ===") print(f"Alice: {alice_balance} octas") print(f"Bob: {bob_balance} octas") ``` 4. Run the code Let's test our code so far: ```shellscript filename="Terminal" python transaction.py ``` You should see output similar to: ``` Connected to Aptos devnet === Addresses === Alice's address: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa Bob's address: 0x7af2d6c93a2feafc9b69b5e8ad9d6b513b260f62f23f3a384a3a2e4a84694a9b === Funding accounts === Accounts funded successfully === Initial Balances === Alice: 100000000 octas Bob: 0 octas ``` ## 3. Building a Transaction Now that we have funded accounts, let's create a transaction to transfer coins from Alice to Bob. This is like filling out a form specifying what you want to send and to whom. 1. Understand transaction structure A transaction in Aptos has several key components: 1. **Sender**: The account initiating the transaction (Alice) 2. **Function**: The on-chain function to call (in this case, a coin transfer) 3. **Arguments**: Data needed by the function (recipient address and amount) 4. **Gas parameters**: Maximum gas amount and gas unit price 5. **Expiration time**: When the transaction is no longer valid if not executed 6. **Sequence number**: A counter that prevents replay attacks 2. Build the transaction Let's add code to build a transaction that transfers 1000 octas from Alice to Bob: Add this code to your `main()` function: ```typescript filename="transaction.ts" // 1. Build the transaction console.log("\n=== 1. Building the transaction ==="); const transaction = await aptos.transaction.build.simple({ sender: alice.accountAddress, data: { function: "0x1::aptos_account::transfer", functionArguments: [bob.accountAddress, 1000], // Transfer 1000 octas }, }); console.log("Transaction built successfully"); // Access transaction details from the raw transaction const rawTxn = transaction.rawTransaction; console.log(`Sender: ${rawTxn.sender}`); console.log(`Sequence Number: ${rawTxn.sequence_number}`); console.log(`Max Gas Amount: ${rawTxn.max_gas_amount}`); console.log(`Gas Unit Price: ${rawTxn.gas_unit_price}`); console.log(`Expiration Timestamp: ${new Date(Number(rawTxn.expiration_timestamp_secs) * 1000).toISOString()}`); ``` Now that we have funded accounts, let's create a transaction to transfer coins from Alice to Bob. This is like filling out a form specifying what you want to send and to whom. 1. Understand transaction structure A transaction in Aptos has several key components: 1. **Sender**: The account initiating the transaction (Alice) 2. **Function**: The on-chain function to call (in this case, a coin transfer) 3. **Arguments**: Data needed by the function (recipient address and amount) 4. **Gas parameters**: Maximum gas amount and gas unit price 5. **Expiration time**: When the transaction is no longer valid if not executed 6. **Sequence number**: A counter that prevents replay attacks 2. Build the transaction Add the following code to your `main()` function to build a transaction that transfers 1000 octas from Alice to Bob: ```python filename="transaction.py" # 1. Build the transaction print("\n=== 1. Building the transaction ===") # Create the entry function payload # This specifies which function to call and with what arguments entry_function = EntryFunction.natural( "0x1::aptos_account", # Module address and name "transfer", # Function name [], # Type arguments (empty for this function) [ # Function arguments with their serialization type TransactionArgument(bob.address(), Serializer.struct), # Recipient address TransactionArgument(1000, Serializer.u64), # Amount to transfer (1000 octas) ], ) # Get the chain ID for the transaction chain_id = await rest_client.chain_id() # Get the sender's current sequence number account_data = await rest_client.account(alice.address()) sequence_number = int(account_data["sequence_number"]) # Create the raw transaction with all required fields raw_transaction = RawTransaction( sender=alice.address(), # Sender's address sequence_number=sequence_number, # Sequence number to prevent replay attacks payload=TransactionPayload(entry_function), # The function to call max_gas_amount=2000, # Maximum gas units to use gas_unit_price=100, # Price per gas unit in octas expiration_timestamps_secs=int(time.time()) + 600, # Expires in 10 minutes chain_id=chain_id, # Chain ID to ensure correct network ) print("Transaction built successfully") print(f"Sender: {raw_transaction.sender}") print(f"Sequence Number: {raw_transaction.sequence_number}") print(f"Max Gas Amount: {raw_transaction.max_gas_amount}") print(f"Gas Unit Price: {raw_transaction.gas_unit_price}") print(f"Expiration Timestamp: {time.ctime(raw_transaction.expiration_timestamps_secs)}") ``` ## 4. Simulating the Transaction Before submitting a transaction, it's wise to simulate it first to estimate the gas cost. This is like checking shipping costs before sending a package. 1. Simulate the transaction Add this code after building the transaction: ```typescript filename="transaction.ts" // 2. Simulate the transaction console.log("\n=== 2. Simulating the transaction ==="); const [simulationResult] = await aptos.transaction.simulate.simple({ signerPublicKey: alice.publicKey, transaction, }); const gasUsed = parseInt(simulationResult.gas_used); const gasUnitPrice = parseInt(simulationResult.gas_unit_price); console.log(`Estimated gas units: ${gasUsed}`); console.log(`Estimated gas cost: ${gasUsed * gasUnitPrice} octas`); console.log(`Transaction would ${simulationResult.success ? "succeed" : "fail"}`); ``` Before submitting a transaction, it's wise to simulate it first to estimate the gas cost. This is like checking shipping costs before sending a package. 1. Simulate the transaction Add this code after building the transaction: ```python filename="transaction.py" # 2. Simulate the transaction print("\n=== 2. Simulating the transaction ===") # Create a BCS transaction for simulation # This doesn't actually submit the transaction to the blockchain simulation_transaction = await rest_client.create_bcs_transaction(alice, TransactionPayload(entry_function)) # Simulate the transaction to estimate gas costs and check for errors simulation_result = await rest_client.simulate_transaction(simulation_transaction, alice) # Extract and display the simulation results gas_used = int(simulation_result[0]['gas_used']) gas_unit_price = int(simulation_result[0]['gas_unit_price']) success = simulation_result[0]['success'] print(f"Estimated gas units: {gas_used}") print(f"Estimated gas cost: {gas_used * gas_unit_price} octas") print(f"Transaction would {'succeed' if success else 'fail'}") ``` ## 5. Signing and Submitting the Transaction Now that we've built and simulated the transaction, we need to sign it with Alice's private key and submit it to the blockchain. 1. Sign the transaction Signing proves that Alice authorized this transaction: Add this code after simulating the transaction: ```typescript filename="transaction.ts" // 3. Sign the transaction console.log("\n=== 3. Signing the transaction ==="); const senderAuthenticator = aptos.transaction.sign({ signer: alice, transaction, }); console.log("Transaction signed successfully"); ``` 2. Submit the transaction Add this code after signing the transaction to submit the signed transaction to the blockchain: ```typescript filename="transaction.ts" // 4. Submit the transaction console.log("\n=== 4. Submitting the transaction ==="); const pendingTransaction = await aptos.transaction.submit.simple({ transaction, senderAuthenticator, }); console.log(`Transaction submitted with hash: ${pendingTransaction.hash}`); ``` Now that we've built and simulated the transaction, we need to sign it with Alice's private key and submit it to the blockchain. 1. Sign the transaction Signing proves that Alice authorized this transaction: Add this code after simulating the transaction: ```python filename="transaction.py" # 3. Sign the transaction print("\n=== 3. Signing the transaction ===") # Sign the raw transaction with the sender's private key # This creates a cryptographic signature that proves the sender authorized this transaction signed_transaction = await rest_client.create_bcs_signed_transaction( alice, # Account with the private key TransactionPayload(entry_function), # The payload from our transaction sequence_number=sequence_number # Use the same sequence number as before ) print("Transaction signed successfully") # We can't easily extract the signature from the signed transaction object, # but we can confirm it was created ``` 2. Submit the transaction Add this code after signing the transaction to submit the signed transaction to the blockchain: ```python filename="transaction.py" # 4. Submit the transaction print("\n=== 4. Submitting the transaction ===") # Submit the signed transaction to the blockchain # This broadcasts the transaction to the network for processing tx_hash = await rest_client.submit_bcs_transaction(signed_transaction) print(f"Transaction submitted with hash: {tx_hash}") ``` ## 6. Waiting for Confirmation After submitting a transaction, we need to wait for it to be processed by the blockchain. This is like waiting for a package to be delivered. 1. Wait for transaction completion Add this code after submitting the transaction: ```typescript filename="transaction.ts" // 5. Wait for the transaction to complete console.log("\n=== 5. Waiting for transaction completion ==="); const txnResult = await aptos.waitForTransaction({ transactionHash: pendingTransaction.hash, }); console.log(`Transaction completed with status: ${txnResult.success ? "SUCCESS" : "FAILURE"}`); // If you want to see more details about the transaction: console.log(`VM Status: ${txnResult.vm_status}`); console.log(`Gas used: ${txnResult.gas_used}`); ``` 2. Verify the results Add this code after waiting for the transaction to check the balances and confirm the transfer worked: ```typescript filename="transaction.ts" // Check final balances const aliceFinalBalance = await aptos.getAccountAPTAmount({ accountAddress: alice.accountAddress, }); const bobFinalBalance = await aptos.getAccountAPTAmount({ accountAddress: bob.accountAddress, }); console.log("\n=== Final Balances ==="); console.log(`Alice: ${aliceFinalBalance} octas (spent ${aliceBalance - aliceFinalBalance} octas on transfer and gas)`); console.log(`Bob: ${bobFinalBalance} octas (received 1000 octas)`); ``` 3. Run the complete code ```shellscript filename="Terminal" npx ts-node transaction.ts ``` You should see output similar to: ``` Connected to Aptos devnet === Addresses === Alice's address: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa Bob's address: 0x7af2d6c93a2feafc9b69b5e8ad9d6b513b260f62f23f3a384a3a2e4a84694a9b === Funding accounts === Accounts funded successfully === Initial Balances === Alice: 100000000 octas Bob: 0 octas === 1. Building the transaction === Transaction built successfully Sender: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa Sequence Number: 0 Max Gas Amount: 20000 Gas Unit Price: 100 Expiration Timestamp: 2025-03-05T22:59:21.000Z === 2. Simulating the transaction === Estimated gas units: 146 Estimated gas cost: 14600 octas Transaction would succeed === 3. Signing the transaction === Transaction signed successfully === 4. Submitting the transaction === Transaction submitted with hash: 0x3a8a3e34a1c64ad9d7636a3a827b7ec3bb12d73825b36fa06d425c5a3b42cccc === 5. Waiting for transaction completion === Transaction completed with status: SUCCESS VM Status: Executed successfully Gas used: 146 === Final Balances === Alice: 99984400 octas (spent 15600 octas on transfer and gas) Bob: 1000 octas (received 1000 octas) ``` After submitting a transaction, we need to wait for it to be processed by the blockchain. This is like waiting for a package to be delivered. 1. Wait for transaction completion Add this code after submitting the transaction: ```python filename="transaction.py" # 5. Wait for the transaction to complete print("\n=== 5. Waiting for transaction completion ===") # Wait for the transaction to be processed by the blockchain # This polls the blockchain until the transaction is confirmed await rest_client.wait_for_transaction(tx_hash) # Get the transaction details to check its status transaction_details = await rest_client.transaction_by_hash(tx_hash) success = transaction_details["success"] vm_status = transaction_details["vm_status"] gas_used = transaction_details["gas_used"] print(f"Transaction completed with status: {'SUCCESS' if success else 'FAILURE'}") print(f"VM Status: {vm_status}") print(f"Gas used: {gas_used}") ``` 2. Verify the results Add this code after waiting for the transaction to check the balances and confirm the transfer worked: ```python filename="transaction.py" # Check final balances alice_final_balance = await rest_client.account_balance(alice.address()) bob_final_balance = await rest_client.account_balance(bob.address()) print("\n=== Final Balances ===") print(f"Alice: {alice_final_balance} octas (spent {alice_balance - alice_final_balance} octas on transfer and gas)") print(f"Bob: {bob_final_balance} octas (received 1000 octas)") ``` 3. Run the complete code ```shellscript filename="Terminal" python transaction.py ``` You should see output similar to: ``` Connected to Aptos devnet === Addresses === Alice's address: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa Bob's address: 0x7af2d6c93a2feafc9b69b5e8ad9d6b513b260f62f23f3a384a3a2e4a84694a9b === Funding accounts === Accounts funded successfully === Initial Balances === Alice: 100000000 octas Bob: 0 octas === 1. Building the transaction === Transaction built successfully Sender: 0x978c213990c4833df71548df7ce49d54c759d6b6d932de22b24d56060b7af2aa Sequence Number: 0 Max Gas Amount: 2000 Gas Unit Price: 100 Expiration Timestamp: Wed Mar 05 22:59:21 2025 === 2. Simulating the transaction === Estimated gas units: 146 Estimated gas cost: 14600 octas Transaction would succeed === 3. Signing the transaction === Transaction signed successfully === 4. Submitting the transaction === === 3. Signing the transaction === Transaction signed successfully === 4. Submitting the transaction === Transaction submitted with hash: 0x3a8a3e34a1c64ad9d7636a3a827b7ec3bb12d73825b36fa06d425c5a3b42cccc === 5. Waiting for transaction completion === Transaction completed with status: SUCCESS VM Status: Executed successfully Gas used: 146 === Final Balances === Alice: 99984400 octas (spent 15600 octas on transfer and gas) Bob: 1000 octas (received 1000 octas) ``` ## 7. (Optional) Explore Your Transaction On-Chain Now that you've successfully executed a transaction, you can explore it on the Aptos Explorer. This will help you understand how transactions are recorded on the blockchain and what information is publicly available. 1. Copy your transaction hash From your terminal output, copy the transaction hash that was printed after submission. It looks something like this: ``` Transaction submitted with hash: 0x3a8a3e34a1c64ad9d7636a3a827b7ec3bb12d73825b36fa06d425c5a3b42cccc ``` 2. Open the Aptos Explorer Go to the [Aptos Explorer](https://explorer.aptoslabs.com/?network=devnet). 3. Ensure you are on Devnet network Look for "Devnet" in the top right corner, or switch networks by clicking the dropdown and selecting Devnet. ![Switching to Devnet network in Aptos Explorer](~/images/screenshots/explorer_devnet.png) 4. Search for your transaction Paste your transaction hash into the search bar in the middle of the page. 5. View the transaction details Wait for the results to appear, then click on the transaction hash to view its details. You should see information about your transaction, including: - Status (should be "Success") - Timestamp - Gas used - Sender and recipient addresses - Amount transferred 6. Explore further From the transaction details page, you can: - Click on the sender or recipient addresses to view their account details - See the exact changes made to the blockchain state - View the transaction payload and arguments ## 8. Next Steps Congratulations! You've successfully created and executed your first transaction on the Aptos blockchain. Here are some suggestions for what to explore next: **Learn about more complex transactions**: - [Multi-Agent Signatures](/build/sdks/ts-sdk/building-transactions/multi-agent-transactions) - Transactions requiring multiple signers - [Sponsoring Transactions](/build/sdks/ts-sdk/building-transactions/sponsoring-transactions) - Having another account pay gas fees - [Batching Transactions](/build/sdks/ts-sdk/building-transactions/batching-transactions) - Sending multiple transactions efficiently **Explore smart contracts or account basics**: - [Your First Move Module](/build/guides/first-move-module) - Create your own smart contract - [Account Basics](/network/blockchain/accounts) [Join the Aptos Discord](https://discord.gg/aptoslabs) and share what you're building! ## Full Code Sample The complete code samples below combine all the snippets we've covered in this tutorial: ```typescript filename="transaction.ts" import { Account, Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; async function main() { // Initialize the Aptos client const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); console.log("Connected to Aptos devnet"); // More code will go here // Generate two accounts const alice = Account.generate(); const bob = Account.generate(); console.log("=== Addresses ==="); console.log(`Alice's address: ${alice.accountAddress}`); console.log(`Bob's address: ${bob.accountAddress}`); // Fund the accounts with test APT from the devnet faucet console.log("\n=== Funding accounts ==="); await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 100_000_000, // 1 APT = 100,000,000 octas }); await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: 0, // Bob starts with 0 APT }); console.log("Accounts funded successfully"); // Check initial balances const aliceBalance = await aptos.getAccountAPTAmount({ accountAddress: alice.accountAddress, }); const bobBalance = await aptos.getAccountAPTAmount({ accountAddress: bob.accountAddress, }); console.log("\n=== Initial Balances ==="); console.log(`Alice: ${aliceBalance} octas`); console.log(`Bob: ${bobBalance} octas`); // 1. Build the transaction console.log("\n=== 1. Building the transaction ==="); const transaction = await aptos.transaction.build.simple({ sender: alice.accountAddress, data: { function: "0x1::aptos_account::transfer", functionArguments: [bob.accountAddress, 1000], // Transfer 1000 octas }, }); console.log("Transaction built successfully"); // Use type assertion to bypass TypeScript's type checking const txnAny = transaction as any; console.log(`Sender: ${alice.accountAddress}`); // Use the known sender address console.log(`Sequence Number: ${txnAny.sequenceNumber || "N/A"}`); console.log(`Max Gas Amount: ${txnAny.maxGasAmount || "N/A"}`); console.log(`Gas Unit Price: ${txnAny.gasUnitPrice || "N/A"}`); console.log( `Expiration Timestamp: ${new Date( Number(txnAny.expirationTimestampSecs || 0) * 1000 ).toISOString()}` ); // 2. Simulate the transaction console.log("\n=== 2. Simulating the transaction ==="); const [simulationResult] = await aptos.transaction.simulate.simple({ signerPublicKey: alice.publicKey, transaction, }); console.log(`Estimated gas units: ${simulationResult.gas_used}`); console.log( `Estimated gas cost: ${ Number(simulationResult.gas_used) * Number(simulationResult.gas_unit_price) } octas` ); console.log( `Transaction would ${simulationResult.success ? "succeed" : "fail"}` ); // 3. Sign the transaction console.log("\n=== 3. Signing the transaction ==="); const senderAuthenticator = aptos.transaction.sign({ signer: alice, transaction, }); console.log("Transaction signed successfully"); // Use type assertion to bypass TypeScript's type checking const authAny = senderAuthenticator as any; const signatureStr = typeof authAny.signature === 'string' ? authAny.signature : JSON.stringify(authAny.signature || ''); console.log(`Signature: ${signatureStr.slice(0, 20)}...`); // 4. Submit the transaction console.log("\n=== 4. Submitting the transaction ==="); const pendingTransaction = await aptos.transaction.submit.simple({ transaction, senderAuthenticator, }); console.log(`Transaction submitted with hash: ${pendingTransaction.hash}`); // 5. Wait for the transaction to complete console.log("\n=== 5. Waiting for transaction completion ==="); const txnResult = await aptos.waitForTransaction({ transactionHash: pendingTransaction.hash, }); console.log( `Transaction completed with status: ${ txnResult.success ? "SUCCESS" : "FAILURE" }` ); // If you want to see more details about the transaction: console.log(`VM Status: ${txnResult.vm_status}`); console.log(`Gas used: ${txnResult.gas_used}`); // Check final balances const aliceFinalBalance = await aptos.getAccountAPTAmount({ accountAddress: alice.accountAddress, }); const bobFinalBalance = await aptos.getAccountAPTAmount({ accountAddress: bob.accountAddress, }); console.log("\n=== Final Balances ==="); console.log( `Alice: ${aliceFinalBalance} octas (spent ${ aliceBalance - aliceFinalBalance } octas on transfer and gas)` ); console.log(`Bob: ${bobFinalBalance} octas (received 1000 octas)`); } main().catch(console.error); ``` ```python filename="transaction.py" import asyncio from aptos_sdk.account import Account from aptos_sdk.async_client import FaucetClient, RestClient from aptos_sdk.transactions import EntryFunction, TransactionPayload, TransactionArgument, RawTransaction from aptos_sdk.bcs import Serializer import time # Network configuration NODE_URL = "https://fullnode.devnet.aptoslabs.com/v1" FAUCET_URL = "https://faucet.devnet.aptoslabs.com" async def main(): # Initialize the clients rest_client = RestClient(NODE_URL) faucet_client = FaucetClient(FAUCET_URL, rest_client) print("Connected to Aptos devnet") # Generate two accounts alice = Account.generate() bob = Account.generate() print("=== Addresses ===") print(f"Alice's address: {alice.address()}") print(f"Bob's address: {bob.address()}") # More code will go here # Fund the accounts with test APT from the devnet faucet print("\n=== Funding accounts ===") alice_amount = 100_000_000 # 1 APT = 100,000,000 octas bob_amount = 0 # Bob starts with 0 APT await faucet_client.fund_account(alice.address(), alice_amount) await faucet_client.fund_account(bob.address(), bob_amount) print("Accounts funded successfully") # Check initial balances alice_balance = await rest_client.account_balance(alice.address()) bob_balance = await rest_client.account_balance(bob.address()) print("\n=== Initial Balances ===") print(f"Alice: {alice_balance} octas") print(f"Bob: {bob_balance} octas") # 1. Build the transaction print("\n=== 1. Building the transaction ===") # Create the entry function payload # This specifies which function to call and with what arguments entry_function = EntryFunction.natural( "0x1::aptos_account", # Module address and name "transfer", # Function name [], # Type arguments (empty for this function) [ # Function arguments with their serialization type TransactionArgument(bob.address(), Serializer.struct), # Recipient address TransactionArgument(1000, Serializer.u64), # Amount to transfer (1000 octas) ], ) # Get the chain ID for the transaction chain_id = await rest_client.chain_id() # Get the sender's current sequence number account_data = await rest_client.account(alice.address()) sequence_number = int(account_data["sequence_number"]) # Create the raw transaction with all required fields raw_transaction = RawTransaction( sender=alice.address(), # Sender's address sequence_number=sequence_number, # Sequence number to prevent replay attacks payload=TransactionPayload(entry_function), # The function to call max_gas_amount=2000, # Maximum gas units to use gas_unit_price=100, # Price per gas unit in octas expiration_timestamps_secs=int(time.time()) + 600, # Expires in 10 minutes chain_id=chain_id, # Chain ID to ensure correct network ) print("Transaction built successfully") print(f"Sender: {raw_transaction.sender}") print(f"Sequence Number: {raw_transaction.sequence_number}") print(f"Max Gas Amount: {raw_transaction.max_gas_amount}") print(f"Gas Unit Price: {raw_transaction.gas_unit_price}") print(f"Expiration Timestamp: {time.ctime(raw_transaction.expiration_timestamps_secs)}") # 2. Simulate the transaction print("\n=== 2. Simulating the transaction ===") # Create a BCS transaction for simulation # This doesn't actually submit the transaction to the blockchain simulation_transaction = await rest_client.create_bcs_transaction(alice, TransactionPayload(entry_function)) # Simulate the transaction to estimate gas costs and check for errors simulation_result = await rest_client.simulate_transaction(simulation_transaction, alice) # Extract and display the simulation results gas_used = int(simulation_result[0]['gas_used']) gas_unit_price = int(simulation_result[0]['gas_unit_price']) success = simulation_result[0]['success'] print(f"Estimated gas units: {gas_used}") print(f"Estimated gas cost: {gas_used * gas_unit_price} octas") print(f"Transaction would {'succeed' if success else 'fail'}") # 3. Sign the transaction print("\n=== 3. Signing the transaction ===") # Sign the raw transaction with the sender's private key # This creates a cryptographic signature that proves the sender authorized this transaction signed_transaction = await rest_client.create_bcs_signed_transaction( alice, # Account with the private key TransactionPayload(entry_function), # The payload from our transaction sequence_number=sequence_number # Use the same sequence number as before ) print("Transaction signed successfully") # We can't easily extract the signature from the signed transaction object, # but we can confirm it was created # 4. Submit the transaction print("\n=== 4. Submitting the transaction ===") # Submit the signed transaction to the blockchain # This broadcasts the transaction to the network for processing tx_hash = await rest_client.submit_bcs_transaction(signed_transaction) print(f"Transaction submitted with hash: {tx_hash}") # 5. Wait for the transaction to complete print("\n=== 5. Waiting for transaction completion ===") # Wait for the transaction to be processed by the blockchain # This polls the blockchain until the transaction is confirmed await rest_client.wait_for_transaction(tx_hash) # Get the transaction details to check its status transaction_details = await rest_client.transaction_by_hash(tx_hash) success = transaction_details["success"] vm_status = transaction_details["vm_status"] gas_used = transaction_details["gas_used"] print(f"Transaction completed with status: {'SUCCESS' if success else 'FAILURE'}") print(f"VM Status: {vm_status}") print(f"Gas used: {gas_used}") # Check final balances alice_final_balance = await rest_client.account_balance(alice.address()) bob_final_balance = await rest_client.account_balance(bob.address()) print("\n=== Final Balances ===") print(f"Alice: {alice_final_balance} octas (spent {alice_balance - alice_final_balance} octas on transfer and gas)") print(f"Bob: {bob_final_balance} octas (received 1000 octas)") if __name__ == "__main__": asyncio.run(main()) ``` # Account Key Rotation > Advanced security feature for rotating account keys to maintain control while changing authentication credentials. import { Aside, Steps } from '@astrojs/starlight/components'; Aptos Move accounts have a public address, an authentication key, a public key, and a private key. The public address is permanent, always matching the account's initial authentication key, which is derived from the original private key. The Aptos account model facilitates the unique ability to rotate an account's private key. Since an account's address is the _initial_ authentication key, the ability to sign for an account can be transferred to another private key without changing its public address. In this guide, we show examples of how to rotate an account's authentication key using the CLI and few of the various Aptos SDKs. Here are the installation links for the SDKs we will cover in this example: - [Aptos CLI](/build/cli) - [Typescript SDK](/build/sdks/ts-sdk) - [Python SDK](/build/sdks/python-sdk) ## Proven and unproven key rotations The onchain logic for key rotation is implemented through two Move APIs: 1. [`account::rotate_authentication_key`], which executes a "proven" rotation. 2. [`account::rotate_authentication_key_call`], which executes an "unproven" rotation. ### Proven key rotations The [`account::rotate_authentication_key`] API requires a signed [`account::RotationProofChallenge`], which proves that the rotation operation is approved by the private key from both before _and_ after the operation. When the operation is successful, the [`account::OriginatingAddress`] table is updated with an entry that maps from the new authentication key to the corresponding account address. The [`account::OriginatingAddress`] table is a reverse lookup table that allows users to query an account address associated with a given authentication key, and only allows for one entry per authentication key. Hence the requirement of a signed [`account::RotationProofChallenge`] to ensure that a malicious actor does not rotate an account's authentication key to a key that is already in the table, as this attack would prevent lookup of the valid originating address that the holder of an authentication key had previously approved. Notably, the [`account::OriginatingAddress`] table is _only_ updated upon key rotation, not upon standard account generation. This means that with proven key rotations, a given private key can theoretically authenticate up to two accounts at the same time: 1. The account address derived from the private key during standard account generation, assuming the account has not undergone any key rotations. 2. A second arbitrary address, which has had its authentication key rotated to the given private key. However, it is considered best practice to only authenticate _one_ account with a given private key at a time, because whenever the [`account::OriginatingAddress`] table is updated, the underlying logic first checks if the rotating account's initial authentication key is in the table, and if so, verifies that the rotating account's address is the one mapped to in the table. This means that if an arbitrary account's authentication key is rotated to a given private key, the standard account whose address is originally derived from the private key will not be able to execute its first authentication key rotation while the associated authentication key is mapped to a second arbitrary account address in the [`account::OriginatingAddress`] table, because this operation would fail the check that the rotating account's address is the one mapped to in the table (since the table is only updated during rotation, not upon standard account generation). To prevent this issue and ensure best practices are followed, you can always run [`account::set_originating_address`] after generating a new account (see below CLI tutorial). ### Unproven key rotations Unlike [`account::rotate_authentication_key`], the [`account::rotate_authentication_key_call`] does _not_ require a signed [`account::RotationProofChallenge`]. This means that the operation is not proven in the sense the private key from _after_ the operation has approved the key rotation. Hence the [`account::OriginatingAddress`] table is _not_ updated for unproven key rotations, and there is thus no restriction on the number of accounts that can be authenticated with a given private key. Note that the `aptos` CLI does not currently support unproven key rotations. While it is technically possible to authenticate as many accounts as you want with a given authentication key via unproven key rotations, it is not considered best practice because this approach does not ensure one-to-one mapping. If you execute an unproven key rotation, it is suggested that you follow up with [`account::set_originating_address`] to ensure a one-to-one mapping from authentication key to account address for ease of originating address lookup (see below CLI tutorial). ## Key rotation with the Aptos CLI 1. Start a localnet Start a localnet: ```shellscript filename="Terminal" aptos node run-localnet ``` The localnet is ready when it prints out: ```shellscript filename="Terminal" Applying post startup steps... Setup is complete, you can now use the localnet! ``` 2. Generate a private key Create a private key corresponding to an authentication key, and thus initial account address, that starts with the vanity prefix `0xaaa`: ```shellscript filename="Terminal" aptos key generate \ --assume-yes \ --output-file private-key-a \ --vanity-prefix 0xaaa ```
Example output ```shellscript filename="Terminal" { "Result": { "Account Address:": "0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b", "PublicKey Path": "private-key-a.pub", "PrivateKey Path": "private-key-a" } } ```
This will generate two files: 1. A private key at `private-key-a`. 2. A public key at `private-key-a.pub`. Since there is not yet an account associated with the authentication key, the following command should fail with a corresponding message: ```shellscript filename="Terminal" aptos account lookup-address \ --public-key-file private-key-a.pub \ --url http://localhost:8080 ```
Example output ```shellscript filename="Terminal" { "Error": "API error: API error Error(AccountNotFound): Account not found by Address(0xaaafb224eb00e4d0ef520ce02038ede850893622562a4189b7f6e5d94454ccd9) and Ledger version(1206)" } ```
3. Initialize a profile Use the private key to initialize `test-profile-1` on the localnet: ```shellscript filename="Terminal" aptos init \ --assume-yes \ --network local \ --private-key-file private-key-a \ --profile test-profile-1 ```
Example output ```shellscript filename="Terminal" Configuring for profile test-profile-1 Configuring for network Local Using command line argument for private key Account 0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b doesn\'t exist, creating it and funding it with 100000000 Octas Account 0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b funded successfully --- Aptos CLI is now set up for account 0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b as profile test-profile-1! Run `aptos --help` for more information about commands { "Result": "Success" } ```
Note that you can always view the profile with: ```shellscript filename="Terminal" aptos config show-profiles --profile test-profile-1 ```
Example output ```shellscript filename="Terminal" { "Result": { "test-profile-1": { "has_private_key": true, "public_key": "0xe0bfe46f41c5be40e7a068e8dff4d6016126b226d947a39262f5b2347217a7e3", "account": "aaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b", "rest_url": "http://localhost:8080", "faucet_url": "http://localhost:8081" } } } ```
However, this will not show the private key, which is hidden by default. If you would like to show the private key: ```shellscript filename="Terminal" aptos config show-private-key --profile test-profile-1 ```
Example output ```shellscript filename="Terminal" { "Result": "0xcc3b0c38ad99e171263a7af930464313d1fb105d0d8e6a4b13f9b1140563a7dd" } ```
4. Look up address Now that there is an onchain account associated with the authentication key, you can look up the account address using `aptos account lookup-address`: ```shellscript filename="Terminal" aptos account lookup-address \ --public-key-file private-key-a.pub \ --url http://localhost:8080 ```
Example output ```shellscript filename="Terminal" { "Result": "aaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b" } ```
Store this address in a shell variable: ```shellscript filename="Terminal" ADDRESS_A=aaa... ``` 5. Look up authentication key Recall that the address of an account is identical to its authentication key when it is initially created, which means that the account address `aaa...` is identical to the account's authentication key: ```shellscript filename="Terminal" aptos move view \ --args address:$ADDRESS_A \ --function-id 0x1::account::get_authentication_key \ --url http://localhost:8080 ```
Example output ```shellscript filename="Terminal" { "Result": [ "0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b" ] } ```
Hence, store the authentication key in a shell variable: ```shellscript AUTH_KEY_A=$ADDRESS_A ``` Note, however, since the account has not yet had its authentication key rotated, there is no corresponding entry in the [`account::OriginatingAddress`] table: ```shellscript filename="Terminal" aptos move view \ --args address:$AUTH_KEY_A \ --function-id 0x1::account::originating_address \ --url http://localhost:8080 ```
Example output ```shellscript filename="Terminal" { "Result": [ { "vec": [] } ] } ```
6. Set originating address To ensure an entry in the [`account::OriginatingAddress`] table for this new account, you can run [`account::set_originating_address`]: ```shellscript filename="Terminal" aptos move run \ --assume-yes \ --function-id 0x1::account::set_originating_address \ --profile test-profile-1 ```
Example output ```shellscript filename="Terminal" { "Result": { "transaction_hash": "0x216992ef37a3c2f42aa9f8fed8f94d9f945a00e952dfe96b46123bb5c387ab6c", "gas_used": 444, "gas_unit_price": 100, "sender": "aaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b", "sequence_number": 0, "success": true, "timestamp_us": 1717809169531279, "version": 3268, "vm_status": "Executed successfully" } } ```
Then you should see an entry in the [`account::OriginatingAddress`] table: ```shellscript filename="Terminal" aptos move view \ --args address:$AUTH_KEY_A \ --function-id 0x1::account::originating_address \ --url http://localhost:8080 ```
Example output ```shellscript filename="Terminal" { "Result": [ { "vec": [ "0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b" ] } ] } ```
7. Rotate authentication key Generate a new private key: ```shellscript filename="Terminal" aptos key generate \ --assume-yes \ --output-file private-key-b \ --vanity-prefix 0xbbb ```
Example output ```shellscript filename="Terminal" { "Result": { "PrivateKey Path": "private-key-b", "Account Address:": "0xbbbdb12f4fa23b8fe8711b77f4ab7108f3a22077c5dfe787eed3d048a0b82734", "PublicKey Path": "private-key-b.pub" } } ```
Rotate the authentication key of the existing onchain account to the new private key: ```shellscript filename="Terminal" aptos account rotate-key \ --assume-yes \ --new-private-key-file private-key-b \ --profile test-profile-1 \ --save-to-profile test-profile-2 ```
Example output ```shellscript filename="Terminal" { "Result": { "message": "Saved new profile test-profile-2", "transaction": { "transaction_hash": "0xe561b710390511203511d15eee6f019a2e43ba32f8e3b7ce6bf812232e3bd27f", "gas_used": 449, "gas_unit_price": 100, "sender": "aaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51", "sequence_number": 1, "success": true, "timestamp_us": 1717810059696079, "version": 1109, "vm_status": "Executed successfully" } } } ```
8. Compare profiles Compare `test-profile-1` (which is now stale) with `test-profile-2` (which is current) noting that the public key has changed, but not the account address: ```shellscript filename="Terminal" aptos config show-profiles --profile test-profile-1 aptos config show-profiles --profile test-profile-2 ```
Example output ```shellscript filename="Terminal" { "Result": { "test-profile-1": { "has_private_key": true, "public_key": "0xb517173e68f4116e99c7fa1677058a6ee786a3b9e12447000db7fd85ab99dbdd", "account": "aaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51", "rest_url": "http://localhost:8080", "faucet_url": "http://localhost:8081" } } } { "Result": { "test-profile-2": { "has_private_key": true, "public_key": "0xadc3dd795fdd8569f59dc7b9900b38a5d7b95348b815de4eb5f00e2c2da07916", "account": "aaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51", "rest_url": "http://localhost:8080", "faucet_url": "http://localhost:8081" } } } ```
Lookup the new authentication key: ```shellscript filename="Terminal" aptos move view \ --args address:$ADDRESS_A \ --function-id 0x1::account::get_authentication_key \ --url http://localhost:8080 ```
Example output ```shellscript filename="Terminal" { "Result": [ "0xbbbdb12f4fa23b8fe8711b77f4ab7108f3a22077c5dfe787eed3d048a0b82734" ] } ```
Store the authentication key in a shell variable: ```shellscript filename="Terminal" AUTH_KEY_B=bbb... ``` 9. Look up originating addresses Check the originating address for the new authentication key: ```shellscript filename="Terminal" aptos move view \ --args address:$AUTH_KEY_B \ --function-id 0x1::account::originating_address \ --url http://localhost:8080 ```
Example output ```shellscript filename="Terminal" { "Result": [ { "vec": [ "0xaaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51" ] } ] } ```
Check the originating address for the old authentication key: ```shellscript filename="Terminal" aptos move view \ --args address:$AUTH_KEY_A \ --function-id 0x1::account::originating_address \ --url http://localhost:8080 ```
Example output ```shellscript filename="Terminal" { "Result": [ { "vec": [] } ] } ```
10. Attempt invalid rotation (same key) Attempt an invalid rotation where the current authentication key is identical to the new authentication key: ```shellscript filename="Terminal" aptos account rotate-key \ --assume-yes \ --new-private-key-file private-key-b \ --profile test-profile-2 \ --skip-saving-profile ```
Example output ```shellscript filename="Terminal" { "Error": "Invalid arguments: New public key cannot be the same as the current public key" } ```
11. Attempt invalid rotation (new key already mapped) Create another private key: ```shellscript filename="Terminal" aptos key generate \ --assume-yes \ --output-file private-key-c \ --vanity-prefix 0xccc ```
Example output ```shellscript filename="Terminal" { "Result": { "PrivateKey Path": "private-key-c", "PublicKey Path": "private-key-c.pub", "Account Address:": "0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958" } } ```
Initialize a new profile: ```shellscript filename="Terminal" aptos init \ --assume-yes \ --network local \ --private-key-file private-key-c \ --profile test-profile-3 ```
Example output ```shellscript filename="Terminal" Configuring for profile test-profile-3 Configuring for network Local Using command line argument for private key Account 0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958 doesn\'t exist, creating it and funding it with 100000000 Octas Account 0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958 funded successfully --- Aptos CLI is now set up for account 0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958 as profile test-profile-3! Run `aptos --help` for more information about commands { "Result": "Success" } ```
Attempt an invalid rotation where the new authentication key is already mapped: ```shellscript filename="Terminal" aptos account rotate-key \ --assume-yes \ --max-gas 100000 \ --new-private-key-file private-key-b \ --profile test-profile-3 \ --skip-saving-profile ``` (`--max-gas` is specified here to skip local simulation, which does not print out as descriptive of an error as the actual transaction.)
Example output ```shellscript filename="Terminal" { "Error": "API error: Unknown error Transaction committed on chain, but failed execution: Move abort in 0x1::account: ENEW_AUTH_KEY_ALREADY_MAPPED(0x10015): The new authentication key already has an entry in the `OriginatingAddress` table" } ```
12. Attempt invalid rotation (invalid originating address) Rotate the authentication key for account `0xaaa...` to use the authentication key for account `0xccc...`: ```shellscript filename="Terminal" aptos account rotate-key \ --assume-yes \ --new-private-key-file private-key-c \ --profile test-profile-2 \ --save-to-profile test-profile-4 ```
Example output ```shellscript filename="Terminal" { "Result": { "message": "Saved new profile test-profile-4", "transaction": { "transaction_hash": "0xa5dec792d82ef7471cdf82b9c957fc79b5815da770ad1dd9232ae4692e4f0895", "gas_used": 449, "gas_unit_price": 100, "sender": "aaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51", "sequence_number": 2, "success": true, "timestamp_us": 1717812312772580, "version": 5355, "vm_status": "Executed successfully" } } } ```
Then try to rotate the authentication key for account `0xccc...` for the first time, an operation that is blocked because an entry for the authentication key was established in the [`account::OriginatingAddress`] table during the last operation: ```shellscript filename="Terminal" aptos account rotate-key \ --assume-yes \ --max-gas 100000 \ --new-private-key-file private-key-b \ --profile test-profile-3 \ --skip-saving-profile ``` (`--max-gas` is specified here to skip local simulation, which does not print out as descriptive of an error as the actual transaction.)
Example output ```shellscript filename="Terminal" { "Error": "API error: Unknown error Transaction committed on chain, but failed execution: Move abort in 0x1::account: EINVALID_ORIGINATING_ADDRESS(0x6000d): Abort the transaction if the expected originating address is different from the originating address on-chain" } ```
13. Clean up Delete the test profiles: ```shell filename="Terminal" aptos config delete-profile --profile test-profile-1 aptos config delete-profile --profile test-profile-2 aptos config delete-profile --profile test-profile-3 aptos config delete-profile --profile test-profile-4 ``` Then you can stop the localnet and delete the private and public key files. 14. Rotate keys for a Ledger You can also perform authentication key rotation with a private key that is securely stored on a Ledger hardware wallet. For more information, see the [Ledger authentication key rotation guide](/build/cli/trying-things-on-chain/ledger#authentication-key-rotation).
## TypeScript key rotation example This program creates two accounts on devnet, Alice and Bob, funds them, then rotates the Alice's authentication key to that of Bob's. View the full example for this code [here](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/rotate_key.ts). The function to rotate is very simple: {/* TODO CODE EXAMPLE */} Commands to run the example script: ### Navigate to the typescript SDK directory, install dependencies and run rotate\_key.ts ```shellscript filename="Terminal" cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript-esm pnpm install && pnpm rotate_key ``` ### rotate\_key.ts output ```shell filename="Terminal" Account Address Auth Key Private Key Public Key ------------------------------------------------------------------------------------------------ Alice 0x213d...031013 '0x213d...031013' '0x00a4...b2887b' '0x859e...08d2a9' Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808 ...rotating... Alice 0x213d...031013 '0x1c06...ac3bb3' '0xf2be...9486aa' '0xbbc1...abb808' Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808 ``` ## Python key rotation example This program creates two accounts on devnet, Alice and Bob, funds them, then rotates the Alice's authentication key to that of Bob's. View the full example for this code [here](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/rotate_key.py). Here's the relevant code that rotates Alice's keys to Bob's: {/* TODO CODE EXAMPLE */} Commands to run the example script: ### Navigate to the python SDK directory, install dependencies and run rotate\_key.ts ```shellscript filename="Terminal" cd aptos-core/ecosystem/python/sdk poetry install && poetry run python -m examples.rotate-key ``` ### rotate\_key.py output ```shellscript filename="Terminal" Account Address Auth Key Private Key Public Key ------------------------------------------------------------------------------------------------ Alice 0x213d...031013 '0x213d...031013' '0x00a4...b2887b' '0x859e...08d2a9' Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808 ...rotating... Alice 0x213d...031013 '0x1c06...ac3bb3' '0xf2be...9486aa' '0xbbc1...abb808' Bob 0x1c06...ac3bb3 0x1c06...ac3bb3 0xf2be...9486aa 0xbbc1...abb808 ``` [`account::rotate_authentication_key`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L326 [`account::rotate_authentication_key_call`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L294 [`account::RotationProofChallenge`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L79 [`account::OriginatingAddress`]: https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70 [`account::set_originating_address`]: https://github.com/alnoki/aptos-core/blob/5ba4a8d1344b0bb6e22665525a96e787b9a44e55/aptos-move/framework/aptos-framework/sources/account.move#L528 # Manage Fungible Assets with Multisig > Enhance fungible asset security by combining multisig accounts with the Fungible Asset Standard for protected management. import { Aside } from '@astrojs/starlight/components'; This tutorial introduces a practical use case that combines Aptos framework multisig account with fungible asset standard to enhance the security margin of the management of fungible assets. Make sure you have understood module publishing and Aptos framework multisig account before moving on to the tutorial. If not, it is highly recommended to try out the following tutorials first: - [Your First Move Module](/build/guides/first-move-module) ## Step 1: Pick an SDK This tutorial was created for the [TypeScript SDK](/build/sdks/ts-sdk). Other developers are invited to add support for the [Python SDK](/build/sdks/python-sdk), [Rust SDK](/build/sdks/rust-sdk), [Go SDK](/build/sdks/go-sdk) and [Unity SDK](/build/sdks/unity-sdk)! ## Step 2: Publish the module To create a fungible asset controlled by an Aptos framework multisig account with all the administrative operations (mint, transfer, burn, freeze/unfreeze), a well-designed smart contract based on fungible asset standard is a prerequisite. The Aptos team provides an example code in `aptos-core` repo. Clone the `aptos-core` repo: ```shellscript filename="Terminal" git clone git@github.com:aptos-labs/aptos-core.git ~/aptos-core ``` Navigate to the `managed_fungible_asset` directory and then publish this package onto your `default` account using CLI: ```shellscript filename="Terminal" cd ~/aptos-core/aptos-move/move-examples/fungible_asset/managed_fungible_asset aptos move publish --named-addresses example_addr=default ``` Navigate to the `multisig_managed_coin` directory and then publish this package onto your `default` account using CLI too: ```shellscript filename="Terminal" cd ~/aptos-core/aptos-move/move-examples/fungible_asset/multisig_managed_coin aptos move publish --named-addresses example_addr=default ``` For this tutorial, `multisig_managed_coin` need to call functions defined in `managed_fungible_asset` on the same address. So both modules have to be published. ## Step 3: Start The example ```shellscript filename="Terminal" cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript ``` Run the `multisig_managed_coin` example: ```shellscript filename="Terminal" MODULE_ADDR=${DEFAULT_ACCOUNT_ADDRESS} pnpm run multisig_managed_coin ``` The example script should execute successfully without any errors. Then you are able to see what it did by searching the `owner1` and `owner2` addresses printed to the console on Aptos explorer. Let's follow the script to understand what it does: ### Generate single signer accounts First, we will generate three single signer accounts, owner1, owner2 and owner3 who will co-own an Aptos framework multisig account. {/* TODO Code snippet typescript title=Generate 3 single signers" /sdks/typescript/examples/typescript/multisig_managed_coin.ts section_1 */} ### Create an Aptos framework multisig account with a managed fungible asset Next, let owner1 call the `initialize()` function defined in `multisig_managed_coin.move`, which first create an Aptos framework multisig account owned by owner1 and add both owner2 and owner3 as owners. Also, it creates a fungible asset called "meme coin" with customized settings denoted in the argument list and make the multisig account the admin of the fungible asset. Also, each proposal needs at least 2 approvals to execute. {/* TODO Code snippet typescript title=Query the multisig account and then call the initialize function" /sdks/typescript/examples/typescript/multisig_managed_coin.ts section_2 */} ### Mint Then we mint 1000 and 2000 meme coin to owner2 and owner3, respectively. The proposed transaction is submitted by owner2 and gets an additional approval from owner3. {/* TODO Code snippet typescript title="Mint 1000 to owner2 and 2000 to owner3" /sdks/typescript/examples/typescript/multisig_managed_coin.ts section_3 */} ### Freeze After minting, the example shows how to freeze account owner1. The proposed transaction is again submitted by owner2 and approved by owner3 in addition. {/* TODO Code snippet typescript title=""Freeze owner1" /sdks/typescript/examples/typescript/multisig_managed_coin.ts section_4 */} ### Force transfer When owner1 is frozen, normal transfer cannot withdraw from or deposit to that account. But as the admin of "meme coin", the multisig account has the capability to do that. Next, Owner2 proposed a transaction to force transfer 1000 meme coins from owner3 to owner1. This time, owner1 approves it. {/* TODO Code snippet typescript title=""Force transfer 1000 meme coins from owner3 to owner1" /sdks/typescript/examples/typescript/multisig_managed_coin.ts section_5 */} ### Burn Finally, all the three owners have 1000 meme coins. Let's burn all the coins! Owner2 makes the proposal and owner1 approves it. {/* TODO Code snippet typescript title="Burn 1000 meme coins from all the three owners' accounts" /sdks/typescript/examples/typescript/multisig_managed_coin.ts section_6 */} ## Conclusion This tutorial shows an e2e flow of using Aptos framework multisig account to administrate fungible asset. Similarly, you can create your own module and leverage our powerful SDK to create the administration schema that fits your needs. # Use Oracles in Your Aptos Applications > Reference guide for integrating various oracle providers like Pyth Network to access off-chain data in smart contracts. import { Aside } from '@astrojs/starlight/components'; This reference guide presents various Oracles that you can use while building on Aptos. Oracles supply off-chain data to the blockchain, enabling smart contracts to access a diverse range of information. Currently, there are two oracles documented here in this guide: Chainlink and Pyth Network. ## Chainlink [Chainlink](https://chain.link/) is the industry-standard oracle platform bringing the capital markets on-chain and powering the majority of decentralized finance. The only all-in-one oracle platform for creating workflows across blockchains and legacy systems while embedding critical data, compliance, and privacy capabilities, which are then represented as a single line of code that runs in a verifiable decentralized runtime. Many of the world’s largest financial services institutions and DeFi protocols have adopted Chainlink’s standards and infrastructure. ### Chainlink Data Feeds By adopting the Chainlink standard for high-quality data, Aptos now has seamless access to the tamper-proof data needed to support the development of highly secure applications on the network. Chainlink Data Feeds provide a secure, reliable, and decentralized source of real-world data to power unique smart contract use cases across decentralized and traditional finance. 1. Data providers aggregate raw price data from a multitude of centralized and decentralized exchanges, accounting for time, volume, and outliers. 2. Independent Chainlink nodes fetch market price data from various data providers and combine the results into an aggregated value. 3. Multiple Chainlink nodes then aggregate their results together off-chain to generate an β€˜oracle report,’ which is made available to smart contracts. **Key Data Feeds developer tools** - [Data Feeds on Aptos](https://docs.chain.link/data-feeds/price-feeds/addresses?page=1\&testnetPage=1\&network=aptos) β€” view all relevant addresses and details to use Aptos with Data Feeds. - [Data Feeds official documentation](https://data.chain.link/feeds) β€” learn how to use Chainlink Data Feeds. ### Chainlink CCIP Chainlink Cross-Chain Interoperability Protocol (CCIP) is the standard for interoperability. CCIP enables developers to build secure cross-chain applications that can transfer tokens, send messages, and initiate actions across blockchains. Through the [Cross-Chain Token (CCT) standard](https://blog.chain.link/ccip-v-1-5-upgrade/), CCIP enables token developers to integrate new and existing tokens with CCIP in a self-serve manner within minutes, without requiring vendor lock-in, hard-coded functions, or external dependencies that may limit future optionality. CCTs offer several benefits: - Self-serve deployments - Full control and ownership for developers - Zero-slippage transfers - Enhanced programmability via configurable rate limits - Reliability features such as Smart Execution CCIP is powered by Chainlink decentralized oracle networks (DONs)β€”a proven standard with a track record of securing tens of billions of dollars and enabling over $25 trillion in on-chain transaction value. **Key CCIP developer tools** - [CCIP Directory](https://docs.chain.link/ccip/directory/mainnet/chain/aptos-mainnet) β€” view all relevant addresses and details needed to use Aptos with CCIP. - [CCIP official documentation](https://docs.chain.link/ccip) β€” start integrating CCIP into your cross-chain application. - [CCIP Token Manager](https://tokenmanager.chain.link/) β€” an intuitive front-end web interface for the deployment of new and management of existing CCTs by their developers, including no-code guided deployments and configuration tools. - [CCIP SDK](https://docs.chain.link/ccip/ccip-javascript-sdk) β€” a software development kit that streamlines the process of integrating CCIP, allowing developers to use JavaScript to create a token transfer frontend dApp. ## Pyth Network The [Pyth Network](https://pyth.network/) is one of the largest first-party Oracle networks, delivering real-time data across [a vast number of chains](https://docs.pyth.network/price-feeds/contract-addresses). The network comprises some of the world’s [largest exchanges, market makers, and financial services providers](https://pyth.network/publishers). These publish proprietary data on-chain for aggregation and distribution to smart contract applications. ### How to Use Pyth Real-Time Data in Aptos Contracts This guide explains how to use real-time Pyth data in Aptos applications. ### Configuring the `Move.toml` file Add the Pyth Contract to your project dependencies in the `Move.toml` file: ```toml copy [dependencies] Pyth = { git = "https://github.com/pyth-network/pyth-crosschain.git", subdir = "target_chains/aptos/contracts", rev = "main" } ``` The named addresses of `pyth`, `wormhole`, and `deployers` must be defined at compile time. These addresses are used to interact with the Pyth contract on Aptos. ```toml copy [addresses] pyth = "0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387" deployer = "0xb31e712b26fd295357355f6845e77c888298636609e93bc9b05f0f604049f434" wormhole = "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625" ``` Consult [Aptos Contract Addresses](https://docs.pyth.network/price-feeds/contract-addresses/aptos) for the complete list of contract addresses on different Aptos networks. ### Write Contract Code The code snippet below provides an example module fetching the BTC/USD price from Pyth price feeds: ```rust {21} copy module example::example { use pyth::pyth; use pyth::price::Price; use pyth::price_identifier; use aptos_framework::coin; // Add the pyth_price_update argument to any method on your contract that needs to read the Pyth price. // See https://docs.pyth.network/price-feeds/fetch-price-updates for more information on how to fetch the pyth_price_update. public fun get_btc_usd_price(user: &signer, pyth_price_update: vector>): Price { // First update the Pyth price feeds let coins = coin::withdraw(user, pyth::get_update_fee(&pyth_price_update)); pyth::update_price_feeds(pyth_price_update, coins); // Read the current price from a price feed. // Each price feed (e.g., BTC/USD) is identified by a price feed ID. // The complete list of feed IDs is available at https://pyth.network/developers/price-feed-ids // Note: Aptos uses the Pyth price feed ID without the `0x` prefix. let btc_price_identifier = x"e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"; let btc_usd_price_id = price_identifier::from_byte_vec(btc_price_identifier); pyth::get_price(btc_usd_price_id) } } ``` The code snippet above does the following things: 1. Call `pyth::get_update_fee` to get the fee required to update the Pyth price feeds. 2. Call `pyth::update_price_feeds` and pass `pyth_price_update` to update the Pyth price feeds. 3. Call `pyth::get_price` to read the current price, providing the [price feed ID](https://pyth.network/developers/price-feed-ids) you wish to read. ### Additional Resources You may find these additional resources helpful for developing your Aptos application. #### Sponsored Feeds on Aptos The price feeds listed in the table below are currently sponsored in **Aptos mainnet**. Update Parameters: **1 second heartbeat or 0.5% price deviation** | Name | Price Feed Id | | --------- | ------------------------------------------------------------------ | | APT/USD | `03ae4db29ed4ae33d323568895aa00337e658e348b37509f5372ae51f0af00d5` | | BTC/USD | `e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43` | | ETH/USD | `ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace` | | SOL/USD | `ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d` | | USDC/USD | `eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a` | | USDT/USD | `2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b` | | CAKE/USD | `2356af9529a1064d41e32d617e2ce1dca5733afa901daba9e2b68dee5d53ecf9` | | SUI/USD | `23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744` | | CETUS/USD | `e5b274b2611143df055d6e7cd8d93fe1961716bcd4dca1cad87a83bc1e78c1ef` | | BNB/USD | `2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f` | | WBTC/USD | `c9d8b075a5c69303365ae23633d4e085199bf5c520a3b90fed1322a0342ffc33` | | THL/USD | `74e3fbb0d33e0ed8c0078b56134dcebdae38852f0858a8ea4de4c5ea7474bd42` | | USDY/USD | `e393449f6aff8a4b6d3e1165a7c9ebec103685f3b41e60db4277b5b6d10e7326` | | WETH/USD | `9d4294bbcd1174d6f2003ec365831e64cc31d9f6f15a2b85399db8d5000960f6` | | THAPT/USD | `b29276972267db5d64ae718fb7f107ad9e72a79cabf9992f0e9bc75ad451a7f6` | | EZETH/USD | `06c217a791f5c4f988b36629af4cb88fad827b2485400a358f3b02886b54de92` | | WEETH/USD | `9ee4e7c60b940440a261eb54b6d8149c23b580ed7da3139f7f08f4ea29dad395` | | USDM/USD | `a6a0dfa49b6b3a93510658245618099f5e842514970f596cf64fad9e0d658193` | | STONE/USD | `4dcc2fb96fb89a802ef9712f6bd2246d3607cf95ca5540cb24490d37003f8c46` | For more details on sponsored feeds, check [here](https://docs.pyth.network/price-feeds/sponsored-feeds) #### API Reference The [Aptos API reference](https://docs.pyth.network/price-feeds/api-reference/aptos) lets you interactively explore the complete API of the Pyth contract. #### Example Applications - [Minimal on-chain contract](https://github.com/pyth-network/pyth-examples/blob/main/price_feeds/aptos/fetch_btc_price/sources/example.move), which updates and returns the BTC/USD price from Pyth price feeds. - [Mint NFT](https://github.com/pyth-network/pyth-examples/tree/main/price_feeds/aptos/mint_nft), a minting application that uses Pyth price feeds to mint an NFT. # Orderless Transactions > Execute transactions out of order for multi-machine signing scenarios while maintaining replay protection and security. As outlined in [AIP-123](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-123.md), orderless transactions allow for transactions to be executed out of order, which is particularly useful in scenarios where multiple machines need to sign for a single sending account, but the order in which they sign does not affect the outcome of the transaction or matter to the creator. Replay is protected by a nonce, which is a unique identifier for a transaction. This allows for the transaction to be executed at any time within the expiration time, regardless of the order in which the machines sign the transaction, but not be able to be replayed after the nonce has expired. The maximum expiration time is 60 seconds for orderless transactions, which is not the same for sequence number transactions. ## Process Overview Orderless transactions are dependent on the transaction payload specified in [AIP-129](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-129.md). The process for building and executing an orderless transaction is as follows: 1. Build a transaction with a `replayProtectionNonce` and a `TransactionPayload::TransactionPayloadPayload` that defines the operation to be executed. 2. Sign and submit the transaction as any other transaction, but with the `replayProtectionNonce` set. Ideally, the nonce should be a random u64 value. Note, that the behavior of the `replayProtectionNonce` is similar to a sequence number, but it does not guarantee ordered execution of transactions. Instead, it ensures that the transaction is unique and cannot be replayed (executed twice) with the same nonce. ## SDK Support These are demonstrations of sponsored transactions: - The [TypeScript SDK](/build/sdks/ts-sdk/building-transactions/orderless-transactions) has documentation - The [Go SDK](https://github.com/aptos-labs/aptos-go-sdk/tree/main/examples/orderless_transaction) has an example # Sponsored Transactions > Allow applications to pay transaction fees for users, simplifying onboarding by removing the need for gas tokens. As outlined in [AIP-39](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-39.md), sponsored transactions allow one account to pay the fees associated with executing a transaction for another account, essentially setting up a fee payer. Sponsored transactions simplify the process for onboarding users into applications by allowing the application to cover all associated fees for interacting with the Aptos blockchain. Here are two examples: - [MerkleTrade](https://merkle.trade/) offers low cost trading to those with Ethereum wallets by creating an Aptos wallet for users and covering all transaction fees so that the user does not need to acquire utility tokens for Aptos. - Community engagement applications like [Graffio](https://medium.com/aptoslabs/graffio-web3s-overnight-sensation-81a6cf18b626) offered to cover transaction fees for custodial accounts to support the collaborative drawing application for those without wallets. ## Process Overview The process for sending a sponsored transaction follows: - The sender of the transaction determines upon an operation, as defined by a `RawTransaction`. - The sender generates a `RawTransactionWithData::MultiAgentWithFeePayer` structure - Prior to the framework 1.8 release, this must contain the fee payer's address. - After framework release 1.8, this can optionally be set to `0x0`. - (Optionally) the sender aggregates signatures from other signers. - The sender can forward the signed transaction to the fee payer to sign and forward it to the blockchain. - Upon execution of the transaction, the sequence number of the sender account is incremented, all gas fees are deducted from the gas fee payer, and all refunds are sent to the gas fee payer. Alternatively, if the fee payer knows the operation and all signers involved, the fee payer could generate and sign the transaction and send it back to the other signers to sign. ## Technical Details In Aptos, a sponsored transaction reuses the same SignedTransaction as any other user transaction: ```rust pub struct SignedTransaction { /// The raw transaction raw_txn: RawTransaction, /// Public key and signature to authenticate authenticator: TransactionAuthenticator, } ``` The difference is in the `TransactionAuthenticator`, which stores the authorization from the fee payer of the transaction to extract utility fees from their account: ```rust pub enum TransactionAuthenticator { ... /// Optional Multi-agent transaction with a fee payer. FeePayer { sender: AccountAuthenticator, secondary_signer_addresses: Vec, secondary_signers: Vec, fee_payer_address: AccountAddress, fee_payer_signer: AccountAuthenticator, }, ... } ``` To prepare a sponsored transaction for an account, the account must first exist on-chain. This is a requirement that is being removed with the 1.8 framework release. As of the 1.8 framework release, an account does not need to exist on-chain. However, the first transaction for an account requires enough gas to not only execute the transaction and cover the costs associated with account creation, even if an account already exists. Future improvements to the account model intend to eliminate this requirement. During signing of the transaction, all parties sign the following: ```rust pub enum RawTransactionWithData { ... MultiAgentWithFeePayer { raw_txn: RawTransaction, secondary_signer_addresses: Vec, fee_payer_address: AccountAddress, }, } ``` Prior to framework release 1.8, all signers were required to know the actual fee payer address prior to signing. As of framework release 1.8, signers can optionally set the address to `0x0` and only the fee payer must sign with their address set. ## SDK Support These are demonstrations of sponsored transactions: - The TypeScript SDK has [several examples](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/examples/typescript-esm/sponsored_transactions) - The Python SDK has an example in [fee\_payer\_transfer\_coin.py](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/fee_payer_transfer_coin.py). - The Rust SDK has a test case in [the API tests](https://github.com/aptos-labs/aptos-core/blob/0a62e54e13bc5da604ceaf39efed5c012a292078/api/src/tests/transactions_test.rs#L255). # Application Integration Guide > Legacy integration guide for system integrators - deprecated in favor of the exchange integration guide. import { Aside } from '@astrojs/starlight/components'; If you provide blockchain services to your customers and wish to add the Aptos blockchain to your platform, then this guide is for you. This system integrators guide will walk you through all you need to integrate the Aptos blockchain into your platform. ## Overview This document will guide you through the following tasks to integrate with Aptos: 1. Prepare an environment for testing. 2. Create an account on the blockchain. 3. Exchange account identifiers with another entity on the blockchain, for example, to perform swaps. 4. Create a transaction. 5. Obtain a gas estimate and validate the transaction for correctness. 6. Submit the transaction to the blockchain. 7. Wait for the outcome of the transaction. 8. Query historical transactions and interactions for a given account with a specific account, i.e., withdraws and deposits. ## Getting Started In order to get started you'll need to select a network and pick your set of tools. There are also a handful of SDKs to help accelerate development. ### SDKs and tools Aptos has multiple SDKs across many different languages and platforms, please check out [SDKs](/build/sdks) for more information. Almost all developers will benefit from exploring the CLI. [Using the CLI](/build/cli) demonstrates how the CLI can be used to create accounts, transfer coins, publish Move modules, and more. ## Accounts on Aptos An [account](/network/blockchain/accounts) represents an entity on the Aptos blockchain that can send transactions. Each account is identified by a particular 32-byte account address and is a container for [Move modules and resources](/network/blockchain/resources). On Aptos, accounts must be created on-chain prior to any blockchain operations involving that account. The Aptos framework supports implicitly creating accounts when transferring Aptos coin via [`aptos_account::transfer`](https://github.com/aptos-labs/aptos-core/blob/88c9aab3982c246f8aa75eb2caf8c8ab1dcab491/aptos-move/framework/aptos-framework/sources/aptos_account.move#L18) or explicitly via [`aptos_account::create_account`](https://github.com/aptos-labs/aptos-core/blob/88c9aab3982c246f8aa75eb2caf8c8ab1dcab491/aptos-move/framework/aptos-framework/sources/aptos_account.move#L13). At creation, an [Aptos account](https://github.com/aptos-labs/aptos-core/blob/88c9aab3982c246f8aa75eb2caf8c8ab1dcab491/aptos-move/framework/aptos-framework/sources/account.move#L23) contains: - A [resource containing Aptos Coin](https://github.com/aptos-labs/aptos-core/blob/60751b5ed44984178c7163933da3d1b18ad80388/aptos-move/framework/aptos-framework/sources/coin.move#L50) and deposit and withdrawal of coins from that resource. - An authentication key associated with their current public, private key(s). - A strictly increasing [sequence number](/network/blockchain/accounts#account-sequence-number) that represents the account's next transaction's sequence number to prevent replay attacks. - A strictly increasing number that represents the next distinct GUID creation number. - An [event handle](/network/blockchain/events) for all new types of coins added to the account. - An event handle for all key rotations for the account. Read more about [Accounts](/network/blockchain/accounts) and [set one up](/build/cli/setup-cli). ## Transactions Aptos [transactions](/network/blockchain/txns-states) are encoded in [Binary Canonical Serialization (BCS)](https://github.com/diem/bcs). Transactions contain information such as the sender’s account address, authentication from the sender, the desired operation to be performed on the Aptos blockchain, and the amount of gas the sender is willing to pay to execute the transaction. Read more in [Transactions and States](/network/blockchain/txns-states). ### Generating transactions Aptos supports two methods for constructing transactions: - Using the Aptos client libraries to generate native BCS transactions. - Constructing JSON-encoded objects and interacting with the REST API to generate native transactions. The preferred approach is to directly generate native BCS transactions. Generating them via the REST API enables rapid development at the cost of trusting the fullnode to generate the transaction correctly. #### BCS-encoded transactions BCS-encoded transactions can be submitted to the `/transactions` endpoint but must specify `Content-Type: application/x.aptos.signed_transaction+bcs` in the HTTP headers. This will return a transaction submission result that, if successful, contains a transaction hash in the `hash` [field](https://github.com/aptos-labs/aptos-core/blob/9b85d41ed8ef4a61a9cd64f9de511654fcc02024/ecosystem/python/sdk/aptos_sdk/client.py#L138). ### Types of transactions Within a given transaction, the target of execution can be one of two types: - An entry function - A Move script All official SDKs support the generation of transactions that target entry functions. This guide points out many of those entry functions, such as `aptos_account::transfer` and `aptos_account::create_account`. Most basic operations on the Aptos blockchain should be available via entry point calls. While one could submit multiple transactions calling entry points in series, such operations benefit from being called atomically from a single transaction. A script payload transaction can call any public (entry) function defined within any module. Here's an example [Move script](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/scripts/two_by_two_transfer) that uses a MultiAgent transaction to extract funds from two accounts and deposit them into two other accounts. This is a [Python example](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/transfer_two_by_two.py) that uses the bytecode generated by compiling that script. ### Status of a transaction Obtain transaction status by querying the API [`/transactions/by_hash/{hash}`](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_transaction_by_hash) with the hash returned during the submission of the transaction. A reasonable strategy for submitting transactions is to limit their lifetime to 30 to 60 seconds, and polling that API at regular intervals until success or several seconds after that time has elapsed. If there is no commitment on-chain, the transaction was likely discarded. All SDKs support this automatically for waiting for transactions. ### Testing transactions or transaction pre-execution To facilitate evaluation of transactions as well as gas estimation, Aptos supports a simulation API that does not require and should not contain valid signatures on transactions. The simulation API is a synchronous API that executes a transaction and returns the output inclusive of gas usage. The simulation API can be accessed by submitting a transaction to [`/transactions/simulate`](https://api.devnet.aptoslabs.com/v1/spec#/operations/simulate_transaction). Both the [Typescript SDK](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/src/api/transactionSubmission/simulate.ts) and [Python SDK](https://github.com/aptos-labs/aptos-python-sdk/blob/main/examples/simulate_transfer_coin.py) support the simulation API. Note the output and gas used may change based upon the state of the account. For gas estimations, we recommend that the maximum gas amount be larger than the amount quoted by this API. ## Viewing current and historical state Most integrations into the Aptos blockchain benefit from a holistic and comprehensive overview of the current and historical state of the blockchain. Aptos provides historical transactions, state, and events, all the result of transaction execution. - Historical transactions specify the execution status, output, and tie to related events. Each transaction has a unique version number associated with it that dictates its global sequential ordering in the history of the blockchain ledger. - The state is the representation of all transaction outputs up to a specific version. In other words, a state version is the accumulation of all transactions inclusive of that transaction version. - As transactions execute, they may emit events. [Events](/network/blockchain/events) are hints about changes in on-chain data. The storage service on a node employs two forms of pruning that erase data from nodes: - state - events, transactions, and everything else While either of these may be disabled, storing the state versions is not particularly sustainable. Events and transactions pruning can be disabled via setting the [`enable_ledger_pruner`](https://github.com/aptos-labs/aptos-core/blob/cf0bc2e4031a843cdc0c04e70b3f7cd92666afcf/config/src/config/storage_config.rs#L141) to `false`. This is default behavior in Mainnet. In the near future, Aptos will provide indexers that mitigate the need to directly query from a node. The REST API offers querying transactions and events in these ways: - [Transactions for an account](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_account_transactions) - [Transaction by version](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_transaction_by_version) - [Events by event handle](https://api.devnet.aptoslabs.com/v1/spec#/operations/get_events_by_event_handle) ## Exchanging and tracking fungible assets Aptos has a standard [Fungible Asset](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/fungible_asset.move). Different types of fungible asset (FA) can be represented in this standard through the use of distinct metadata object. A user's FA is stored in `FungibleStore` objects owned by them. For each type of FA, every account has one primary store for that FA and optional multiple secondary stores. The difference between primary and secondary stores is the address of primary store is deterministic based on the addresses of user account and metadata object. ### Transferring FAs between users FAs, including APT, can be transferred between users' primary stores via the [`primary_fungible_store::transfer`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/primary_fungible_store.move#L142) function. For any `FungibleStore` s, [`fungible_asset::transfer`](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/fungible_asset.move#L347) would be invoked with `FungibleStore` object addresses. It is noted in the migration phase from coin to FA, withdraw/deposit/transfer FA paired from coin should call `0x1::coin::deposit/withdraw/transfer`(coin module API) to transfer the asset because the account may have the asset in both form but fungible asset API can only move FA part but not the coin part. In contrast, coin API could move both parts. For other FA, since it does not have a paired coin type, only fungible asset API can be used to move assets. To know which API to call, please refer to [`concurrent_fungible_asset_balance`](/build/indexer/indexer-api/fungible-asset-balances) table `standard` field, where "v1" means using coin API and "v2" means using fungible asset API. ### Current balance for Fungible Asset Indexer users can just query [`concurrent_fungible_asset_balance`](/build/indexer/indexer-api/fungible-asset-balances) to get the balance. For node API, the current balance for an APT FA of FungibleStore is available at the account resources URL: `https://{rest_api_server}/accounts/{fungible_store_object_address}/resource/0x1::fungible_asset::FungibleStore`. The balance is stored as `balance`. The resource also contains a metadata object of the FA type and the frozen status. The address of the primary fungible store can be calculated as `sha3_256(32-byte account address | 32-byte metadata object address | 0xFC)`. The metadata object address of APT FA is `0xA`. Aptos users have the option to upgrade to concurrent fungible balance to allow parallelization of balance updates, improving the performance of a single account. When a user has upgraded a fungible store balance to support concurrent update, the fungible store object will have another resource `ConcurrentFungibleBalance` that contains the balance of the store, and the `balance` field of FungibleStore will be set to 0. The current balance for an APT FA of `ConcurrentFungibleBalance` (if exists) is available at the account resources URL: `https://{rest_api_server}/accounts/{fungible_store_object_address}/resource/0x1::fungible_asset::ConcurrentFungibleBalance`. Therefore, to get the total balance of a fungible asset, it is either the non-zero balance of `FungibleStore` or the `balance` field of `ConcurrentFungibleBalance` if it exists and the balance of `FungibleStore` is 0. ```json { "type": "0x1::fungible_asset::FungibleStore", "data": { "balance": "233910778869", "frozen": false, "metadata": { "inner": "0xedc2704f2cef417a06d1756a04a16a9fa6faaed13af469be9cdfcac5a21a8e2e" } } } ``` ```json { "type": "0x1::fungible_asset::ConcurrentFungibleBalance", "data": { "balance": "233910778869" } } ``` ## Exchanging and tracking coins Aptos has a standard [Coin type](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move). Different types of coins can be represented in this type through the use of distinct structs that represent the type parameter or generic for `Coin`. Coins are stored within an account under the resource `CoinStore`. At account creation, each user has the resource `CoinStore<0x1::aptos_coin::AptosCoin>` or `CoinStore`, for short. Within this resource is the Aptos coin: `Coin`. ### Transferring coins between users Coins, including APT, can be transferred between users via the [`aptos_account::transfer_coins`](https://github.com/aptos-labs/aptos-core/blob/d1610e1bb5214689a37a9cab59cf9254e8eb2be1/aptos-move/framework/aptos-framework/sources/aptos_account.move#L92) function for all coins and [`aptos_account::transfer`](https://github.com/aptos-labs/aptos-core/blob/88c9aab3982c246f8aa75eb2caf8c8ab1dcab491/aptos-move/framework/aptos-framework/sources/aptos_account.move#L18) for Aptos coins. ### Current balance for a coin To retrieve the balance of a coin, or a coin that was migrated to a fungible asset, you can use the `0x1::coin::balance(account address)` view function. This will combine the coin and coin migrated to fungible asset balances. ```typescript filename="example.ts" import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); const coinType = "0x1::aptos_coin::AptosCoin"; const account = "0x00000000000000000000000000000001"; const [balanceStr] = await aptos.view<[string]>({ payload: { function: "0x1::coin::balance", typeArguments: [coinType], functionArguments: [account] } }); const balance = parseInt(balanceStr, 10); ``` ### Querying transactions In Aptos, each transaction is committed as a distinct version to the blockchain. This allows for the convenience of sharing committed transactions by their version number; to do so, query: `https://{rest_server_api}/transactions/by_version/{version}` Transactions submitted by an account can also be queried via the following URL where the `sequence_number` matches the sequence number of the transaction: `https://{rest_server_api}/account/{address}/transactions?start={sequence_number}&limit=1` A transfer transaction would appear as follows: ```json { "version": "13629679", "gas_used": "4", "success": true, "vm_status": "Executed successfully", "changes": [ { "address": "0xb258b91eee04111039320a85b0c24a2dd433909e14a6b5c32ee722e0fdecfddc", "data": { "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": { "coin": { "value": "1000" }, "deposit_events": { "counter": "1", "guid": { "id": { "addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "creation_num": "2" } } }, ... } }, "type": "write_resource" }, ... ], "sender": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", "sequence_number": "0", "max_gas_amount": "2000", "gas_unit_price": "1", "expiration_timestamp_secs": "1660616127", "payload": { "function": "0x1::aptos_account::transfer", "arguments": [ "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "1000" ], "type": "entry_function_payload" }, "events": [ { "key": "0x0300000000000000810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", "guid": { "id": { "addr": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", "creation_num": "3" } }, "sequence_number": "0", "type": "0x1::coin::WithdrawEvent", "data": { "amount": "1000" } }, { "key": "0x02000000000000005098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "guid": { "id": { "addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "creation_num": "2" } }, "sequence_number": "0", "type": "0x1::coin::DepositEvent", "data": { "amount": "1000" } } ], "timestamp": "1660615531147935", "type": "user_transaction" } ``` Here is a breakdown of the information in a transaction: - `version` indicates the globally unique identifier for this transaction, its ordered position in all the committed transactions on the blockchain - `sender` is the account address of the entity that submitted the transaction - `gas_used` is the units paid for executing the transaction - `success` and `vm_status` indicate whether the transaction successfully executed and any reasons why it might not have - `changes` include the final values for any state resources that have been modified during the execution of the transaction - `events` contain all the events emitted during the transaction execution - `timestamp` is the near real-time timestamp of the transaction's execution If `success` is false, then `vm_status` will contain an error code or message that resulted in the transaction failing to succeed. When `success` is false, `changes` will be limited to gas deducted from the account and the sequence number incrementing. There will be no `events`. Each event in `events` is differentiated by a `key`. The `key` is derived from the `guid` in `changes`. Specifically, the `key` is a 40-byte hex string where the first eight bytes (or 16 characters) are the little-endian representation of the `creation_num` in the `guid` of the `changes` event, and the remaining characters are the account address. As events do not dictate what emitted them, it is imperative to track the path in `changes` to determine the source of an event. In particular, each `CoinStore` has both a `WithdrawEvent` and a `DepositEvent`, based upon the type of coin. In order to determine which coin type is used in a transaction, an indexer can compare the `guid::creation_num` in a `changes` event combined with the address to the `key` for events in `events`. Using the above example, `events[1].guid` is equivalent to `changes[0].data.data.deposit_events.guid`, which is ```json {"addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "creation_num": "2"} ``` ### Querying events Aptos provides clear and canonical events for all withdraw and deposit of coins. This can be used in coordination with the associated transactions to present to a user the change of their account balance over time, when that happened, and what caused it. With some amount of additional parsing, metadata such as the transaction type and the other parties involved can also be shared. Query events by handle URL: `https://{rest_api_server}/accounts/{address}/events/0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>/withdraw_events` ```json [ { "version":"13629679", "key": "0x0300000000000000cb2f940705c44ba110cd3b4f6540c96f2634938bd5f2aabd6946abf12ed88457", "guid": { "id": { "addr": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", "creation_num": "3" } }, "sequence_number": "0", "type": "0x1::coin::WithdrawEvent", "data": { "amount": "1000" } } ] ``` Gather more information from the transaction that generated the event by querying `https://{rest_server_api}/transactions/by_version/{version}` where `{version}` is the same value as the `{version}` in the event query. ### Tracking coin balance changes Consider the transaction from the earlier section, but now with an arbitrary coin `0x1337::my_coin::MyCoin` and some gas parameters changed: ```json { "version": "13629679", "gas_used": "20", "success": true, "vm_status": "Executed successfully", "changes": [ { "address": "0xb258b91eee04111039320a85b0c24a2dd433909e14a6b5c32ee722e0fdecfddc", "data": { "type": "0x1::coin::CoinStore<0x1337::my_coin::MyCoin>", "data": { "coin": { "value": "1000" }, "deposit_events": { "counter": "1", "guid": { "id": { "addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "creation_num": "2" } } }, ... } }, "type": "write_resource" }, ... ], "sender": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", "sequence_number": "0", "max_gas_amount": "2000", "gas_unit_price": "110", "expiration_timestamp_secs": "1660616127", "payload": { "function": "0x1::aptos_account::transfer_coins", "type_arguments": [ "0x1337::my_coin::MyCoin" ], "arguments": [ "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "1000" ], "type": "entry_function_payload" }, "events": [ { "key": "0x0300000000000000810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", "guid": { "id": { "addr": "0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b", "creation_num": "3" } }, "sequence_number": "0", "type": "0x1::coin::WithdrawEvent", "data": { "amount": "1000" } }, { "key": "0x02000000000000005098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "guid": { "id": { "addr": "0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e", "creation_num": "2" } }, "sequence_number": "0", "type": "0x1::coin::DepositEvent", "data": { "amount": "1000" } } ], "timestamp": "1660615531147935", "type": "user_transaction" } ``` There are three balance changes in this transaction: 1. A withdrawal of `1000` of `0x1337::my_coin::MyCoin` from the transaction sending account `0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b` 2. A deposit of `1000` of `0x1337::my_coin::MyCoin` to receiving account `0x5098df8e7969b58ab3bd2d440c6203f64c60a1fd5c08b9d4abe6ae4216246c3e` 3. A gas fee `2200` of `0x1::aptos_coin::AptosCoin` from the sending account `0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b` To retrieve the withdrawal information: 1. Scan the `changes` for `0x1::coin::CoinStore`. Note the `CoinType` is a generic signifying which coin is stored in the store. In this example, the `CoinType` is `0x1337::my_coin::MyCoin`. 2. Retrieve the `guid` for `withdraw_events`. In this example, the `guid` contains `addr` `0x810026ca8291dd88b5b30a1d3ca2edd683d33d06c4a7f7c451d96f6d47bc5e8b` and `creation_num` `3`. 3. Scan for events with this `guid` and extract the event associated with it. In this example, it is the `0x1::coin::WithdrawEvent`. 4. Note the `amount` field will be the number of `CoinType` removed from the account in the `guid`. In this example, it is `1000`. To retrieve the deposit information, it's the same as withdrawal except: 1. The `guid` used is under `deposit_events` 2. The `amount` will be a positive increase on the account's balance. 3. The event's name will be: `0x1::coin::DepositEvent` To retrieve the gas fee: 1. The `gas_used` field must be multiplied times the `gas_unit_price`. In this example, `gas_used=20` and `gas_unit_price=110` so the total gas coins withdrawn is `2200`. 2. Gas is always: `0x1::aptos_coin::AptosCoin` To retrieve information about the number of decimals of the coin: 1. You can retrieve the number of decimals for a coin via its: `0x1::coin::CoinInfo` 2. This will be located at the address of the coin type. In this example, you would need to look up `0x1::coin::CoinInfo<0x1337::my_coin::MyCoin>` at address `0x1337`. To create some sample data to explore, conduct ["Your first transaction"](/build/guides/first-transaction). To learn more about coin creation, make ["Your First Coin"](/build/guides/first-coin). # Transaction Management > Build scalable transaction management systems on Aptos with proper account handling and sequence number management. This guide explains how to build a transaction management harness that can scale on the Aptos blockchain. ## Background In Aptos, transactions are mapped back to an account in terms of the entity that signs or authorizes that transaction and provides an account-based sequence number. When the Aptos network receives a new transaction, several rules are followed with respect to this: - The transaction sent from an account must be authorized correctly by that account. - The current time as defined by the most recent ledger update must be before the expiration timestamp of the transaction. - The transaction's sequence number must be equal to or greater than the sequence number on-chain for that account. Once the initial node has accepted a transaction, the transaction makes its way through the system by an additional rule. If a transactions sequence number is higher than the current on-chain sequence number, it can only progress toward consensus if every node in the path has seen a transaction with the sequence number between the on-chain state and the current sequence number. Example: Alice owns an account whose current on-chain sequence number is 5. Alice submits a transaction to node Bob with sequence number 6. Bob the node accepts the transaction but does not forward it, because Bob has not seen 5. In order to make progress, Alice must either send Bob transaction number 5 or Bob must be notified from consensus that 5 was committed. In the latter, Alice submitted the transaction through another node. Beyond this there are two remaining principles: - A single account can have at most 100 uncommitted transactions submitted to the blockchain. Any more than that and the transactions will be rejected. This can happen silently if Alice submits the first 100 to Bob the node and the next 100 to Carol the node. If both those nodes share a common upstream, then that upstream will accept Alice's 100 sent via Bob but silently reject Alice's 100 sent via Carol. - Submitting to distinct transactions to multiple nodes will result in slow resolution as transactions will not make progress from the submitted node until the submitted knows that all preceding transactions have been committed. For example, if Alice sends the first 50 via Bob and the next 50 via Carol. ## Building a Transaction Manager Now that we understand the nuances of transactions, let's dig into building a robust transaction manager. This consists of the following core components: - A sequence number generator that allocates and manages available sequence numbers for a single account. - A transaction manager that receives payloads from an application or a user, sequence numbers from the sequence number generator, and has access to the account key to combine the three pieces together into a viable signed transaction. It then also takes the responsibility for pushing the transaction to the blockchain. - An on-chain worker, leader harness that lets multiple accounts share the signer of a single shared account. Currently, this framework assumes that the network builds no substantial queue, that is a transaction that is submitted executes and commits with little to no delay. In order to address high demand, this work needs to be extended with the following components: - Optimizing `base_gas_unit` price to ensure priority transactions can be committed to the blockchain. - Further handling of transaction processing rates to ensure that the expiration timer is properly set. - Handling of transaction failures to either be ignored or resubmitted based upon desired outcome. Note, an account should be managed by a single instance of the transaction manager. Otherwise, each instance of the transaction manager will likely have stale in-memory state resulting in overlapping sequence numbers. ### Implementations - Python - [Sequence number manager](https://github.com/aptos-labs/aptos-core/pull/7987) - [Transaction manager](https://github.com/aptos-labs/aptos-core/pull/7987) - [Worker-leader smart contract](https://github.com/aptos-labs/aptos-core/pull/7986) ### Managing Sequence Numbers Each transaction requires a distinct sequence number that is sequential to previously submitted transactions. This can be provided by the following process: 1. At startup, query the blockchain for the account’s current sequence number. 2. Support up to 100 transactions in flight at the same time, that is 100 sequence numbers can be allocated without confirming that any have been committed. 3. If there are 100 transactions in flight, determine the actual committed state by querying the network. This will update the current sequence number. 4. If there are less than 100 transactions in flight, return to step 2. 5. Otherwise, sleep for .1 seconds and continue to re-evaluate the current on-chain sequence number. 6. All transactions should have an expiration time. If the expiration time has passed, assume that there has been a failure and reset the sequence number. The trivial case is to only monitor for failures when the maximum number of transactions are in flight and to let other services manages this otherwise. In parallel, monitor new transactions submitted. Once the earliest transaction expiration time has expired synchronize up to that transaction. Then repeat the process for the next transaction. If there is any failure, wait until all outstanding transactions have timed out and leave it to the application to decide how to proceed, e.g., replay failed transactions. The best method to waiting for outstanding transactions is to query the ledger timestamp and ensure it is at least elapsed the maximum timeout from the last transactions submit time. From there, validate with mempool that all transactions since the last known committed transaction are either committed or no longer exist within the mempool. This can be done by querying the REST API for transactions of a specific account, specifying the currently being evaluated sequence number and setting a limit to 1. Once these checks are complete, the local transaction number can be resynchronized. These failure handling steps are critical for the following reasons: - Mempool does not immediate evict expired transactions. - A new transaction cannot overwrite an existing transaction, even if it is expired. - Consensus, i.e., the ledger timestamp, dictates expirations, the local node will only expire after it sees a committed timestamp after the transactions expiration time and a garbage collection has happened. ### Managing Transactions Once a transaction has been submitted it goes through a variety of steps: 1. Submission to a REST endpoint. 2. Pre-execution validation in the Mempool during submission. 3. Transmission from Mempool to Mempool with pre-execution validation happening on each upstream node. 4. Inclusion in a consensus proposal. 5. One more pre-execution validation. 6. Execution and committing to storage. There are many potential failure cases that must be considered: - Failure during transaction submission (1 and 2): - Visibility: The application will receive an error either that the network is unavailable or that the transaction failed pre-execution validation. - If the error is related to availability or duplicate sequence numbers, wait until access is available and the sequence number has re-synchronized. - Pre-execution validation failures are currently out of scope, outside of those related to duplicate sequence numbers, account issues are likely related to an invalid key for the account or the account lacks sufficient funds for gas. - Failure between submission and execution (3, 4, and 5): - Visibility: Only known by waiting until the transaction has expired. - These are the same as other pre-execution validation errors due to changes to the account as earlier transactions execute. It is likely either duplicate sequence numbers or the account lacks sufficient funds for gas. - Failure during execution (6): - Visibility: These are committed to the blockchain. - These errors occur as a result of on-chain state issues, these tend to be application specific, such as an auction where a new bid might not actually be higher than the current bid. ### Workers and Identity Using the above framework, a single account can push upwards of 100 transactions from the start of a block to the end of a block. Assuming that all 100 transactions are consumed within 1 block, it will take a bit of time for the next 100 slots to be available. This is due to the network delays as well as the multi-staged validator pipeline. To fully leverage the blockchain for massive throughput, using a single user account is not enough. Instead, Aptos supports the concept of worker accounts that can share the responsibility of pushing work through a shared account, also known as a resource account. In this model, each worker has access to the `SignerCap` of the shared account, which enables them to impersonate the shared account or generate the `signer` for the shared account. Upon gaining the `signer`, the transaction can execute the logic that is gated by the signer of the shared account. Another model, if viable, is to decouple the `signer` altogether away from permissions and to make an application specific capability. Then this capability can be given to each worker that lets them operate on the shared infrastructure. Note that parallelization on the shared infrastructure can be limited if any transaction would have any read or write conflicts. This won’t prevent multiple transactions from executing within a block, but can impact maximum blockchain performance. # Your First NFT > Learn to create, mint, and transfer digital assets (NFTs) on Aptos using the TypeScript SDK with step-by-step examples. import { Aside, Steps } from '@astrojs/starlight/components'; This tutorial will guide you through the process of using the Aptos TypeScript SDK (`@aptos-labs/ts-sdk`) to create a new digital asset (often referred to as an NFT) on Aptos. By the end of this tutorial, you will know how to: 1. Create a collection of digital assets (NFTs). 2. Mint a new digital asset (NFT) within that collection. 3. Transfer the digital asset (NFT) between accounts. 4. Verify the digital asset's (NFT's) movement by checking the updated balances. ## Walking Through The Code Below is the step-by-step explanation of how to create, transfer, and interact with a digital asset on-chain. We'll go through how the example code (shown in full at the end) does it. To skip to just getting the code running, see [**Running An Example**](#running-an-example). ### Code Walkthrough 1. Setup the Client We import and configure the `Aptos` client from the SDK to connect to the specified network: ```tsx filename="index.ts" const APTOS_NETWORK = NetworkToNetworkName[process.env.APTOS_NETWORK] || Network.DEVNET; const config = new AptosConfig({ network: APTOS_NETWORK }); const aptos = new Aptos(config); ``` This `aptos` object allows us to interact with the Aptos blockchain (funding accounts, creating assets, submitting transactions, etc.). 2. Create and Fund Accounts We generate two accounts, Alice and Bob. On devnet, we can easily fund them with test APT. ```tsx filename="index.ts" const alice = Account.generate(); const bob = Account.generate(); await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: INITIAL_BALANCE }); await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: INITIAL_BALANCE }); ``` 3. Create a Collection We create a collection in Alice's account. A collection acts like a "folder" or "category" for digital assets. In this case, we are creating `"Example Collection"`. ```tsx filename="index.ts" const createCollectionTransaction = await aptos.createCollectionTransaction({ creator: alice, description: "This is an example collection.", name: "Example Collection", uri: "aptos.dev", }); const committedTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: createCollectionTransaction, }); await aptos.waitForTransaction({ transactionHash: committedTxn.hash }); ``` 4. Mint a Digital Asset With the collection created, we can now mint a digital asset (an NFT) for the collection. This involves providing details like the name, description, and a URI (often linking to metadata like images). ```tsx filename="index.ts" const mintTokenTransaction = await aptos.mintDigitalAssetTransaction({ creator: alice, collection: "Example Collection", description: "This is an example digital asset.", name: "Example Asset", uri: "https://aptos.dev/asset.png", }); const mintTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: mintTokenTransaction, }); await aptos.waitForTransaction({ transactionHash: mintTxn.hash }); ``` 5. Transfer the Digital Asset Once minted, the asset belongs to Alice. We can verify this by fetching Alice's digital assets. Then we build and submit a transaction to transfer this asset to Bob. ```tsx filename="index.ts" const aliceDigitalAssets = await aptos.getOwnedDigitalAssets({ ownerAddress: alice.accountAddress }); const digitalAssetAddress = aliceDigitalAssets[0].token_data_id; const transferTransaction = await aptos.transferDigitalAssetTransaction({ sender: alice, digitalAssetAddress, recipient: bob.accountAddress, }); const transferTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: transferTransaction, }); await aptos.waitForTransaction({ transactionHash: transferTxn.hash }); ``` After completion, the asset should now appear in Bob's account. 6. Verify the Balances Finally, we check both Alice's and Bob's accounts to ensure that Alice no longer has the asset and Bob now has it. ```tsx filename="index.ts" const aliceDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({ ownerAddress: alice.accountAddress }); const bobDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({ ownerAddress: bob.accountAddress }); console.log(`Alice's digital asset balance: ${aliceDigitalAssetsAfter.length}`); console.log(`Bob's digital asset balance: ${bobDigitalAssetsAfter.length}`); ``` ## Running An Example ### Getting Started 1. Set up Your Project Create a new directory for your project and initialize a Node.js project: ```shellscript filename="Terminal" mkdir aptos-digital-asset-tutorial cd aptos-digital-asset-tutorial npm init -y ``` This will create a `package.json` file, allowing you to install dependencies and run scripts. 2. Install Dependencies You will need the Aptos TypeScript SDK and `dotenv` to manage environment variables: ```shellscript filename="Terminal" npm install @aptos-labs/ts-sdk dotenv npm install --save-dev @types/node ``` 3. Create tsconfig.json Create a `tsconfig.json` file with the following: ```json filename="tsconfig.json" { "compilerOptions": { "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "types": ["node"], "lib": ["es2020"] } } ``` This configuration ensures TypeScript properly recognizes Node.js types and provides appropriate type checking. 4. Configure Environment Variables Create a `.env` file with the following: ```shellscript filename="Terminal" APTOS_NETWORK=devnet ``` 5. Adding index.ts Create an `index.ts` file with the following: ```tsx filename="index.ts" // Update the TODOs below to customize this digital asset to your needs. // You will want to customize the Collection values and individual Digital Asset values. // This example demonstrates creating a collection, populating it with digital assets, and transferring them. import "dotenv/config"; import { Account, Aptos, AptosConfig, Network, NetworkToNetworkName, } from "@aptos-labs/ts-sdk"; // Verify environment variables are loaded console.log("Environment variables loaded:", { APTOS_NETWORK: process.env.APTOS_NETWORK || "not set" }); const INITIAL_BALANCE = 100_000_000; console.log("Step 1: Setting up a client to connect to Aptos"); const APTOS_NETWORK = NetworkToNetworkName[process.env.APTOS_NETWORK!] || Network.DEVNET; const config = new AptosConfig({ network: APTOS_NETWORK }); const aptos = new Aptos(config); async function example() { console.log("\n=== Step 2: Creating and funding accounts ===\n"); const alice = Account.generate(); const bob = Account.generate(); console.log(`Alice's address: ${alice.accountAddress}`); console.log(`Bob's address: ${bob.accountAddress}`); console.log("Funding Alice's account..."); await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: INITIAL_BALANCE }); console.log("Alice's account funded!"); console.log("Funding Bob's account..."); await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: INITIAL_BALANCE }); console.log("Bob's account funded!"); console.log("\n=== Step 3: Creating a collection ===\n"); // TODO: Update these values to customize your Digital Asset! const collectionName = "Example Collection"; const collectionDescription = "This is an example collection."; const collectionURI = "aptos.dev"; console.log("Building the collection creation transaction..."); const createCollectionTransaction = await aptos.createCollectionTransaction({ creator: alice, description: collectionDescription, name: collectionName, uri: collectionURI, }); console.log("Submitting the collection creation transaction..."); const committedTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: createCollectionTransaction, }); console.log("Waiting for the collection creation transaction to complete..."); await aptos.waitForTransaction({ transactionHash: committedTxn.hash }); console.log("Collection created successfully!"); console.log("\n=== Step 4: Minting a digital asset ===\n"); // TODO: Update the values of the Digital Assets you are minting! const tokenName = "Example Asset"; const tokenDescription = "This is an example digital asset."; const tokenURI = "aptos.dev/asset"; console.log("Building the mint transaction..."); const mintTokenTransaction = await aptos.mintDigitalAssetTransaction({ creator: alice, collection: collectionName, description: tokenDescription, name: tokenName, uri: tokenURI, }); console.log(mintTokenTransaction) console.log("Submitting the mint transaction..."); const mintTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: mintTokenTransaction, }); console.log(mintTxn) console.log("Waiting for the mint transaction to complete..."); await aptos.waitForTransaction({ transactionHash: mintTxn.hash }); console.log("Digital asset minted successfully!"); console.log("\n=== Step 5: Transferring the digital asset ===\n"); // Wait for the indexer to update with the latest data from on-chain await new Promise((resolve) => setTimeout(resolve, 5000)); const aliceDigitalAssets = await aptos.getOwnedDigitalAssets({ ownerAddress: alice.accountAddress, }); // Check if Alice has any digital assets before accessing them if (aliceDigitalAssets.length === 0) { console.error("No digital assets found for Alice. Make sure the minting was successful."); return; } const digitalAssetAddress = aliceDigitalAssets[0].token_data_id; console.log("Building the transfer transaction..."); const transferTransaction = await aptos.transferDigitalAssetTransaction({ sender: alice, digitalAssetAddress, recipient: bob.accountAddress, }); console.log("Submitting the transfer transaction..."); const transferTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: transferTransaction, }); console.log("Waiting for the transfer transaction to complete..."); await aptos.waitForTransaction({ transactionHash: transferTxn.hash }); console.log("Digital asset transferred successfully!"); console.log("\n=== Step 6: Verifying digital asset balances ===\n"); const aliceDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({ ownerAddress: alice.accountAddress, }); const bobDigitalAssetsAfter = await aptos.getOwnedDigitalAssets({ ownerAddress: bob.accountAddress, }); console.log(`Alice's digital asset balance: ${aliceDigitalAssetsAfter.length}`); console.log(`Bob's digital asset balance: ${bobDigitalAssetsAfter.length}`); console.log("\n=== Step 7: Transaction hashes for explorer ===\n"); console.log(`Collection creation transaction: ${committedTxn.hash}`); console.log(`Mint transaction: ${mintTxn.hash}`); console.log(`Transfer transaction: ${transferTxn.hash}`); console.log("\nYou can view these transactions on the Aptos Explorer:"); console.log("https://explorer.aptoslabs.com/?network=devnet"); } example(); ``` 6. Run the code ```shellscript filename="Terminal" npx ts-node index.ts ``` If everything is set up correctly, you will see output logs detailing each step, transaction hashes, and final balances. 7. View Your Transactions on the Explorer After running the code, you'll see transaction hashes in the console output, especially in Step 7 which displays all transaction hashes for easy reference: ```shellscript filename="Terminal" === Step 7: Transaction hashes for explorer === Collection creation transaction: 0x8c5d2a4ce32d76349bfb4f3830740c1c103399e8cbc31d6e2c7a871c88e6ad48 Mint transaction: 0x673d2cbb9fef468fe41f271c0fcf20872e9fa79afb6a2000368394000071b02e Transfer transaction: 0x3a1e99d6fd3f8e7e962c311f3dfd92c11e468da5b6084123b8f7e0248a37ffa7 You can view these transactions on the Aptos Explorer: https://explorer.aptoslabs.com/?network=devnet ``` You can view these transactions on the Aptos Explorer: 1. Copy the transaction hash from your console 2. Visit [Aptos Explorer](https://explorer.aptoslabs.com/?network=devnet) 3. Make sure you're on the correct network (Devnet) 4. Paste the transaction hash in the search bar 5. View the details of your transaction, including: - The sender and recipient addresses - The exact time the transaction was processed - Gas fees paid - The digital asset that was transferred This is a great way to verify your transactions and understand how they're recorded on the blockchain. ### Further Reading & Resources - [aptos ts-sdk docs](/build/sdks/ts-sdk) - [Account basics](/network/blockchain/accounts) - [REST API specification](/rest-api) # Indexer API Access > Access Aptos Indexer GraphQL API for historical data, transactions, fungible assets, and tokens with SDK integration and direct endpoints import { Aside, CardGrid, LinkCard } from '@astrojs/starlight/components'; {/* */} Aptos Labs hosts a public version of the Indexer GraphQL API that anyone can use to get basic historical and aggregate data about transactions, fungible assets, and tokens from on-chain. You can explore it by hand by viewing the Hasura Explorer below for the network you are interested in. You can also access the API via the GraphQL endpoints below. For more information on the format of data in each field / table, please see the [table reference page](/build/indexer/indexer-api/indexer-reference). ## SDK Access (Primary Method) The primary way to use the Indexer is to access it through the [TypeScript SDK](/build/sdks/ts-sdk/fetch-data-via-sdk). The TypeScript SDK will automatically handle rate limits, and can seamlessly allow for both [Fullnode REST API](/build/apis/fullnode-rest-api) access and Indexer access depending on what data is needed. ## Hasura Explorer (Manual Queries) Choose a network to explore the free Aptos-Hosted Indexer API using the Hasura Explorer: ## GraphQL API Endpoints (Direct Access) If you need to directly make GraphQL queries to the Aptos-Labs hosted Indexer API, then use the following endpoints: - **Mainnet:** `https://api.mainnet.aptoslabs.com/v1/graphql` - **Testnet:** `https://api.testnet.aptoslabs.com/v1/graphql` - **Devnet:** `https://api.devnet.aptoslabs.com/v1/graphql` ### Rate limits Learn more about the rate limits that apply to the Aptos Labs hosted indexer API by reading the [Geomi docs](https://geomi.dev/docs/admin/billing). If you need a higher rate limit, consider the following solutions: 1. Get an API Key from [Geomi](https://geomi.dev/). Learn more about API keys at the [Geomi docs site](https://geomi.dev/docs/api-keys). 2. Run the Aptos Indexer API yourself. See the guide to self-hosting [here](/build/indexer/txn-stream/self-hosted). # Get Account Transactions Data > Retrieve historical transaction data for accounts using GraphQL queries with transaction versions and chronological ordering import { GraphQLEditor } from '~/components/react/GraphQLEditor'; import { Aside } from '@astrojs/starlight/components'; `AccountTransactionsData` retrieves `transaction_version`s of transactions that affect a specified account address, ordered in descending order. `transaction_version` is a unique id given to each transaction on-chain that increases by 1 each time. This query is essential for applications that require a historical log of transactions for audit, tracking, or display purposes. #### Variables: - `$address`: **String** - The blockchain account address for which to query transaction data. Example: `"0x1abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"`. - `$limit`: **Integer** - Specifies the maximum number of transaction versions to return. Helps manage the volume of data retrieved. Example: `10`. - `$offset`: **Integer** - The offset from which to start fetching the transaction versions. Useful for paginating results. Example: `0`.
# Getting Recent Transactions A helpful variant of the above query limits results to just ones that happened after a specific `transaction_version`. All results will have a `transaction_version` greater than `$gt`. #### Variables: - `$address`: **String** - The blockchain account address for which to query transaction data. Example: `"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`. - `$limit`: **Integer** - Specifies the maximum number of transaction versions to return. This helps limit the results to a manageable size. Example: `10`. - `$gt`: **bigint** - The transaction version number above which transactions should be fetched. A transaction version is a sequentially increasing number that increments for every transaction. Transaction version 0 is the first transaction (genesis transaction), and a transaction version 100 is the 101st transaction in the blockchain. Example: `599296148`. # Get Aptos Name From Address > Look up registered Aptos Name Service (ANS) domain names for account addresses with reverse domain lookup functionality import { GraphQLEditor } from '~/components/react/GraphQLEditor'; import { Aside } from '@astrojs/starlight/components'; `getNameFromAddress` looks for a registered domain name for a given account. For example, a user could register their account with the Aptos Name Service to be associated to `love.apt`. You can learn more by going to [https://www.aptosnames.com/](https://www.aptosnames.com/). #### Variables: - `$registered_address`: **String** - The account address you want to find any associated active domain names for. Ex. `"0xca4349ce902a656570a4f344cc8f360fb13fd41b5fae77bcc9ee82252d67539e"`. # Indexer Architecture > Understanding Aptos Indexer architecture: Transaction Stream Service, custom processors, database integration, and API structure import { ThemedImage } from '~/components/ThemedImage'; The Aptos Indexer stores data from on-chain (via the Transaction Stream Service). It indexes basic data about transactions, fungible assets, tokens, collections, accounts, ANS (Aptos Name Service) names, and more. Apps can query that data via the Indexer API. Aptos Labs hosts a free version of the Indexer API to help the community get access to data such as: 1. Historical data - Ex. [What transactions have impacted this account?](/build/indexer/indexer-api/account-transactions) 2. Aggregate data - Ex. [How many delegators are in this staking pool?](/build/indexer/indexer-api/get-delegators) 3. Specific info best searched via query - Ex. [What NFTs does an account own?](/build/indexer/indexer-api/get-nfts) ### High Level Breakdown Here is how the Indexer creates that API at a high-level:
The Indexer uses the [Transaction Stream Service](/build/indexer/txn-stream) and custom processors written with the [Indexer SDK](/build/indexer/indexer-sdk) to update a database with rich tables. Then it exposes an API for Aptos apps to access the consolidated data. For situations where you need to go beyond the Aptos hosted Indexer API data, you will want to create a custom processor with the [Indexer SDK](/build/indexer/indexer-sdk). Writing a custom processor can help you: 1. Get access to different types of data. 2. Store additional information beyond what the Aptos Labs hosted Indexer API is saving. 3. Change how transactions are processed. If you would like to operate your own Indexer API as a service, see how to [host your own Indexer](/build/indexer/indexer-api/self-hosted). ## Detailed Overview You can use the below diagram for a much more in-depth diagram explaining how the Indexer code actually works behind the scenes.