This is the full developer documentation for Aptos Docs # Build the Future of Web3 on Aptos > Everything you need to build a best-in-class Web3 experience. Features * [Keyless](/build/guides/aptos-keyless) * [Passkeys](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-66.md) * [On-chain Randomness](/build/smart-contracts/randomness) * [Gas and Storage Fees](/network/blockchain/gas-txn-fee) * [Parallel Execution](/network/blockchain/execution) * [Fee Payer](/build/sdks/ts-sdk/building-transactions/sponsoring-transactions) Tooling * [Aptos CLI](/build/cli) * [Indexer API](/build/indexer/indexer-api) * [Official SDKs](/build/sdks) * [Testnet Faucet](/network/faucet) * [Faucet API](/build/apis/faucet-api) Resources * [Apply for a Grant](https://aptosfoundation.org/grants) * [Aptos Learn](https://learn.aptoslabs.com) * [Ecosystem Projects](https://aptosfoundation.org/ecosystem/projects) Connect * [Developer Discussions](https://github.com/aptos-labs/aptos-developer-discussions/discussions) * [Discussion Forum](https://forum.aptosfoundation.org) * [Discord](https://discord.gg/aptosnetwork) * [Telegram](https://t.me/aptos) # Aptos MCP # Aptos Model Context Protocol (MCP) [Section titled “Aptos Model Context Protocol (MCP)”](#aptos-model-context-protocol-mcp) 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 [Section titled “Getting Started”](#getting-started) 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. [Claude Code ](aptos-mcp/claude)Set up for Claude Code [Cursor ](aptos-mcp/cursor)Set up for Cursor # Aptos MCP & Claude Code ## Set up Claude Code with Aptos MCP [Section titled “Set up Claude Code with Aptos MCP”](#set-up-claude-code-with-aptos-mcp) 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 the [Aptos Developer Portal](https://aptos.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. ``` # Aptos MCP & Cursor # Set up Cursor with Aptos MCP [Section titled “Set up Cursor with Aptos MCP”](#set-up-cursor-with-aptos-mcp) 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 the [Aptos Developer Portal](https://aptos.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 [Section titled “Verify Cursor runs your MCP”](#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 ![image](https://github.com/user-attachments/assets/568600be-2a00-4381-876d-619e5771f602) 4. Click the “refresh” icon to update the MCP. 5. Make sure the Cursor AI window dropdown is set to `Agent` ![image (1)](https://github.com/user-attachments/assets/957ab3eb-72ef-46ee-b129-f43ecb327158) 6. Prompt the agent with `what aptos mcp version are you using?` to verify the connection. The agent should reply with something like: ![Screenshot 2025-06-26 at 3 54 44PM](https://github.com/user-attachments/assets/4ead13c6-1697-40e1-b4e7-0fbf7dd5f281) # Aptos Improvement Proposals (AIPs) 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? [Section titled “What do AIPs cover?”](#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? [Section titled “What is this section of the docs mostly about?”](#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. # AIP-115 - Stateless Accounts [AIP-115](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-115.md) covers stateless accounts. ## General FAQ [Section titled “General FAQ”](#general-faq) ### What is a Stateless Account? [Section titled “What is a Stateless Account?”](#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? [Section titled “How is it different from a regular account?”](#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? [Section titled “How does it work?”](#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 [Section titled “Technical Details FAQ”](#technical-details-faq) ### What is the default auth\_key for Stateless Accounts? [Section titled “What is the default auth\_key for Stateless Accounts?”](#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? [Section titled “What is the sequence number of a Stateless Account?”](#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? [Section titled “When is the account resource automatically created?”](#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? [Section titled “Does creating the account resource incur extra gas cost?”](#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? [Section titled “Any behavior change to account module at the Move level?”](#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? [Section titled “Can users force-create the account resource upfront?”](#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? [Section titled “Any behavior change to API?”](#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? [Section titled “Do existing accounts get affected?”](#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? [Section titled “Do dApps / CEX need to change anything?”](#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? [Section titled “Is this compatible with Orderless Transactions?”](#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? [Section titled “Will all accounts become Stateless in the future?”](#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 [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 [Section titled “General FAQ”](#general-faq) ### What is in the Block Epilogue Transaction? [Section titled “What is in the Block Epilogue Transaction?”](#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 [Section titled “Compatibility FAQ”](#compatibility-faq) ### What does this mean for my dApp? [Section titled “What does this mean for my dApp?”](#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? [Section titled “What apps are likely to be affected?”](#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? [Section titled “What can I do to process the new transaction type?”](#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 APIs The Aptos Blockchain network can be accessed by several APIs, depending on your use-case. ## Aptos Fullnode [Section titled “Aptos Fullnode”](#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. [Aptos Fullnode REST API (Mainnet) ](/build/apis/fullnode-rest-api-reference?network=mainnet)Mainnet API playground for Aptos Fullnode REST API [Aptos Fullnode REST API (Testnet) ](/build/apis/fullnode-rest-api-reference?network=testnet)Testnet API playground for Aptos Fullnode REST API [Aptos Fullnode REST API (Devnet) ](/build/apis/fullnode-rest-api-reference?network=devnet)Devnet API playground for Aptos Fullnode REST API ## Indexer [Section titled “Indexer”](#indexer) [Indexer GraphQL API ](/build/indexer)This GraphQL API offers a high-level, opinionated GraphQL interface to read state from the Aptos Blockchain. It's ideal for interacting with NFTs, Aptos Objects, or custom Move contracts. Learn more about the Indexer-powered GraphQL API here. [Transaction Stream API ](/build/indexer/txn-stream)This GRPC API streams historical and real-time transaction data to an indexing processor. It's used by Aptos Core Indexing and can also support custom app-specific indexing processors for real-time blockchain data processing. Learn more here. ## Faucet (Only Testnet/Devnet) [Section titled “Faucet (Only Testnet/Devnet)”](#faucet-only-testnetdevnet) [Faucet API ](/build/apis/faucet-api)This API provides the ability to receive test tokens on devnet. Its primary purpose is the development and testing of applications and Move contracts before deploying them to mainnet. On testnet you can mint at the mint page. 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 [Section titled “Aptos Labs operated API Deployments”](#aptos-labs-operated-api-deployments) [Aptos Labs](https://aptoslabs.com) operates a deployment of these APIs on behalf of [Aptos Foundation](https://aptosfoundation.org/) 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 [Aptos Build](https://build.aptoslabs.com/) account. # Aptos Labs Aptos Build [Aptos Build](https://build.aptoslabs.com) 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 Aptos Build at the dedicated [Aptos Build docs site](https://build.aptoslabs.com/docs). # Data Providers If you want to access aptos blockchain data but don’t need it in real-time. We have a few options that will let you access this data using SQL or UIs for building dashboards. This type of data is often used for analytics since it allows for aggregations. ## Review of data endpoints [Section titled “Review of data endpoints”](#review-of-data-endpoints) Hitting the full node directly will give the latest data (will be missing historical unless it’s an archival full node) using [REST API](/build/apis#aptos-fullnode) Indexer layer on top of this will provide a [GRPC transaction stream](/build/indexer/txn-stream/aptos-hosted-txn-stream) On top of this transaction stream, we’ve built out some product logic tables that can be queried through [GraphQL](/build/indexer/) Since the logic to parse out transaction 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 [Section titled “SQL Tables”](#sql-tables) Indexer defines several processors that create different database tables. ### Core tables [Section titled “Core tables”](#core-tables) These are parsed directly from node API response, one option is to split it out into the following tables: * Blocks - version, block height, epoch, timestamp * Transactions - version, sender, entry function, gas * Signatures - signature types, signer, fee payer address * Events - type and data for events We store data as table items, resources or modules * (write set) changes - change index, change type, resource address * Table items - table key, table handle, key (content and type), value (content and type) * (move) resources - resource address, resource type, data * (move) modules - bytecode for deployed modules ## Vendors of off-chain data [Section titled “Vendors of off-chain data”](#vendors-of-off-chain-data) Most of our data vendors only provide core datasets. A [subset of vendors](https://aptosfoundation.org/currents/aptos-on-chain-data-capabilities-with-dune-nansen-and-other-providers) is listed below ### Google bigquery public dataset [Section titled “Google bigquery public dataset”](#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](/_vercel/image?url=_astro%2Fbq_sql.BOgk74gq.png\&w=1280\&q=100) We also have sample analytics queries [using the above resources](https://github.com/aptos-labs/explorer/tree/main/analytics) ### Dune [Section titled “Dune”](#dune) We have a dashboard here: ### Allium [Section titled “Allium”](#allium) Data source for many downstream vendors such as defillama and rwa.xyz. Raw data is available: They also have transfers for stablecoins ### Artemis [Section titled “Artemis”](#artemis) Provides [topline metrics](https://app.artemis.xyz/asset/aptos) as well as chart builder ### Nansen [Section titled “Nansen”](#nansen) Provides [topline metrics](https://app.nansen.ai/macro/blockchains?chain=aptos) with additional functionality with account. ### Sentio [Section titled “Sentio”](#sentio) They have a guide here: Data is found in data source -> external project -> sentio/aptos-overview They also provide stack tracing of transactions ### RWA.xyz [Section titled “RWA.xyz”](#rwaxyz) Data can be found here: You’ll need to make an account to access stablecoin details. ### Flipside [Section titled “Flipside”](#flipside) Has pivoted from dashboard vendor to more of a vibe coding tool. ## Other vendors [Section titled “Other vendors”](#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) # 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: ## Using the faucet [Section titled “Using the faucet”](#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 [Section titled “Using the faucet in a wallet”](#using-the-faucet-in-a-wallet) Most wallets, such as [Petra](https://aptosfoundation.org/ecosystem/project/petra) or [Pontem](https://aptosfoundation.org/ecosystem/project/pontem-wallet) will have a faucet button for devnet. See full list of [Aptos Wallets](https://aptosfoundation.org/ecosystem/projects/wallets). ### Using the faucet in the Aptos CLI [Section titled “Using the faucet in the Aptos CLI”](#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 aptos account fund-with-faucet --account 0xd0f523c9e73e6f3d68c16ae883a9febc616e484c4998a72d8899a1009e5a89d6 --amount 100000000 ``` ### Using the faucet in the TypeScript SDK [Section titled “Using the faucet in the TypeScript SDK”](#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 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 [Section titled “Using the faucet in the Go SDK”](#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 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 [Section titled “Calling the faucet: Other languages not supported by SDKs”](#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 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 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 [Section titled “Fullnode REST API Explorer”](#fullnode-rest-api-explorer) [Mainnet Fullnode REST API ](https://fullnode.mainnet.aptoslabs.com/v1/spec#/)REST API Explorer for Mainnet [Testnet Fullnode REST API ](https://fullnode.testnet.aptoslabs.com/v1/spec#/)REST API Explorer for Testnet [Devnet Fullnode REST API ](https://fullnode.devnet.aptoslabs.com/v1/spec#/)REST API Explorer for Devnet ## Understanding rate limits [Section titled “Understanding rate limits”](#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 [Aptos Build docs](https://build.aptoslabs.com/docs/start/billing). ## Viewing current and historical state [Section titled “Viewing current and historical state”](#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. Note Ensure the [fullnode](/network/nodes/networks) you are communicating with is up-to-date. The fullnode must reach the version containing your transaction to retrieve relevant data from it. There can be latency from the fullnodes retrieving state from [validator fullnodes](/network/blockchain/fullnodes), which in turn rely upon [validator nodes](/network/blockchain/validator-nodes) as the source of truth. 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 [Section titled “Reading state with the View function”](#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 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 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. # Fullnode API Reference # CLI 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 [Section titled “📥 Install the Aptos CLI”](#-install-the-aptos-cli) [Mac ](/build/cli/install-cli/install-cli-mac)Install Aptos CLI via homebrew [Windows ](/build/cli/install-cli/install-cli-windows)Install Aptos CLI on Windows via powershell script or pre-compiled binary [Linux ](/build/cli/install-cli/install-cli-linux)Install Aptos CLI on Linux via shell script or pre-compiled binary [Advanced (Install Specific Versions) ](/build/cli/install-cli/install-cli-specific-version)Build a specific version of the Aptos CLI from source ## ⚙️ Setup the Aptos CLI [Section titled “⚙️ Setup the Aptos CLI”](#️-setup-the-aptos-cli) [Setup the CLI ](/build/cli/setup-cli)Setup and configure the Aptos CLI [Advanced (Move Prover) ](/build/cli/setup-cli/install-move-prover)Setup and install the Move Prover ## 🛠️ Using the Aptos CLI [Section titled “🛠️ Using the Aptos CLI”](#️-using-the-aptos-cli) [Move Contracts ](/build/cli/working-with-move-contracts)Compile, Publish, Simulate, and Benchmark Move Contracts [Trying things On-chain ](/build/cli/trying-things-on-chain)Interact with Aptos, create accounts, query accounts, use a hardware device like Ledger [Running a Local Network ](/build/cli/running-a-local-network)Run a local node / network # Formatting Move Contracts `movefmt` is a formatter tool that makes Move code much easier to write, read, and maintain — greatly improving the development experience on Aptos. ## Installation [Section titled “Installation”](#installation) `movefmt` is integrated into the Aptos CLI. To begin using it, first install it using the CLI update command. ```shellscript # Install movefmt for first time usage aptos update movefmt ``` To install a specific version of `movefmt`: ```shellscript # 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 [Section titled “Format your code”](#format-your-code) Similar to compilation and testing, you can use the following command to format the Move package: ```shellscript # Format the Move package aptos move fmt ``` Different ways of emitting the formatting result is supported: ```shellscript # 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: ```plaintext 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 # 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 [Section titled “Feedback”](#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 the Aptos CLI on Linux 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 [Section titled “Install via Script”](#install-via-script) 1. In the terminal, use one of the following commands: ```shellscript curl -fsSL "https://aptos.dev/scripts/install_cli.sh" | sh ``` Or use the equivalent `wget` command: ```shellscript wget -qO- "https://aptos.dev/scripts/install_cli.sh" | sh ``` Caution If you are getting `Illegal instruction` errors when running the CLI, it may be due to your CPU not supporting SIMD instructions. Specifically for older non-SIMD processors or Ubuntu x86\_64 docker containers on ARM Macs, you may need to run the following command instead to skip SIMD instructions: ```shellscript curl -fsSL "https://aptos.dev/scripts/install_cli.sh" | sh -s -- --generic-linux ``` 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. Note If you would like to update the Aptos CLI to the latest version, you can run `aptos update`. # Install via Package Manager (Optional) [Section titled “Install via Package Manager (Optional)”](#install-via-package-manager-optional) Note When installing Aptos via a package manager, please update it through the same package manager in the future. ### Arch Linux [Section titled “Arch Linux”](#arch-linux) #### Install via AUR (Arch User Repository) [Section titled “Install via AUR (Arch User Repository)”](#install-via-aur-arch-user-repository) ```shellscript git clone https://aur.archlinux.org/aptos-bin.git cd aptos-bin makepkg -si ``` or use an AUR helper like `yay`: ```shellscript yay -S aptos-bin ``` # Install via Pre-Compiled Binaries (Backup Method) [Section titled “Install via Pre-Compiled Binaries (Backup Method)”](#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. Note When using the pre-compiled binaries method, you can update the Aptos CLI by deleting your existing installation, then following the installation steps again. # Install the Aptos CLI on Mac For Mac, the easiest way to install the Aptos CLI is with the package manager `brew`. # Installation [Section titled “Installation”](#installation) 1. Ensure you have brew installed . 2. Open a new terminal and enter the following commands. ```shellscript brew update brew install aptos ``` 3. Open another terminal and run aptos help to verify the CLI is installed. ```shellscript aptos help ``` Caution If `brew` does not work for you, you can try the steps here: [Install via Script](#install-via-script) or [Install via Pre-Compiled Binaries](#install-via-pre-compiled-binaries-backup-method).) # Upgrading the CLI [Section titled “Upgrading the CLI”](#upgrading-the-cli) Upgrading the CLI with brew just takes 2 commands: ```shellscript brew update brew upgrade aptos ``` # Install via Script [Section titled “Install via Script”](#install-via-script) 1. In the terminal, use one of the following commands: ```shellscript curl -fsSL "https://aptos.dev/scripts/install_cli.sh" | sh ``` Or use the equivalent `wget` command: ```shellscript 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. Note If you would like to update the Aptos CLI to the latest version, you can run `aptos update`. # Install via Pre-Compiled Binaries (Backup Method) [Section titled “Install via Pre-Compiled Binaries (Backup Method)”](#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. Note When using the pre-compiled binaries method, you can update the Aptos CLI by deleting your existing installation, then following the installation steps again. # Install Specific Aptos CLI Versions (Advanced) 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 [Section titled “Install on macOS / Linux”](#install-on-macos--linux) 1. Follow the steps to . 2. Ensure you have cargo installed by following the steps on . 3. Build the CLI tool: 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 by running target/cli/aptos help. These help instructions also serve as a useful detailed guide for specific commands. ## Install on Windows [Section titled “Install on Windows”](#install-on-windows) 1. Follow the steps to build Aptos from source . 2. Ensure you have cargo installed by following the steps on . 3. Build the CLI tool: 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 by running target\cli\aptos.exe help. These help instructions also serve as a useful detailed guide for specific commands. # Install the Aptos CLI on Windows 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 [Section titled “Install via PowerShell Script”](#install-via-powershell-script) 1. In PowerShell, run the install script: ```powershell 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. Note If you would like to update the Aptos CLI to the latest version via script, you can run `aptos update`. # Install via Package Manager (Optional) [Section titled “Install via Package Manager (Optional)”](#install-via-package-manager-optional) Note When installing Aptos via a package manager, please update it through the same package manager in the future. ### If you have [Scoop](https://scoop.sh/) installed, you can run the following command to install the Aptos CLI: [Section titled “If you have Scoop installed, you can run the following command to install the Aptos CLI:”](#if-you-have-scoop-installed-you-can-run-the-following-command-to-install-the-aptos-cli) ```powershell 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: [Section titled “If you have Chocolatey installed, you can run the following command to install the Aptos CLI:”](#if-you-have-chocolatey-installed-you-can-run-the-following-command-to-install-the-aptos-cli) ```powershell choco install aptos ``` ### If you have [winget](https://winget.run/) installed, you can run the following command to install the Aptos CLI: [Section titled “If you have winget installed, you can run the following command to install the Aptos CLI:”](#if-you-have-winget-installed-you-can-run-the-following-command-to-install-the-aptos-cli) ```powershell winget install aptos ``` # Install via Pre-Compiled Binaries (Backup Method) [Section titled “Install via Pre-Compiled Binaries (Backup Method)”](#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`. Note You may want to add this path to your PATH environment variable to simplify calling the Aptos CLI going forward. 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`. Note When installing with pre-compiled binaries, you can update the Aptos CLI by deleting your existing installation, then following the installation steps again. Caution If neither of the above methods work, you will have to build the CLI from source by following these steps: [Install Specific Aptos CLI Versions (Advanced)](/build/cli/install-cli/install-cli-specific-version) # Managing a Network Node via Aptos CLI 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 aptos node --help ``` # Running a Public Network (Advanced) Caution If you just want to run your own local network for testing, you can learn how to do that [here](/build/cli/running-a-local-network). ## Genesis ceremonies [Section titled “Genesis ceremonies”](#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 [Section titled “Generating genesis”](#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 [Section titled “Prepare aptos-core”](#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 [Section titled “The layout file”](#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 [Section titled “Building the Aptos Framework”](#building-the-aptos-framework) From your Aptos-core repository, build the framework and package it: ```shellscript 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 [Section titled “The ValidatorConfiguration file”](#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 cargo run --package aptos -- genesis generate-keys --output-dir bobs ``` 2. Generate your `ValidatorConfiguration`: ```shellscript 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 [Section titled “Generating a genesis and waypoint”](#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 cargo run --package aptos -- genesis generate-genesis --local-repository-dir genesis ``` ### Starting an `aptos-node` [Section titled “Starting an aptos-node”](#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 ## Basics [Section titled “Basics”](#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 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 [Section titled “Alternate Modes”](#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 [Section titled “Benchmarking”](#benchmarking) ```shellscript 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.** Note To minimize measurement errors, the benchmark harness executes the same transaction multiple times. For this reason, it may take a while for the benchmark task to complete. ### Gas Profiling [Section titled “Gas Profiling”](#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 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 Local Network via Aptos CLI 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 [Section titled “Starting A Local Network”](#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 aptos node run-local-testnet --with-indexer-api ``` Caution Note: Despite the name (`local-testnet`), this has nothing to do with the Aptos testnet, it will run a network entirely local to your machine. You should expect to see an output similar to this: ```shellscript 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. Caution If you ran into an error, look at the common errors below to debug. Common Errors On Network Startup ### Address Already In Use [Section titled “Address Already In Use”](#address-already-in-use) ```shellscript 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 [Section titled “Too many open files error”](#too-many-open-files-error) ```shellscript 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 ulimit -n 1048576 ``` ### Docker is not available [Section titled “Docker is not available”](#docker-is-not-available) ```shellscript 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 aptos node run-local-testnet --help ``` ## Using The Local Network [Section titled “Using The Local Network”](#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 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 aptos move publish --profile --package-dir /opt/git/aptos-core/aptos-move/move-examples/hello_blockchain --named-addresses HelloBlockchain=local ``` ### Configuring the TypeScript SDK [Section titled “Configuring the TypeScript SDK”](#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 [Section titled “Resetting the local network”](#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 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 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 aptos node run-local-testnet --force-restart --assume-yes ``` # Setup CLI Initial Configuration 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. Caution If you still need to install the CLI, follow [these steps](/build/cli/install-cli/install-cli-specific-version). 1. Run `aptos init` and follow the instructions in the command line. Note To use default settings, you can provide no input and just press “Enter”. For example: ```shellscript aptos init ``` ```shellscript 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) [Section titled “(Optional) Creating Named Configurations (Profiles)”](#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 aptos init --profile bob ``` ```shellscript aptos account fund-with-faucet --profile bob ``` ```shellscript { "Result": "Added 100000000 Octas to account 0x63169727b08fc137b8720e451f7a90584ccce04c301e151daeadc7b8191fdfad" } ``` ## (Optional) Setting Up Shell Completion [Section titled “(Optional) Setting Up Shell Completion”](#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 aptos config generate-shell-completions --shell --output-file ``` Example command for [`oh my zsh`](https://ohmyz.sh/): ```shellscript aptos config generate-shell-completions --shell zsh --output-file ~/.oh-my-zsh/completions/_aptos ``` ## (Optional) Global Config [Section titled “(Optional) Global Config”](#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 aptos config set-global-config --config-type global ``` You should see: ```json { "Result": { "config_type": "Global" } } ``` # Install the Move Prover 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) [Section titled “Installation through Aptos CLI (Recommended)”](#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`. Note Environment variables `BOOGIE_EXE` and `Z3_EXE` will be set automatically after installation. Please make sure they are in effect in the current environment. ## Installation through `aptos-core` (Not Recommended) [Section titled “Installation through aptos-core (Not Recommended)”](#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` Note `dev_setup.sh -p` updates your `~./profile` with environment variables to support the installed Move Prover tools. You may need to set `.bash_profile` or `.zprofile` or other setup files for your shell. 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 aptos move prove --package-dir aptos-move/move-examples/hello_prover/ ``` ## Troubleshooting [Section titled “Troubleshooting”](#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 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 Follow the steps below to quickly get started. 1. Initialize Run the following to initialize a package using the `hello-blockchain` template: ```shellscript 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. hello\_blockchain.move ```move module hello_blockchain::message { use std::error; use std::signer; use std::string; use aptos_framework::event; #[test_only] use std::debug; //:!:>resource struct MessageHolder has key { message: string::String, } //<:!:resource #[event] struct MessageChange has drop, store { account: address, from_message: string::String, to_message: string::String, } /// There is no message present const ENO_MESSAGE: u64 = 0; #[view] public fun get_message(addr: address): string::String acquires MessageHolder { assert!(exists(addr), error::not_found(ENO_MESSAGE)); borrow_global(addr).message } public entry fun set_message(account: signer, message: string::String) acquires MessageHolder { let account_addr = signer::address_of(&account); if (!exists(account_addr)) { move_to(&account, MessageHolder { message, }) } else { let old_message_holder = borrow_global_mut(account_addr); let from_message = old_message_holder.message; event::emit(MessageChange { account: account_addr, from_message, to_message: copy message, }); old_message_holder.message = message; } } #[test(account = @0x1)] public entry fun sender_can_set_message(account: signer) acquires MessageHolder { let msg: string::String = string::utf8(b"Running test for sender_can_set_message..."); debug::print(&msg); let addr = signer::address_of(&account); aptos_framework::account::create_account_for_test(addr); set_message(account, string::utf8(b"Hello, Blockchain")); assert!( get_message(addr) == string::utf8(b"Hello, Blockchain"), ENO_MESSAGE ); } } ``` 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 [Section titled “Learn More”](#learn-more) [Smart Contracts ](/build/smart-contracts)Learn how to build in Move [Create Package ](/build/smart-contracts/create-package)Get started by learning how to create a Move package # Trying Things On-Chain With Aptos CLI 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 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 Note You can install the Aptos CLI by following [these steps](/build/cli) if you have not done so already. 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 aptos init --profile ``` Once you have a funded account you can send coins between accounts with the `transfer` command like this: ```shellscript aptos account transfer --account superuser --amount 100 ``` You should see a result like: ```json { "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. Note To have more control over what your generated credentials look like, instead of `aptos init`, you can use: 1. `aptos key generate --vanity-prefix 0x` 2. `aptos account fund-with-faucet --account ` Note however that addresses are different than keys. # Use Hardware Ledger via the Aptos CLI Using a hardware wallet like Ledger is the most secure way to sign transactions on `mainnet` as your private key never leaves your device. Caution The `Ledger Nano S` has limited memory and may not be able to sign many transactions on Aptos. If you are trying to sign a transaction that is too big for your device to handle, you will get the error `Wrong raw transaction length`. ## Initial Setup [Section titled “Initial Setup”](#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. Note Whenever you want to sign using your Ledger you will need to plug it in, unlock it, and open the Aptos app before running any CLI commands. 6. Create a new Ledger profile in the Aptos CLI ```shellscript aptos init --profile --ledger ``` Then follow the terminal prompts like so: ```text 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 [Section titled “Signing Using Ledger”](#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. Note This process works for any command that requires a signature, whether that’s to transfer coins, publish a Move contract, interact with a contract, etc. For example, if you wanted to publish a Move package like the [`hello_blockchain`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain) demo contract you could follow the above steps then run: ```shellscript aptos move publish --profile --named-addresses hello_blockchain= ``` You should see a response like: ```shellscript 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 [Section titled “Authentication key rotation”](#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](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) 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. Caution Before you start this tutorial make sure you have completed the [key rotation guide](/build/guides/key-rotation). 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 aptos node run-localnet ``` The localnet is ready when it prints out: ```shellscript Applying post startup steps... Setup is complete, you can now use the localnet! ``` Note If you are a power user on MacOS or Linux, the following command can be used to start a fresh localnet as a background process: ```shellscript mkdir -p localnet-data aptos node run-localnet \ --assume-yes \ --test-dir localnet-data \ --force-restart & export LOCALNET_PID=$! ``` You can then stop the localnet at any point with the following command: ```shellscript kill $LOCALNET_PID ``` 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 aptos key generate \ --assume-yes \ --output-file private-key-a \ --vanity-prefix 0xaaa ``` Example output ```shellscript { "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 aptos init \ --assume-yes \ --network local \ --private-key-file private-key-a \ --profile hot-wallet-1 ``` Example output ```shellscript 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](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) 1000 on your Ledger: ```shellscript aptos account rotate-key \ --assume-yes \ --new-derivation-index 1000 \ --profile hot-wallet-1 \ --save-to-profile ledger-wallet-1000 ``` Note As a best practice, this command uses a [BIP44 account index](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) that starts at a large number (1000) to indicate that the account is secured by a rotated authentication key on a Ledger, to ensure it does not conflict with any other existing accounts. This practice aids in profile recovery, as shown below. Follow the instructions from the CLI prompt: ```shellscript Approve rotation proof challenge signature on your Ledger device ``` Example output ```shellscript { "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 aptos config show-profiles --profile hot-wallet-1 aptos config show-profiles --profile ledger-wallet-1000 ``` Example output ```shellscript { "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. Note If you are using a UNIX-like machine: ```shell rm private-key-a rm private-key-b rm private-key-a.pub rm private-key-b.pub ``` Now that you have successfully rotated the authentication key of the hot wallet, you can delete the profiles too: ```shellscript aptos config delete-profile --profile hot-wallet-1 aptos config delete-profile --profile ledger-wallet-1000 ``` Example output ```shellscript { "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](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) offset of 1000, you can easily recover the profile using the [BIP44 account index](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) alone: ```shellscript aptos init \ --assume-yes \ --derivation-index 1000 \ --network local \ --profile ledger-wallet-1000-recovered ``` Example output ```shellscript 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 aptos config show-profiles --profile ledger-wallet-1000-recovered ``` Example output ```shellscript { "Result": { "ledger-wallet-1000-recovered": { "has_private_key": false, "public_key": "0x20ba83f9b9fdab73b0ace8fda26ce24c98cf55060b72b69cfbd25add6a25d09b", "account": "aaac71af5f2a4af4ec2639a15799bf9b945afb061c8bee102b636531c1b00eb5", "rest_url": "http://localhost:8080", "faucet_url": "http://localhost:8081" } } } ``` Note The `aptos init` command first checks the [`account::OriginatingAddress`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) table for determining the account address associated with a public key, so as long as you follow best practices from the [key rotation guide](/build/guides/key-rotation) and only authenticate one account at a time with a private key, you’ll easily be able to recover your profile based on the [BIP44 account index](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) alone. 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 aptos key generate \ --assume-yes \ --output-file private-key-b \ --vanity-prefix 0xbbb ``` Example output ```shellscript { "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 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 Approve rotation proof challenge signature on your Ledger device ``` ```shellscript Approve transaction on your Ledger device ``` Example output ```shellscript { "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 aptos config rename-profile \ --profile ledger-wallet-1000-recovered \ --new-profile-name ledger-wallet-1000-stale ``` Example output ```shellscript { "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 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 Approve rotation proof challenge signature on your Ledger device ``` Example output ```shellscript { "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 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 aptos config delete-profile --profile temporary-hot-wallet aptos config delete-profile --profile ledger-wallet-1000-stale ``` Example output ```shellscript { "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. Note If you are using a UNIX-like machine: ```shell rm private-key-* ``` 9. Clean up Delete the remaining test profile: ```shell aptos config delete-profile --profile ledger-wallet-1000 ``` Then stop the localnet. Note If you are using a UNIX-like machine: ```shell aptos config delete-profile --profile ledger-wallet-1000 kill $LOCALNET_PID rm -fr localnet-data ``` # Look Up On-Chain Account Info Using Aptos CLI Note You can install the Aptos CLI by following [these steps](/build/cli) if you have not done so already. You can look up resources and data an account has on-chain by running the following command: ```shellscript 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 { "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 aptos account list --query modules ``` This will show all modules that an account has. For example: ```json { "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 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). Note Throughout this document there are parts of commands you will have to modify to fit your situation. Those variables will be wrapped in triangle brackets ``. ## 1. Compiling Move [Section titled “1. Compiling Move”](#1-compiling-move) You can compile a Move package by running: ```shellscript aptos move compile --package-dir ``` Note The package directory is the folder which contains the `Move.toml` file. 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 [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 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 [Section titled “2. Unit Testing Move Contracts”](#2-unit-testing-move-contracts) The Aptos CLI can also be used to compile and run unit tests locally by running: ```shellscript 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 [Section titled “Printing Debugging Information”](#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 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 [Section titled “3. Generating Test Coverage Reports”](#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 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 [Section titled “4. Publishing Move Contracts”](#4-publishing-move-contracts) To publish a Move contract, you will need to run: ```shellscript 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. You can estimate the gas fees associated with publishing your Move contract by using the [Gas Profiler](/build/cli/working-with-move-contracts/local-simulation-benchmarking-and-gas-profiling). Caution By default Move contracts publish their source code. To avoid publishing with source code, publish with the `--included-artifacts none` argument. Since the Aptos blockchain is inherently open by design, note that even without source access it is possible to regenerate Move source from published Move bytecode. ## 5. Running Published Contracts [Section titled “5. Running Published Contracts”](#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 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 [Section titled “6. (Optional) Formally Verifying Move Scripts”](#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 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 ## Package info [Section titled “Package info”](#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 [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`. Note Set your working directory to [`aptos-move/move-examples/cli_args`](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/cli_args) to follow along: ```shellscript cd /aptos-core/aptos-move/move-examples/cli_args ``` ## Deploying the package [Section titled “Deploying the package”](#deploying-the-package) Start by mining a vanity address for Ace, who will deploy the package: ```shellscript aptos key generate \ --vanity-prefix 0xace \ --output-file ace.key ``` Output ```shellscript { "Result": { "Account Address:": "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46", "PublicKey Path": "ace.key.pub", "PrivateKey Path": "ace.key" } } ``` Note The exact account address should vary for each run, though the vanity prefix should not. Store Ace’s address in a shell variable, so you can call it inline later on: ```shellscript # Your exact address will vary ace_addr=0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46 ``` Fund Ace’s account with the faucet (only works on devnet): ```shellscript aptos account fund-with-faucet --account $ace_addr ``` Output ```shellscript { "Result": "Added 100000000 Octas to account acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46" } ``` Now publish the package under Ace’s account: ```shellscript aptos move publish \ --named-addresses test_account=$ace_addr \ --private-key-file ace.key \ --assume-yes ``` Output ```json { "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 [Section titled “Entry functions”](#entry-functions) The only module in the package, `cli_args.move`, defines a simple `Holder` resource with fields of various data types: ```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 /// 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()`: Note To pass vectors (including nested vectors) as arguments from the command line, use JSON syntax escaped with quotes! ```shellscript 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 { "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 { "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 aptos move run \ --json-file entry_function_arguments.json \ --private-key-file ace.key \ --assume-yes ``` Output ```json { "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" } } ``` Note If you are trying to run the example yourself don’t forget to substitute Ace’s actual address for `` in `entry_function_arguments.json`! ## View functions [Section titled “View functions”](#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 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 aptos move view \ --function-id $ace_addr::cli_args::reveal \ --type-args \ 0x1::account::Account \ 0x1::account::Account \ --args address:$ace_addr ``` ```shellscript aptos move view --json-file view_function_arguments.json ``` Note If you are trying to run the example yourself don’t forget to substitute Ace’s actual address for `` in `view_function_arguments.json` (twice)! ```json { "function_id": "::cli_args::reveal", "type_args": [ "0x1::account::Account", "0x1::account::Account" ], "args": [ { "type": "address", "value": "" } ] } ``` ```shellscript { "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 [Section titled “Script functions”](#script-functions) The package also contains a script, `set_vals.move`, which is a wrapper for the setter function: ```move 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 aptos move compile --named-addresses test_account=$ace_addr ``` Output ```json { "Result": [ "acef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46::cli_args" ] } ``` Next, run `aptos move run-script`: ```shellscript 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 { "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 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 { "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 { "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 aptos move view \ --function-id $ace_addr::cli_args::reveal \ --type-args \ 0x1::account::Account \ 0x1::chain_id::ChainId \ --args address:$ace_addr ``` ```json { "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! ♥" } ] } ``` Note As of the time of this writing, the `aptos` CLI only supports script function arguments for vectors of type `u8`, and only up to a vector depth of 1. Hence `vector
` and `vector>` are invalid script function argument types. # Local Simulation, Benchmarking & Gas Profiling ## Overview [Section titled “Overview”](#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). Note Local simulations do not result in any to the on-chain state. ## Deploying the Example Contract [Section titled “Deploying the Example Contract”](#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 cd aptos-move/move-examples/hello_blockchain ``` Then publish the package using the following command. ```shellscript 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. Note Note: publishing the package to devnet/testnet is just one way to set up the stage for local simulation and is not the only one possible. Alternatively you can use a local node, or simulate transactions that do not need to have code published first, such as scripts and even the package publishing transaction itself. ## Local Simulation [Section titled “Local Simulation”](#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 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" } } ``` Note Local and remote simulation shall produce identical results. ## Benchmarking [Section titled “Benchmarking”](#benchmarking) To measure the running time(s) of your transaction, use the `--benchmark` option. ```shellscript 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.** Note To minimize measurement errors, the benchmark harness executes the same transaction multiple times. For this reason, it may take a while for the benchmark task to complete. ## Gas Profiling [Section titled “Gas Profiling”](#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 [Section titled “Using the Gas Profiler”](#using-the-gas-profiler) The gas profiler can be invoked by appending the `--profile-gas` option. ```shellscript 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/index.html) ### Understanding the Gas Report [Section titled “Understanding the Gas Report”](#understanding-the-gas-report) The gas report consists of three sections that help you to understand the gas usage through different lenses. #### Flamegraphs [Section titled “Flamegraphs”](#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](/_vercel/image?url=_astro%2Fgas-profiling-flamegraph-0.8n15Htax.png\&w=1280\&q=100) 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](/_vercel/image?url=_astro%2Fgas-profiling-flamegraph-1.BaQ9e-VE.png\&w=1280\&q=100) There is also “Search” button in the top-right corner that allows to match certain items and highlight them. ![gas-profiling-flamegraph-2.png](/_vercel/image?url=_astro%2Fgas-profiling-flamegraph-2.DpBftyhf.png\&w=1280\&q=100) #### Cost Break-down [Section titled “Cost Break-down”](#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](/_vercel/image?url=_astro%2Fgas-profiling-cost-break-down-table.CxVgnmyq.png\&w=640\&q=100) #### Full Execution Trace [Section titled “Full Execution Trace”](#full-execution-trace) The final section of the gas report is the full execution trace of the transaction that looks like this: ```text 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 ## Background [Section titled “Background”](#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). Note If you would like to follow along, start by completing the [Arguments in JSON](/build/cli/working-with-move-contracts/arguments-in-json-tutorial) tutorial steps! 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 [Section titled “Account creation”](#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 aptos key generate \ --vanity-prefix 0xbee \ --output-file bee.key ``` Output ```shellscript { "Result": { "PublicKey Path": "bee.key.pub", "PrivateKey Path": "bee.key", "Account Address:": "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc" } } ``` Note The exact account address should vary for each run, though the vanity prefix should not. Store Bee’s address in a shell variable, so you can call it inline later on: ```shellscript # Your exact address should vary bee_addr=0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc ``` Fund Bee’s account using the faucet: ```shellscript aptos account fund-with-faucet --account $bee_addr ``` Output ```shellscript { "Result": "Added 100000000 Octas to account beec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc" } ``` Ace can now create a multisig account: ```shellscript aptos multisig create \ --additional-owners $bee_addr \ --num-signatures-required 2 \ --private-key-file ace.key \ --assume-yes ``` Output ```shellscript { "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 # Your address should vary multisig_addr=0x57478da34604655c68b1dcb89e4f4a9124b6c0ecc1c59a0931d58cc4e60ac5c5 ``` ## Inspect the multisig [Section titled “Inspect the multisig”](#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 aptos move view \ --function-id 0x1::multisig_account::num_signatures_required \ --args \ address:"$multisig_addr" ``` Output ```shellscript { "Result": [ "2" ] } ``` ```shellscript aptos move view \ --function-id 0x1::multisig_account::owners \ --args \ address:"$multisig_addr" ``` Output ```shellscript { "Result": [ [ "0xbeec980219d246581cef5166dc6ba5fb1e090c7a7786a5176d111a9029b16ddc", "0xacef1b9b7d4ab208b99fed60746d18dcd74865edb7eb3c3f1428233988e4ba46" ] ] } ``` ```shellscript aptos move view \ --function-id 0x1::multisig_account::last_resolved_sequence_number \ --args \ address:"$multisig_addr" ``` Output ```shellscript { "Result": [ "0" ] } ``` ```shellscript aptos move view \ --function-id 0x1::multisig_account::next_sequence_number \ --args \ address:"$multisig_addr" ``` Output ```shellscript { "Result": [ "1" ] } ``` ## Enqueue a publication transaction [Section titled “Enqueue a publication transaction”](#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 aptos move build-publish-payload \ --named-addresses test_account=$multisig_addr \ --json-output-file publication.json \ --assume-yes ``` Output ```shellscript { "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 aptos multisig create-transaction \ --multisig-address $multisig_addr \ --json-file publication.json \ --store-hash-only \ --private-key-file ace.key \ --assume-yes ``` Output ```shellscript { "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 aptos move view \ --function-id 0x1::multisig_account::last_resolved_sequence_number \ --args \ address:"$multisig_addr" ``` Output ```shellscript { "Result": [ "0" ] } ``` However, the next sequence number has been incremented because a transaction has been enqueued: ```shellscript aptos move view \ --function-id 0x1::multisig_account::next_sequence_number \ --args \ address:"$multisig_addr" ``` Output ```shellscript { "Result": [ "2" ] } ``` The multisig transaction enqueued on-chain can now be inspected: ```shellscript aptos move view \ --function-id 0x1::multisig_account::get_transaction \ --args \ address:"$multisig_addr" \ u64:1 ``` Output ```shellscript { "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 [Section titled “Enqueue a governance parameter transaction”](#enqueue-a-governance-parameter-transaction) Now have Bee enqueue a governance parameter setter transaction, storing the entire transaction payload on-chain: ```shellscript 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 { "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 aptos move view \ --function-id 0x1::multisig_account::next_sequence_number \ --args \ address:"$multisig_addr" ``` Output ```shellscript { "Result": [ "3" ] } ``` Now both the publication and parameter transactions are pending: ```shellscript aptos move view \ --function-id 0x1::multisig_account::get_pending_transactions \ --args \ address:"$multisig_addr" ``` Output ```shellscript { "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 [Section titled “Execute the publication transaction”](#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 aptos move view \ --function-id 0x1::multisig_account::can_be_executed \ --args \ address:"$multisig_addr" \ u64:1 ``` Output ```shellscript { "Result": [ false ] } ``` Before Bee votes, however, she verifies that the payload hash stored on-chain matches the publication entry function JSON file: ```shellscript aptos multisig verify-proposal \ --multisig-address $multisig_addr \ --json-file publication.json \ --sequence-number 1 ``` Output ```shellscript { "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 aptos multisig approve \ --multisig-address $multisig_addr \ --sequence-number 1 \ --private-key-file bee.key \ --assume-yes ``` Output ```shellscript { "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 aptos move view \ --function-id 0x1::multisig_account::can_be_executed \ --args \ address:"$multisig_addr" \ u64:1 ``` Output ```shellscript { "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 aptos multisig execute-with-payload \ --multisig-address $multisig_addr \ --json-file publication.json \ --private-key-file bee.key \ --max-gas 10000 \ --assume-yes ``` Note Pending the resolution of [#8304](https://github.com/aptos-labs/aptos-core/issues/8304), the transaction simulator (which is used to estimate gas costs) is broken for multisig transactions, so you will have to manually specify a max gas amount. 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 [Section titled “Execute the governance parameter transaction”](#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 aptos move view \ --function-id 0x1::multisig_account::can_be_executed \ --args \ address:"$multisig_addr" \ u64:2 ``` Output ```shellscript { "Result": [ false ] } ``` Before Ace votes, however, he verifies that the payload stored on-chain matches the function arguments he expects: ```shellscript 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 { "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 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 { "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 aptos multisig approve \ --multisig-address $multisig_addr \ --sequence-number 2 \ --private-key-file ace.key \ --assume-yes ``` Output ```shellscript { "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 aptos multisig execute \ --multisig-address $multisig_addr \ --private-key-file ace.key \ --max-gas 10000 \ --assume-yes ``` Output ```shellscript { "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" ``` # create-aptos-dapp `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? [Section titled “Why use create-aptos-dapp?”](#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 [Section titled “Prerequisites”](#prerequisites) * [node and npm](https://nodejs.org/en) (npm ≥ 5.2.0) * [Python 3.6+](https://www.python.org/) ## Using `create-aptos-dapp` [Section titled “Using create-aptos-dapp”](#using-create-aptos-dapp) 1. Navigate to the directory you want to work in. ```shellscript cd your/workspace ``` 2. Install create-aptos-dapp. * npx ```shellscript npx create-aptos-dapp@latest ``` * pnpx ```shellscript pnpx create-aptos-dapp@latest ``` * yarn ```shellscript yarn create aptos-dapp ``` * pnpm ```shellscript 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](/_vercel/image?url=_astro%2Fcad-video.CDIWZeeH.gif\&w=640\&q=100) ## Templates [Section titled “Templates”](#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 [Section titled “Current Templates”](#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://learn.aptoslabs.com/en/dapp-templates/boilerplate-template) * [NFT minting dapp Template](https://learn.aptoslabs.com/en/dapp-templates/nft-minting-template) * [Token minting dapp Template](https://learn.aptoslabs.com/en/dapp-templates/token-minting-template) * [Token staking dapp Template](https://learn.aptoslabs.com/en/dapp-templates/token-staking-template) * [Custom indexer template](https://learn.aptoslabs.com/en/dapp-templates/custom-indexer-template) ## Tools `create-aptos-dapp` utilizes [Section titled “Tools create-aptos-dapp utilizes”](#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 ## Why do we use `import.meta.env`? [Section titled “Why do we use import.meta.env?”](#why-do-we-use-importmetaenv) 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` [Section titled “I tried to publish my dapp to a live server but getting 404 error”](#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? [Section titled “What is Tailwind CSS?”](#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`? [Section titled “What is shadcn/ui?”](#what-is-shadcnui) 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? [Section titled “How to modify the theme?”](#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? [Section titled “How to add components?”](#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 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? [Section titled “How to add colors?”](#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? [Section titled “How to add dark mode?”](#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). # Get Started Content for build/get-started could not be fully rendered due to component compatibility issues. # Developer Setup 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: * Next.js ```shellscript pnpx create-next-app@latest ``` * Vite (TS) ```shellscript pnpx create vite my-aptos-app --template react-ts ``` 2. Install @aptos-labs/ts-sdk ```shellscript npm i @aptos-labs/ts-sdk ``` 3. Setup TS SDK [TS SDK Quickstart ](/build/sdks/ts-sdk/quickstart)See how to setup your account, network, use the faucet, send / simulate transactions, and more 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 [Indexer ](/build/indexer)Efficiently query for on-chain state like balances, transaction activity, token data, and more [TS SDK Examples ](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/examples/typescript)20+ Examples of how to use the TS SDK [Aptos Build ](https://build.aptoslabs.com/)Hitting rate limits for Fullnode API / Indexers? Get an API Key here * Smart Contract ```shellscript pnpx create-next-app@latest ``` * Create Aptos Dapp ```shellscript pnpx create vite my-aptos-app --template react-ts ``` * Next.js 1. Install CLI [Aptos CLI ](/build/cli)Instructions for how to install Aptos CLI 2. Setup Editor or IDE Add the following extensions to your editor of choice to make Move Development easier * JetBrains IDE [Move Language ](https://plugins.jetbrains.com/plugin/14721-move-language)Language server and syntax highlighter for JetBrains IDEs like CLion, Rust Rover, WebStorm, IntelliJ * VSCode [Aptos Move Analyzer ](https://marketplace.visualstudio.com/items?itemName=MoveBit.aptos-move-analyzer)Language server and syntax highlighter for VSCode 3. Create Smart Contract Navigate to your application folder and initialize a new smart contract by doing: ```shellscript 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 Smart Contract Guide ](/build/guides/build-e2e-dapp/1-create-smart-contract#what-is-a-movetoml-file)An easy todo list guide for how to setup a smart contract with Move * Vite (TS) [Move Language ](https://plugins.jetbrains.com/plugin/14721-move-language)Language server and syntax highlighter for JetBrains IDEs like CLion, Rust Rover, WebStorm, IntelliJ * JetBrains IDE [Aptos Move Analyzer ](https://marketplace.visualstudio.com/items?itemName=MoveBit.aptos-move-analyzer)Language server and syntax highlighter for VSCode * VSCode 1. Install create-aptos-dapp Run the below command to install a dApp from a template in seconds: * npx ```shellscript npx create-aptos-dapp@latest ``` * pnpx ```shellscript 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](/_vercel/image?url=_astro%2Fcad-video.CDIWZeeH.gif\&w=640\&q=100) 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 [Create Aptos Dapp ](/build/create-aptos-dapp)Get more information about the tool [Templates ](/build/create-aptos-dapp#templates)Browse premade templates [FAQ ](/build/create-aptos-dapp/faq)Get help for common issues and questions * npx ```shellscript npx create-aptos-dapp@latest ``` * pnpx ```shellscript pnpx create-aptos-dapp@latest ``` # Ethereum to Aptos Cheatsheet To learn more about the differences and similarities see [Aptos Learn](https://learn.aptoslabs.com/en/tutorials/ethereum-to-aptos-guide/cheat-sheet?workshop=eth-to-aptos) ### High Level Overview [Section titled “High Level Overview”](#high-level-overview) | Feature | Ethereum | Aptos | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | | **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 | | **Account Addresses** | 160-bit | 256-bit | | **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 | | **Storage Mindset** | Contract-based storage | Account centric mindset for code and data | | **Example Code** | [ERC-20](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20) | [Fungible Asset](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/fungible_asset.move) | | **Caller ID** | `msg.sender` | `&signer` reference | | **Upgradeability** | Proxy patterns | Direct module upgrades | | **Safety & Security** | Vulnerable to attacks like reentrancy | Mitigates common vulnerabilities | | **Dispatch Type** | Dynamic dispatch | Static dispatch | | **FT Standard** | [ERC-20](https://docs.openzeppelin.com/contracts/4.x/erc20) | [Coin](/build/smart-contracts/aptos-coin) (legacy) and [Fungible Asset](/build/smart-contracts/fungible-asset) | | **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) | | **Blockchain Interaction** | [Ethers.js library](https://docs.ethers.org/v6/) | [Aptos Typescript SDK](/build/sdks/ts-sdk) | ### Comparing Token Standards in Detail [Section titled “Comparing Token Standards in Detail”](#comparing-token-standards-in-detail) | | Solidity | Move (Aptos) | | ---------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Token Structure** | Each token is its own contract. | Every token is a typed `Coin` or `FungibleAsset` using a single, reusable contract. | | **Token Standard** | Must conform to standards like ERC20; implementations can vary. | Uniform interface and implementation for all tokens. | | **Balance Storage** | Balances stored in contract using a mapping structure. | **Resource-Oriented Balance**: Balances stored as a resource in the user’s account. Resources cannot be arbitrarily created, ensuring integrity of token value. | | **Transfer Mechanism** | Tokens can be transferred without receiver’s explicit permission. | Except for specific cases (like AptosCoin), Tokens generally require receiver’s `signer` authority for transfer. | ### Comparing EVM and Move VM in Detail [Section titled “Comparing EVM and Move VM in Detail”](#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. | # Solana to Aptos Cheatsheet 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) | # Learn from Guides Choose one of the following guides to learn how to use Aptos for your use cases! [Your First Transaction ](/build/guides/first-transaction)How to generate, submit and verify a transaction to the Aptos blockchain. [Your First NFT ](/build/guides/your-first-nft)Learn the Aptos Ttoken interface and how to use it to generate your first NFT. [Your First Fungible Asset ](/build/guides/first-fungible-asset)Learn how to deploy and manage a fungible asset. [Your First Coin ](/build/guides/first-coin)Learn how to deploy and manage a coin. [Your First Move Module ](/build/guides/first-move-module)Write your first Move module for the Aptos blockchain. [Your First Dapp ](/build/guides/build-e2e-dapp)Learn how to build your first dapp. Focuses on building the user interface for the dapp. [Your First Multisig ](/build/guides/first-multisig)Learn how to perform assorted operations using K-of-N multi-signer authentication. # Aptos Keyless ## Integrate with Aptos Keyless accounts [Section titled “Integrate with Aptos Keyless accounts”](#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 [Section titled “Using an IAM Provider? Integrate with Aptos Federated Keyless”](#using-an-iam-provider-integrate-with-aptos-federated-keyless) * [Federated Keyless](/build/guides/aptos-keyless/federated-keyless) ## Example [Section titled “Example”](#example) Visit this page to learn more [Simple Example](/build/guides/aptos-keyless/simple-example) [aptos-keyless-example](https://stackblitz.com/edit/vitejs-vite-3fuvtu?embed=1\&file=README.md) # Federated Keyless ## Federated Keyless [Section titled “Federated Keyless”](#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 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. Caution **Losing access to the JWK owner account compromises the Federated Keyless accounts created with it** The JWK owner account is the only account that can update the JWKS. If you lose access to the JWK owner account, you will not be able to update the JWKS and the Federated Keyless accounts created with it will stop working in the case of a key rotation. Users will be unable to validate their JWT tokens as they will be signed with the new key whos public key is not registered on the Aptos blockchain. 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](https://aptos.dev/en/build/smart-contracts/move-reference?page=aptos-framework%2Fdoc%2Fjwks.md#0x1_jwks_update_federated_jwk_set). Caution **Handling key rotations** Whenever there is a key rotation of the JWKS, it is important to update the JWKS registered on chain promptly to avoid any loss of access to Federated Keyless accounts. See [here](/build/guides/aptos-keyless/federated-keyless/key-considerations) for more info. 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 ## Federated Keyless Key Considerations [Section titled “Federated Keyless Key Considerations”](#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. # FAQ ## Federated Keyless FAQs [Section titled “Federated Keyless FAQs”](#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](https://aptos.dev/en/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 The Federated Keyless Example shows how to set up a Federated Keyless account using Auth0 as the IAM provider. Explore the code in [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/tree/main/examples/federated-keyless-example/). The Keyless Simple Example is currently undergoing maintenance. Please check back later. # How Keyless Works 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](/_vercel/image?url=_astro%2Fkeyless-overview.UO_96U-T.png\&w=1280\&q=100 "Keyless overview") ## Overview [Section titled “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 [Section titled “Flow: Deriving a keyless account for a user in a dApp”](#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](/_vercel/image?url=_astro%2Fkeyless-account.DOrLBuB3.png\&w=1280\&q=100 "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(epk,ρ), where ρ 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 addr=H("uid-123","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 addr. But how can the dApp authorize TXN from this account at addr? We discuss that next. ## Flow: Obtaining a zero-knowledge proof before transacting [Section titled “Flow: Obtaining a zero-knowledge proof before transacting”](#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](/_vercel/image?url=_astro%2Fkeyless-proof.BIDeEdPP.png\&w=1280\&q=100 "Keyless proof diagram") **Step 1**: The dApp sends all the necessary public information (i.e., epk, GPK) and private information (i.e., JWT, signature σ\_G from Google, EPK blinding factor ρ, and pepper r) to the **prover service**. **Step 2**: The prover derives the user’s address addr and computes a zero-knowledge proof (ZKP) π for the keyless relation R\_keyless (described below). This proof acts as a **privacy-preserving** digital certificate, and binds the user’s address addr to the ephemeral public key epk. The prover then sends π to the dApp. In order to bind the epk with the user’s address 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 epk in its `nonce` field, and (3) contains the same information as in the address, without leaking anything about the JWT, its signature σ\_G, ρ, or r. More formally, the ZKP π convinces a verifier (i.e., the blockchain), who has public inputs (addr,epk,GPK), that the prover knows secret inputs (jwt,σ\_G,ρ,r) such that the relation R\_keyless depicted below holds: ![Keyless relation diagram](/_vercel/image?url=_astro%2Fkeyless_relation.Dlvebw35.png\&w=1280\&q=100 "Keyless relation diagram") Recall from before that the signed JWT itself binds the blockchain address addr to epk, so that epk can sign transactions for 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 R\_keyless). Next, we show how the dApp can now authorize TXNs from addr. ## Flow: Sending a TXN from a keyless account [Section titled “Flow: Sending a TXN from a keyless account”](#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](/_vercel/image?url=_astro%2Fkeyless-signing.I2tyCOJT.png\&w=1280\&q=100 "Keyless signing diagram") **Step 1**: The dApp obtains an ephemeral signature σ\_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 π, the ephemeral public key epk, and the ephemeral signature σ\_eph to the blockchain validators. **Step 3**: To check the TXN is validly-signed, the validators perform several steps: (1) check that epk has not expired, (2) fetch the user’s address addr from the TXN, (3) verify the ZKP against (addr,epk,GPK), and (4) verify the ephemeral signature σ\_eph on the TXN against the epk. If all these checks pass, they can safely execute the TXN. ## Want more? [Section titled “Want more?”](#want-more) The key ideas behind keyless accounts are also explained in this 20 minute presentation below. [Play](https://youtube.com/watch?v=sKqeGR4BoI0) # Keyless Integration Guide Note **Keyless Account Scoping** Use of the \*\**Aptos Keyless Integration Guide*\*\* will allow for the integration of keyless accounts directly into your application. This means that blockchain accounts are scoped to your application’s domain (logging in with your Google account on dApp A and logging in with your Google account on dApp B will create separate accounts). Stay tuned for more to come on Aptos’ plan to allow Keyless accounts to be used portably across applications. To provide feedback, get support, or be a design partner as we enhance Aptos Keyless, join us here: 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 [Section titled “Example Implementation”](#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 [Section titled “1. Present the user with a “Sign In with \[IdP\]” button on the UI”](#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` Note This implementation is an example of how to store the `EphemeralKeyPair` in local storage. Different implementations may be used according to your application’s needs. ```typescript /** * 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 [Section titled “2. Handle the callback by parsing the token and create a Keyless account for the user”](#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) [Section titled “3. Store the KeylessAccount in local storage (Optional)”](#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 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 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 [Section titled “4. Submit transactions to the Aptos blockchain”](#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 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 [Section titled “Aptos Keyless Benefits”](#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 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 [Section titled “Registering your dApp with Google”](#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](/_vercel/image?url=_astro%2Fgoogle-credentials-nav.Du05EmhD.png\&w=640\&q=100 "Google Credentials navigation screenshot") 2. Click on “Create Credentials” and select “OAuth client ID.” ![Google create credentials screenshot](/_vercel/image?url=_astro%2Fgoogle-create-credentials.CMY28IKo.png\&w=640\&q=100 "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 . 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 () for handling authentication responses from Google. ## Registering your dApp with Apple [Section titled “Registering your dApp with Apple”](#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](/_vercel/image?url=_astro%2Fapple-dev-program.CC7sWnZt.png\&w=1280\&q=100 "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 . 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 () for handling authentication responses from Apple. # Keyless Terminology and FAQ ## Terminology [Section titled “Terminology”](#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., ) * `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 [Section titled “Ceremony”](#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 [Section titled “Frequently Asked Questions”](#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 Explore the code in [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/tree/main/examples/keyless-example/). The Keyless Simple Example is currently undergoing maintenance. Please check back later. This is a live Keyless example on StackBlitz. Follow the instructions in the `README.md` to add your own Google `client_id`. Explore the code in [aptos-keyless-example repository](https://github.com/aptos-labs/aptos-keyless-example/tree/main/examples/keyless-example/). [aptos-keyless-example](https://stackblitz.com/edit/vitejs-vite-3fuvtu?embed=1\&file=README.md) # Build an End-to-End Dapp on Aptos 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/developer-docs/tree/main/apps/nextra/pages/en/build/guides/build-e2e-dapp). ## Chapters [Section titled “Chapters”](#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) ## Prerequisites [Section titled “Prerequisites”](#prerequisites) You must have: * [node and npm](https://nodejs.org/en/) 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 [Section titled “Setup”](#setup) In this section, we will create a `my-first-dapp` directory to hold our project files, both client-side code (React based) and the Move code (our smart contract). For that, 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 `npx create-aptos-dapp@latest` to create the project. ```shellscript npx create-aptos-dapp@latest ``` 3. Follow the instructions to create the project. * 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. The tool will create the project in a directory with the same name as the project and install the required dependencies. Follow the `Next Steps` instructions. Now let’s [create a smart contract](/build/guides/build-e2e-dapp/1-create-smart-contract). # 1. Create a Smart Contract 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](/_vercel/image?url=_astro%2Fbuild-e2e-dapp-img-1-1.CD8k67X7.png\&w=640\&q=100) ### What is a `Move.toml` file? [Section titled “What is a Move.toml file?”](#what-is-a-movetoml-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? [Section titled “Why sources directory?”](#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? [Section titled “What is the tests directory?”](#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 [Section titled “Create a Move module”](#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 PROJECT_NAME=my-aptos-dapp VITE_APP_NETWORK=devnet VITE_APTOS_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 ``` Note You just created a new account on the Aptos (dev) network! Yay! You can see it by going to the Aptos Explorer Devnet network view, pasting the `VITE_MODULE_PUBLISHER_ACCOUNT_ADDRESS` value from your `.env` file into the search field, and clicking on the dropdown option! The Boilerplate template comes with a pre generated `message_board.move` file, a relevant test file and a `Move.toml` file. 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 module todolist_addr::todolist { } ``` 2. Open the `Move.toml` file. 3. Add the following code to that Move file: ```toml [addresses] todolist_addr='_' ``` Note A Move module is stored under an address (so when it published anyone can access it using that address); the syntax for a Move module is ```move module :: { } ``` In our module, the `account-address` is `todolist_addr` (a variable we just declared on the `Move.toml` file in the previous step that holds an `address`), and the `module-name` is `todolist` (a random name we selected). ### What is the `'_'` in the `Move.toml` file? [Section titled “What is the '\_' in the Move.toml file?”](#what-is-the-_-in-the-movetoml-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. `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 ... namedAddresses: { todolist_addr: process.env.VITE_MODULE_PUBLISHER_ACCOUNT_ADDRESS, }, ... ``` Note Later, when we will run the each of the `move` commands, it will run these scripts, and replace the `'_'` with the actual account address that assigned to the `todolist_addr` variable. ### Our contract logic [Section titled “Our contract logic”](#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 `task_created` event. 3. Let an account mark their task as completed. Note Creating an event is not mandatory yet useful if dapps/users want to monitor data, such as how many people create a new task, using the [Aptos Indexer](/build/indexer). We can start with defining a `TodoList` struct, that holds the: * tasks array * new task event * 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: * the task ID - derived from the TodoList task counter. * address - 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 ... struct TodoList has key { tasks: Table, set_task_event: event::EventHandle, task_counter: u64 } struct Task has store, drop, copy { task_id: u64, address: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: 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. 3. On the top of the module, import those types by adding: ```move ... use aptos_framework::event; use std::string::String; use aptos_std::table::Table; ... ``` 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 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::message_board", "1cecfef9e239eff12fb1a3d189a121c37f48908d86c0e9c02ec103e0a05ddebb::todolist" ] } ``` At this point, we have successfully compiled our Move module. Yay! We can also delete the `message_board.move` file, as we won’t be using it. And remove the `message_board_addr` from the `Move.toml` file. 3. Let’s make sure everything is still working by running the `npm run move:compile` command again. 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 [Section titled “Create list function”](#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` ```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 public entry fun create_list(account: &signer){ let tasks_holder = TodoList { tasks: table::new(), set_task_event: account::new_event_handle(account), 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. ### Create task function [Section titled “Create task function”](#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 public entry fun create_task(account: &signer, content: String) acquires TodoList { // gets the signer address let signer_address = signer::address_of(account); // gets the TodoList resource let todo_list = borrow_global_mut(signer_address); // increment task counter let counter = todo_list.task_counter + 1; // creates a new Task let new_task = Task { task_id: counter, address: signer_address, content, completed: false }; // adds the new task into the tasks table table::upsert(&mut todo_list.tasks, counter, new_task); // sets the task counter to be the incremented counter todo_list.task_counter = counter; // fires a new task created event event::emit_event( &mut borrow_global_mut(signer_address).set_task_event, new_task, ); } ``` 2. Since we now use two new modules - signer and table (you can see it being used in `signer::` and `table::`) - we need to import these modules. At the top of the file, add those two use statements: ```move use std::signer; use aptos_std::table::{Self, Table}; // This one we already have, need to modify it ``` **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 `task_created` event that holds the new Task data. `emit_event` is an `aptos-framework` function that accepts a reference to the event handle and a message. In our case, we are passing the function a reference (using the sign &) to the account’s `TodoListresource` `set_task_event` property as the first argument and a second message argument which is the new Task we just created. Remember, we have a `set_task_event` property in our `TodoList` struct. ### Complete task function [Section titled “Complete task function”](#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 public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { // gets the signer address let signer_address = signer::address_of(account); // gets the TodoList resource let todo_list = borrow_global_mut(signer_address); // gets the task matches the task_id let task_record = table::borrow_mut(&mut todo_list.tasks, task_id); // update 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 look for the task with the provided `task_id` on the `todo_list.tasks` table. * Finally, we update that task completed property to be true. Now try to compile the code: 2. Run: `npm run move:compile` 3. Another `Unbound` error? To fix this, add a `use` statement to use the `account` module. ```move use aptos_framework::account; ``` 4. run `npm run move:compile` again. ### Add validations [Section titled “Add validations”](#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 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: ```move public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { // gets the signer address let signer_address = signer::address_of(account); // assert signer has created a list assert!(exists(signer_address), 1); // gets the TodoList resource let todo_list = borrow_global_mut(signer_address); // assert task exists assert!(table::contains(&todo_list.tasks, task_id), 2); // gets the task matched the task_id let task_record = table::borrow_mut(&mut todo_list.tasks, task_id); // assert task is not completed assert!(task_record.completed == false, 3); // update 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 // Errors const ENOT_INITIALIZED: u64 = 1; const ETASK_DOESNT_EXIST: u64 = 2; const ETASK_IS_COMPLETED: u64 = 3; ``` Now we can update our asserts with these constants: ```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), ENOT_INITIALIZED); ... } public entry fun complete_task(account: &signer, task_id: u64) acquires TodoList { // gets the signer address let signer_address = signer::address_of(account); assert!(exists(signer_address), ENOT_INITIALIZED); // gets the TodoList resource let todo_list = borrow_global_mut(signer_address); // assert task exists assert!(table::contains(&todo_list.tasks, task_id), ETASK_DOESNT_EXIST); // gets the task matched the task_id let task_record = table::borrow_mut(&mut todo_list.tasks, task_id); // assert task is not completed assert!(task_record.completed == false, ETASK_IS_COMPLETED); // update 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 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 [Section titled “Write tests”](#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 use one function to test the whole flow of the app and will have it in the `todolist.move` file. The test steps are: ```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 #[test] public entry fun test_flow() { } ``` Note: Test functions use the `#[test]` annotation. Note we need to use `entry` here because we are testing an `entry` function. 3. Update the test function to be: ```move #[test(admin = @0x123)] public entry fun test_flow(admin: signer) acquires TodoList { // creates an admin @todolist_addr account for test account::create_account_for_test(signer::address_of(&admin)); // initialize contract with admin account create_list(&admin); // creates a task by the admin account create_task(&admin, string::utf8(b"New Task")); let task_count = event::counter(&borrow_global(signer::address_of(&admin)).set_task_event); assert!(task_count == 1, 4); let todo_list = borrow_global(signer::address_of(&admin)); assert!(todo_list.task_counter == 1, 5); let task_record = table::borrow(&todo_list.tasks, todo_list.task_counter); assert!(task_record.task_id == 1, 6); assert!(task_record.completed == false, 7); assert!(task_record.content == string::utf8(b"New Task"), 8); assert!(task_record.address == signer::address_of(&admin), 9); // updates task as completed complete_task(&admin, 1); let todo_list = borrow_global(signer::address_of(&admin)); let task_record = table::borrow(&todo_list.tasks, 1); assert!(task_record.task_id == 1, 10); assert!(task_record.completed == true, 11); assert!(task_record.content == string::utf8(b"New Task"), 12); assert!(task_record.address == 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: 3. At the top of the file, add this `use` statement: ```move use std::string::{Self, String}; // already have it, need to modify ``` 4. Run the `npm run move:test` command. If all goes right, we should see a success message like: ```move Running Move unit tests [ PASS ] 0x1cecfef9e239eff12fb1a3d189a121c37f48908d86c0e9c02ec103e0a05ddebb::todolist::test_flow Test result: OK. Total tests: 1; passed: 1; failed: 0 { "Result": "Success" } ``` 5. Let’s add one more test to make sure our `complete_task` function works as expected. Add another test function with: ```move #[test(admin = @0x123)] #[expected_failure(abort_code = ENOT_INITIALIZED)] public entry fun account_can_not_update_task(admin: signer) acquires TodoList { // creates an admin @todolist_addr account for test account::create_account_for_test(signer::address_of(&admin)); // account can not toggle task as no list was created 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. 6. Run the `aptos move test` command. If all goes right, we should see a success message like: ```shellscript 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 [Section titled “Publish todolist module to chain”](#publish-todolist-module-to-chain) 1. Run: `npm run move:compile` We are getting some *Unused alias* errors. This is because we added the `string` alias before since we use it in our tests. But we don’t use this alias in our smart contract code. This is why we are getting this error when we want to compile the module but not are getting it when we only run tests. To fix it, we can add a `use` statement that would be used only in tests. Add the following `use` statement where we have all of our import statements. ```move use std::string::String; // change to this ... #[test_only] use std::string; // add this ``` 2. Run: `npm run move:test` and `npm run move:compile` - all should work without errors. 3. Run: `npm run move:publish` 4. Enter `yes` in the prompt. **Oh no! We got an error!** It complains about an account mismatch. 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 Transaction submitted: https://explorer.aptoslabs.com/txn/0x68dadf24b9ec29b9c32bd78836d20032de615bbef5f10db580228577f7ca945a?network=devnet Code was successfully deployed to object address 0x2bce4f7bb8a67641875ba5076850d2154eb9621b0c021982bdcd80731279efa6 { "Result": "Success" } ``` 6. You can now head to the [Aptos Explorer](https://explorer.aptoslabs.com/) link and view the transaction details. You can also see the module published on chain by looking for the object address. Note Check out your `.env` file and see the `VITE_MODULE_ADDRESS` variable, it is set to the object address of the published module. 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 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 [Section titled “Set 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` library. 1. Run: `npm run dev` At this point you should have your app running on , 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 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 [Section titled “Our dapp UI”](#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](/_vercel/image?url=_astro%2Fbuild-e2e-dapp-img-3.C7OGrGlZ.png\&w=1280\&q=100) 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 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. 2. Import wallet from the wallet adapter React provider: ```tsx import { useWallet } from "@aptos-labs/wallet-adapter-react"; ``` 2. Extract the account object from the wallet adapter: ```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. 3. Next, we want to fetch the account’s TodoList resource. Begin by importing `useEffect` by using `import { useEffect } from "react";` Let’s add a `useEffect` hook to our file that would call a function to fetch the resource whenever our account address changes: ```tsx function App() { import { useEffect } from "react" ... useEffect(() => { fetchList(); }, [account?.address]); ... } ``` 4. Before creating our `fetchList` function, let’s also create a local state to store whether the account has a list: ```tsx function App ( ... const [accountHasList, setAccountHasList] = useState(false); ... ) ``` also import `useState` using `import { useState, useEffect } from "react";` 5. Import `MODULE_ADDRESS` variable using `import { MODULE_ADDRESS } from "./constants";`. This is the address of the module we published in the previous chapter. 6. Import `aptosClient` using `import { aptosClient } from "./utils/aptosClient";`. This is a client `create-aptos-dapp` created for us to interact with the chain. 7. Our `useEffect` hook is calling a `fetchList` function; let’s create it: ```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); } }; ``` 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`. 7. Let’s update our UI based on the `accountHasList` state: ```tsx return ( <>
{!accountHasList && (
)}
); ``` We now have an **Add new list** button that appears only if the account doesn’t have a list. 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 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 const { account, signAndSubmitTransaction } = useWallet(); ``` 2. Add an `onClick` event to the new list button: ```tsx ``` 3. Update the import statement from `@aptos-labs/wallet-adapter-react` to also import the `InputTransactionData` type and ```tsx import { useWallet, InputTransactionData, } from "@aptos-labs/wallet-adapter-react"; ``` 4. Add the `addNewList` function: ```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 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 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 const [transactionInProgress, setTransactionInProgress] = useState(false); ``` 7. Update our `addNewList` function to update the local state: ```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 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 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 [Section titled “Fetch tasks”](#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 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 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 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 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 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 // adds the new task into the tasks table table::upsert(&mut todo_list.tasks, counter, new_task); ``` So the object we built is: ```typescript { 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 {!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 [Section titled “Add task”](#add-task) 1. Update our UI with an *add task* input: ```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 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 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 our Add ` ``` 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 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 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 [Section titled “Mark task as completed”](#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 onCheckboxChange(event, task.task_id)} /> ``` 2. Create the `onCheckboxChange` function: ```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 ... {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 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 [Section titled “Overview”](#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 [Section titled “Infrastructure”](#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 [Section titled “Address Standards”](#address-standards) ### Addresses [Section titled “Addresses”](#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 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 [Section titled “Account Standards”](#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 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 [Section titled “Asset Standards”](#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. Note APT, the native token of Aptos, is a migrated coin. This means it can be used with both the Coin and Fungible Asset standards. ### Coin Standard (tl;dr) [Section titled “Coin Standard (tl;dr)”](#coin-standard-tldr) 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) [Section titled “Fungible Asset Standard (tl;dr)”](#fungible-asset-standard-tldr) 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 [Section titled “Retrieving Balances”](#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 [Section titled “Coin (and migrated coins) Balances”](#coin-and-migrated-coins-balances) Note Note: This includes APT and any other coin that was migrated to a fungible asset. If the asset is a migrated coin, use this over fungible asset balance. The fungible asset balance will not include the coin portion of the balance. 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 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 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 [Section titled “Fungible Asset Balances”](#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 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 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-reference#tag/accounts/GET/accounts/%7Baddress%7D/balance/%7Basset_type%7D) to get the balance of a migrated coin or fungible asset. ## Tracking Balance Changes [Section titled “Tracking Balance Changes”](#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 [Section titled “Coin Balance Changes”](#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 [Section titled “Fungible Asset Balance Changes”](#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%5C_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 [Section titled “Coins migrated to Fungible Asset Balance Changes”](#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 ```plaintext 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%5C_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 [Section titled “Transferring Assets”](#transferring-assets) ### Coin (or migrated coin) Transfers [Section titled “Coin (or migrated coin) Transfers”](#coin-or-migrated-coin-transfers) Note APT, the native token of Aptos, is a migrated coin. Please use the `aptos_account::transfer` functions to transfer APT tokens. 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. ### Fungible Asset Transfers [Section titled “Fungible Asset Transfers”](#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. Caution Note: This will not create an account for the user if it hasn’t been created yet. You will need to call `0x1::aptos_account::create_account(account address)` to create the account before the user can submit transactions. ## Testing [Section titled “Testing”](#testing) In order to check that everything is working correctly, we’ve provided these checks. ### Balance Checks [Section titled “Balance 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 [Section titled “Balance Change / Transfer Checks”](#balance-change--transfer-checks) #### Check Coin Transfer [Section titled “Check Coin Transfer”](#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 [Section titled “Check Fungible Asset Transfer”](#check-fungible-asset-transfer) To test a transfer, you can fund an account with the fungible asset here 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 [Section titled “Stablecoin Addresses”](#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://aptosfoundation.org/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 [Section titled “FAQ”](#faq) ### What is the finality of a transaction? [Section titled “What is the finality of a transaction?”](#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? [Section titled “What is the transaction fee on a transaction?”](#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 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 [Section titled “Step 1: Pick an SDK”](#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 [Section titled “Step 2: Install the CLI”](#step-2-install-the-cli) [Install the precompiled binary for the Aptos CLI](/build/cli). *** ## Step 3: Run the example [Section titled “Step 3: Run the example”](#step-3-run-the-example) * TypeScript Clone the `aptos-ts-sdk` repo and build it: ```shellscript 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 cd examples/typescript/ ``` Install the necessary dependencies: ```shellscript pnpm install ``` Run the TypeScript [`your_coin`](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/your_coin.ts) example: ```shellscript pnpm run your_coin ``` The application will complete, printing: ```shellscript Bob's initial MoonCoin balance: 0. Alice mints herself 100 MoonCoin. Alice transfers 100 MoonCoin to Bob. Bob's updated MoonCoin balance: 100. ``` * Python Clone the `aptos-core` repo: ```shellscript git clone https://github.com/aptos-labs/aptos-core ``` Navigate to the Python SDK directory: ```shellscript cd aptos-core/ecosystem/python/sdk ``` Install the necessary dependencies: ```shellscript 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 poetry run python -m examples.your_coin ~/aptos-core/aptos-move/move-examples/moon_coin ``` ### Step 3.1: Build the package [Section titled “Step 3.1: Build the package”](#step-31-build-the-package) The example run will pause with the following output: ```shellscript === 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 cd ~/aptos-core/aptos-move/move-examples/moon_coin ``` Next, build the package using the CLI: ```shellscript 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 [Section titled “Step 3.2: Completing the example”](#step-32-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 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 [Section titled “Step 4: MoonCoin in depth”](#step-4-mooncoin-in-depth) ### Step 4.1: Building and publishing the MoonCoin package [Section titled “Step 4.1: Building and publishing the MoonCoin package”](#step-41-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: * TypeScript 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 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 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 ```python 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 [Section titled “Step 4.2: Understanding the MoonCoin module”](#step-42-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. Note ManagedCoin framework [`ManagedCoin`](https://github.com/aptos-labs/aptos-core/blob/f81ccb01f00227f9c0f36856fead4879f185a9f6/aptos-move/framework/aptos-framework/sources/managed_coin.move#L1) is a simple coin management framework for coins directly managed by users. It provides convenience wrappers around `mint` and `burn`. ```move 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 [Section titled “Step 4.3: Understanding coins”](#step-43-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`. Note The entity that creates a new coin gains the capabilities for minting, burning, and freezing. *** #### Step 4.3.1: Initializing a coin [Section titled “Step 4.3.1: Initializing a coin”](#step-431-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. Note MoonCoin calls this `initialize` function automatically upon package publishing. *** #### Step 4.3.2: Registering a coin [Section titled “Step 4.3.2: Registering a coin”](#step-432-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 [Section titled “Step 4.3.3: Minting a coin”](#step-433-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 [Section titled “Step 4.3.4: Transferring a coin”](#step-434-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. Note There are two separate withdraw and deposit events instead of a single transfer event. ## Supporting documentation [Section titled “Supporting documentation”](#supporting-documentation) * [Aptos CLI](/build/cli) * [TypeScript SDK](/build/sdks/ts-sdk) * [Python SDK](/build/sdks/python-sdk) * [REST API specification](/network/nodes/aptos-api-spec) # Your First Fungible Asset 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 [Section titled “Seeing Fungible Assets In Action”](#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 . 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 git clone https://github.com/aptos-labs/aptos-ts-sdk.git ``` 3. Navigate to the top-level of the cloned repository. ```shellscript cd aptos-ts-sdk ``` 4. Install the SDKs dependencies. ```shellscript pnpm install ``` 5. Build the TypeScript SDK. The example requires the local build of the TypeScript SDK. ```shellscript 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 cd examples/typescript ``` 9. Install the dependencies for the examples. ```shellscript pnpm install ``` 10. Run your\_fungible\_asset. ```shellscript pnpm run your_fungible_asset ``` You should see an output demonstrating how the fungible assets are created and transferred that looks like this: ```shellscript === Addresses === Alice: 0x0c5dd7abbd67db06325fa1a2f37a1833f9a92ff2beb90f32495a9d80972429cd Bob: 0x2a796f4255d5c23684fe6cc521069d684516031bb5ae1ad2061ddc5414450807 Charlie: 0xd824909be65a224f651ff6e9b82ec99ad5707fcef739d1003be20fc69fb93d7a === 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=0x0c5dd7abbd67db06325fa1a2f37a1833f9a92ff2beb90f32495a9d80972429cd --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: 0x0c8a24987bdf2e5e40d8a00f6c97ac55419757bc440097d76959a64dbeafc351 metadata address: 0x2e0e90c701233467f27150f42d365e27e72eb0be8e2a74ee529c31b813bbb321 All the balances in this example refer to balance in primary fungible stores of each account. Alice's initial balance: 0. Bob's initial balance: 0. Charlie's initial balance: 0. Alice mints Charlie 100 coins. Charlie's updated "Tutorial Token" primary fungible store balance: 0. 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 "Tutorial Token" balance: 0. Alice unfreezes Bob's account. Alice burns 50 coins from Bob. Bob's updated "Tutorial Token" balance: 0. Bob transfers 10 coins to Alice as the owner. Alice's updated "Tutorial Token" balance: 0. Bob's updated "Tutorial Token" balance: 0. done. ``` Note If you change the name of the token in the `fa_coin.move` contract you will see the output update with that name. ## Understanding the `fa_coin.move` Example Contract [Section titled “Understanding the fa\_coin.move Example Contract”](#understanding-the-fa_coinmove-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 [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 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; //... } ``` 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 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), utf8(ASSET_SYMBOL), 8, utf8(b"http://example.com/favicon.ico"), utf8(b"http://example.com"), ); 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 #[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 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 [Section titled “Summary”](#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](/build/smart-contracts/move-reference?branch=mainnet\&page=aptos-framework%2Fdoc%2Ffungible_asset.md) for more details on the function signatures and implementation details. # Your First Move Module 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) Note This tutorial is not meant to teach you the fundamentals of Move. That is a longer topic best learned through the [Move Book](/build/smart-contracts/book). ## 1. Setup [Section titled “1. Setup”](#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 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 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`. Note As we are configuring your Aptos CLI for this folder, notice that this setup follows the logic of the blockchain itself: 1. Which network are we working with (default `devnet`, which refreshes every week)? 2. What is the account we are transacting from (creating a unique private key, which in turn generates a cryptographic public key and account address)? 3. How do I pay for “gas”? (For devnet, testnet, and local networks, the Aptos CLI will helpfully fund this account with Aptos Coin, APT). For now, just press ‘enter’ repeatedly to accept all the defaults. You should see a success message like this: ```shellscript --- Aptos CLI is now set up for account 0x9ec1cfa30b885a5c9d595f32f3381ec16d208734913b587be9e210f60be9f9ba as profile default! { "Result": "Success" } ``` Note What you might not have noticed is that the Aptos CLI has created a new hidden folder `.aptos/` with a `.gitignore` and `config.yaml` which contains the account information, including private key, public key, and account address. You can view hidden files with `ls -a` in Unix/Mac terminal or `dir /ah` in Windows. ## 2. (Optional) Explore What You Just Did On-Chain [Section titled “2. (Optional) Explore What You Just Did On-Chain”](#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 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](/_vercel/image?url=_astro%2Fexplorer_devnet.D3PWblc6.png\&w=320\&q=100) 4. Search for your account Paste your newly created address into the search bar. Caution Do not press enter! There is a known bug where searching with Enter does not work. 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](/_vercel/image?url=_astro%2Fexplorer_account.DlK3EDU1.png\&w=1280\&q=100) 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. Note The explorer is an important tool to see the contracts we are deploying, and also offers a way to look up what a contract does. Just search for the address where a contract is deployed and you will be able to see the code for that module. ## 3. Writing and Compiling Your First Module [Section titled “3. Writing and Compiling Your First Module”](#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](/_vercel/image?url=_astro%2Fmove_blockchain.US8AdnUd.png\&w=640\&q=100) 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? [Section titled “What is a Move Module?”](#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 Note If you’re familiar with Rust, you’ll find Move’s syntax very similar. If you’re coming from Solidity, think of modules as reusable smart contract libraries. ### Your First Move Module [Section titled “Your First Move Module”](#your-first-move-module) 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 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 Note Move has some unique characteristics that make it different from other smart contract languages: 1. Resource types are used to represent assets and state that can only exist in one place at a time 2. Ability modifiers like `key`, `store`, and `drop` control how values can be used 3. Explicit acquire annotations tell us which resources a function might access 3. Compile the module Compile the Move module we just created with `aptos move compile --named-addresses my_first_module=default` Note The `--named-addresses` flag maps our module name to our account’s address. In Move, modules must be associated with an address at compile time - we’re using `'default'` which points to the account we just created. You should see a message like this if it succeeded: ```shellscript ❯ 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 [Section titled “4. Testing and Debugging”](#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 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 #[test_only] module my_first_module::message_tests { use std::string; use std::signer; use aptos_framework::account; use my_first_module::message; #[test] fun test_set_and_get_message() { // Set up test account let test_account = account::create_account_for_test(@0x1); // Test setting a message message::set_message(&test_account, string::utf8(b"Hello World")); // Verify the message was set correctly let stored_message = message::get_message(signer::address_of(&test_account)); assert!(stored_message == string::utf8(b"Hello World"), 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 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 ] 0x9ec1cfa30b885a5c9d595f32f3381ec16d208734913b587be9e210f60be9f9ba::message_tests::test_set_and_get_message Test result: OK. Total tests: 1; passed: 1; 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 Note ### Debugging Tips [Section titled “Debugging Tips”](#debugging-tips) 1. Use `debug::print()` in test functions 2. Debug prints will show up automatically during test execution 3. Remember that debug statements will only work in tests, not in production code. They will have no impact on code performance. 4. To debug module state: * Print account addresses with `debug::print(&addr)` * Print string values with `debug::print(&some_string)` * Print boolean conditions with `debug::print(&some_bool)` ## 5. Publishing Your Module [Section titled “5. Publishing Your Module”](#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 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 { "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 [Section titled “(Optional) Seeing Your Contract On-Chain”](#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](/_vercel/image?url=_astro%2Fexplorer_coins.BHJq95xO.png\&w=1280\&q=100) 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](/_vercel/image?url=_astro%2Fexplorer_modules.BhR9Jwde.png\&w=1280\&q=100) 5. Verify the module You should see your “message” module listed Note You can share the explorer link to your module and others can even interact with the module by connecting a wallet. ## 6. Interacting with Your Module [Section titled “6. Interacting with Your Module”](#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 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 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! Note How long did it take you to get through this guide? We want to hear from you! ## Next Steps [Section titled “Next Steps”](#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 [Section titled “Supporting documentation”](#supporting-documentation) * [Account basics](/network/blockchain/accounts) * [TypeScript SDK](/build/sdks/ts-sdk) * [Python SDK](/build/sdks/python-sdk) * [REST API specification](/network/nodes/aptos-api-spec) # Your First Aptos Multisig (Python SDK) 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 Note If you’re coming from Ethereum/Solidity, note that Aptos handles multisig accounts differently. Aptos implements [multisig directly at the protocol level](/network/blockchain/accounts), allowing accounts to require multiple signatures without deploying additional smart contracts. ![Multisig Diagram](/_astro/multisig_chart.CdkJfsah.svg) We’re interfacing with Aptos using the [Aptos Python SDK](/build/sdks/python-sdk). 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 [Section titled “Setup”](#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: * Mac/Linux ```shellscript python3 --version ``` * Windows ```shellscript python --version ``` You should see something like “Python 3.7” or higher. Caution If you see an error or your Python version is below version 3.7, download Python from [python.org](https://python.org/). 3. Create project directory Create a new folder for our project: ```shellscript mkdir my-first-multisig ``` 4. Navigate to project directory Move into this new folder: ```shellscript cd my-first-multisig ``` 5. Create virtual environment Set up an isolated Python environment: * Mac/Linux ```shellscript python3 -m venv venv ``` * Windows ```shellscript 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 * Mac/Linux ```shellscript source venv/bin/activate ``` * Windows ```shellscript .\venv\Scripts\activate ``` Note If you get an error about scripts not being allowed to run, you can enable them with PowerShell: ```powershell Set-ExecutionPolicy RemoteSigned -Scope CurrentUser ``` Confirm by typing `[Y]` and pressing Enter, then retry the activation command. 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 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 [Section titled “Creating the Foundation”](#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: * Mac/Linux ```shellscript touch multisig.py ``` * Windows ```shellscript echo "" > multisig.py ``` 2. Add base code Open `multisig.py` in your IDE (we recommend VSCode or JetBrains) and add the following code: Apache-2.0 ```python # Copyright © Aptos Foundation 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. Note You can get free test tokens on Aptos Devnet or Testnet by using the `FaucetClient`. ## Creating Our Key Holders [Section titled “Creating Our Key Holders”](#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 # 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. Note Each time you run this script it will generate new accounts on the devnet. You’ll need to save the private key and account address if you want to continue working with that account. 2. Add account information display Add this code below `chad = Account.generate()`: ```python 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: * Mac/Linux ```shellscript python3 multisig.py ``` * Windows ```shellscript python multisig.py ``` You should see output showing the addresses, authentication keys, and public keys for each account. For example: ```shellscript === Account addresses === Alice: 0x5323a06f21b04af53fc57367b50d3bbb5675c655bc9bc62f33b5e083d5d06b8b Bob: 0x9f3e94fc92e0076336c122a576304c0b9fa8def13a98c469dce05e0836b9fe5b Chad: 0x1d0e7b790493dcf7bc7ce60bf4ccdcca1d38ce0d7f8dd26d2791a6d3ff6da708 === Authentication keys === Alice: 0x5323a06f21b04af53fc57367b50d3bbb5675c655bc9bc62f33b5e083d5d06b8b Bob: 0x9f3e94fc92e0076336c122a576304c0b9fa8def13a98c469dce05e0836b9fe5b Chad: 0x1d0e7b790493dcf7bc7ce60bf4ccdcca1d38ce0d7f8dd26d2791a6d3ff6da708 === Public keys === Alice: 0x730264a36d4ec90af2e28e1cf9c4d686440598317123469a7c827d4fcdf74715 Bob: 0xcf21e85337a313bdac33d068960a3e52d22ce0e6190e9acc03a1c9930e1eaf3e Chad: 0xa1a2aef8525eb20655387d3ed50b9a3ea1531ef6117f579d0da4bcf5a2e1f76d ``` Note For each user, note the [account address](/network/blockchain/accounts#account-address) and [authentication key](/network/blockchain/accounts#authentication-key) are identical, but the [public key](/network/blockchain/accounts#creating-an-account) is different. 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. ## Configuring the Multisig Vault [Section titled “Configuring the Multisig Vault”](#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 # 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. Note This is like setting up a bank vault’s access rules: “Any two of these three people must approve to access the vault.” 2. Display multisig information Print the multisig account information by adding this code below our newly defined `multisig_address`: ```python 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: * Mac/Linux ```shellscript python3 multisig.py ``` * Windows ```shellscript 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 === 2-of-3 Multisig account === Account public key: 2-of-3 Multi-Ed25519 public key Account address: 0x08cac3b7b7ce4fbc5b18bc039279d7854e4c898cbf82518ac2650b565ad4d364 ``` ## Funding Our Accounts [Section titled “Funding Our Accounts”](#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 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 # 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: * Mac/Linux ```shellscript python3 multisig.py ``` * Windows ```shellscript python multisig.py ``` The output should show each account with its respective balance. For example: ```shellscript === Funding accounts === Alice's balance: 10000000 Bob's balance: 20000000 Chad's balance: 30000000 Multisig balance: 40000000 ``` Caution If any balance shows as 0, you may need to rerun the funding command as the faucet occasionally has temporary issues. Note Values are in octas (1 APT = 100\_000\_000 octas). This is similar to how 1 dollar = 100 cents. ## Creating Our First Multisig Transaction [Section titled “Creating Our First Multisig Transaction”](#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 # 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 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: * Mac/Linux ```shellscript python3 multisig.py ``` * Windows ```shellscript python multisig.py ``` You should see something like: ```shellscript === 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. Note Like gathering two bank managers to sign a withdrawal slip - we need both signatures before the transaction can proceed. Changing the number of managers required from two out of three to 13 out of 22 (or any K-of-N your business needs) is a few more lines of code. ## Submitting the Multisig Transaction [Section titled “Submitting the Multisig Transaction”](#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 # 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 # 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 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: * Mac/Linux ```shellscript python3 multisig.py ``` * Windows ```shellscript python multisig.py ``` You should see something like: ```shellscript === 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 Note You can verify transaction on Aptos Explorer: * Go to [Aptos Explorer](https://explorer.aptoslabs.com/) * Make sure Explorer is set to Devnet (check the top right corner) ![Switching to Devnet network in Aptos Explorer](/_vercel/image?url=_astro%2Fexplorer_devnet.D3PWblc6.png\&w=320\&q=100) * Search for your multisig address or transaction hash * Review the transaction details and balance changes ## Going Further: Advanced Features [Section titled “Going Further: Advanced Features”](#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 [Section titled “Vanity Addresses”](#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 [Section titled “Account Rotation”](#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 [Section titled “Governance & Smart Contracts”](#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 Note Let us know what excites you most about multisig on Aptos! Join our community channels to share your ideas and experiences. ## Next Steps [Section titled “Next Steps”](#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 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 Note This tutorial builds on concepts from the Aptos blockchain. If you’re new to blockchain development, don’t worry - we’ll explain key concepts along the way. You can jump to the full code sample [here](#full-code-sample) or continue reading for a step-by-step walkthrough. ## 1. Setting Up Your Environment [Section titled “1. Setting Up Your Environment”](#1-setting-up-your-environment) * TypeScript 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: * npm ```shellscript npm install @aptos-labs/ts-sdk ``` * yarn ```shellscript yarn add @aptos-labs/ts-sdk ``` * pnpm ```shellscript pnpm add @aptos-labs/ts-sdk ``` 2. Create a project directory Create a new directory for your project: ```shellscript mkdir my-first-transaction cd my-first-transaction ``` 3. Create a new file Create a new file named `transaction.ts`: * Mac/Linux ```shellscript touch transaction.ts ``` * Windows ```shellscript type nul > transaction.ts ``` * Python ```shellscript npm install @aptos-labs/ts-sdk ``` * npm ```shellscript yarn add @aptos-labs/ts-sdk ``` * yarn ```shellscript pnpm add @aptos-labs/ts-sdk ``` * pnpm ```shellscript touch transaction.ts ``` * Mac/Linux ```shellscript type nul > transaction.ts ``` * Windows 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 pip install aptos-sdk ``` 2. Create a project directory Create a new directory for your project: ```shellscript mkdir my-first-transaction cd my-first-transaction ``` 3. Create a new file Create a new file named `transaction.py`: * Mac/Linux ```shellscript touch transaction.py ``` * Windows ```shellscript type nul > transaction.py ``` * Mac/Linux ```shellscript touch transaction.py ``` * Windows ```shellscript type nul > transaction.py ``` ## 2. Creating Test Accounts [Section titled “2. Creating Test Accounts”](#2-creating-test-accounts) * TypeScript 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 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); ``` Note We’re connecting to the Aptos devnet, which is a test network where you can experiment without using real coins. The devnet is reset periodically, so don’t store anything important there. You can explore the full TypeScript SDK source code in the [aptos-ts-sdk repository](https://github.com/aptos-labs/aptos-ts-sdk). 2. Generate accounts Add this code inside your `main()` function to create two accounts - Alice (sender) and Bob (receiver): ```typescript // 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}`); ``` Note Each account has a unique address (like a bank account number) and a keypair (like your login credentials). The address is derived from the public key, while the private key is kept secret and used for signing transactions. For more details on how accounts work in Aptos, see [Account basics](/network/blockchain/accounts). 3. Fund the accounts Add this code after generating the accounts to get test coins from the faucet: ```typescript // 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 npx ts-node transaction.ts ``` You should see output similar to: ```plaintext 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 ``` Note The addresses you see will be different from the ones shown here, as they are randomly generated each time. * Python 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 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()) ``` Note We’re connecting to the Aptos devnet, which is a test network where you can experiment without using real coins. The devnet is reset periodically, so don’t store anything important there. You can explore the full Python SDK source code in the [aptos-python-sdk repository](https://github.com/aptos-labs/aptos-python-sdk). 2. Generate accounts Add this code inside your `main()` function to create two accounts - Alice (sender) and Bob (receiver): ```python # 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()}") ``` Note Each account has a unique address (like a bank account number) and a keypair (like your login credentials). The address is derived from the public key, while the private key is kept secret and used for signing transactions. For more details on how accounts work in Aptos, see [Account basics](/network/blockchain/accounts). 3. Fund the accounts Add this code after generating the accounts to get test coins from the faucet: ```python # 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 python transaction.py ``` You should see output similar to: ```plaintext 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 ``` Note The addresses you see will be different from the ones shown here, as they are randomly generated each time. ## 3. Building a Transaction [Section titled “3. Building a Transaction”](#3-building-a-transaction) * TypeScript 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 Note All data in Aptos transactions is serialized using Binary Canonical Serialization (BCS), a compact and deterministic format designed for blockchain use. The SDK handles this serialization for you. BCS ensures that transaction data is consistently encoded across different platforms and languages, which is critical for a blockchain where the same transaction might be processed by nodes running different implementations. 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 // 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()}`); ``` Note The function `0x1::aptos_account::transfer` is a built-in function in the Aptos framework that transfers coins between accounts. The `0x1` prefix indicates it’s part of the core framework. Behind the scenes, this function calls the [Coin Move module source code](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move) to perform the actual transfer. * Python 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 Note All data in Aptos transactions is serialized using Binary Canonical Serialization (BCS), a compact and deterministic format designed for blockchain use. The SDK handles this serialization for you. BCS ensures that transaction data is consistently encoded across different platforms and languages, which is critical for a blockchain where the same transaction might be processed by nodes running different implementations. 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 # 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)}") ``` Note The function `0x1::aptos_account::transfer` is a built-in function in the Aptos framework that transfers coins between accounts. The `0x1` prefix indicates it’s part of the core framework. Behind the scenes, this function calls the [Coin Move module source code](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move) to perform the actual transfer. The Python SDK uses several key components to construct transactions: * `EntryFunction.natural()` creates a callable Move function reference * `TransactionArgument` with `Serializer` types ensures proper BCS serialization * `TransactionPayload` wraps the entry function for inclusion in the transaction * `RawTransaction` combines all transaction parameters into a complete transaction This layered approach gives you fine-grained control over transaction construction. ## 4. Simulating the Transaction [Section titled “4. Simulating the Transaction”](#4-simulating-the-transaction) * TypeScript 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 // 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"}`); ``` Note Gas is the computational fee paid to process transactions on the blockchain. The total cost is calculated as `gas_used × gas_unit_price`. During simulation, the blockchain executes the transaction in a temporary environment to estimate these costs without making permanent changes to the blockchain state. This helps you avoid failed transactions due to insufficient gas. * Python 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 # 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'}") ``` Note Gas is the computational fee paid to process transactions on the blockchain. The total cost is calculated as `gas_used × gas_unit_price`. During simulation, the blockchain executes the transaction in a temporary environment to estimate these costs without making permanent changes to the blockchain state. This helps you avoid failed transactions due to insufficient gas. ## 5. Signing and Submitting the Transaction [Section titled “5. Signing and Submitting the Transaction”](#5-signing-and-submitting-the-transaction) * TypeScript 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 // 3. Sign the transaction console.log("\n=== 3. Signing the transaction ==="); const senderAuthenticator = aptos.transaction.sign({ signer: alice, transaction, }); console.log("Transaction signed successfully"); ``` Note Digital signatures work like a personal seal or signature in the physical world. They prove that the transaction was authorized by the account owner (who has the private key) and haven’t been tampered with. 2. Submit the transaction Add this code after signing the transaction to submit the signed transaction to the blockchain: ```typescript // 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}`); ``` Note The transaction hash is a unique identifier for your transaction, similar to a tracking number for a package. When submitting a transaction, the Aptos blockchain performs several validation checks, including verifying the transaction signature and ensuring the sequence number hasn’t been used before (preventing replay attacks). You can use the hash to check the status of your transaction on the [Aptos Explorer](https://explorer.aptoslabs.com/) or via the [REST API](/build/apis/fullnode-rest-api-reference). * Python 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 # 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 ``` Note Digital signatures work like a personal seal or signature in the physical world. They prove that the transaction was authorized by the account owner (who has the private key) and haven’t been tampered with. 2. Submit the transaction Add this code after signing the transaction to submit the signed transaction to the blockchain: ```python # 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}") ``` Note The transaction hash is a unique identifier for your transaction, similar to a tracking number for a package. When submitting a transaction, the Aptos blockchain performs several validation checks, including verifying the transaction signature and ensuring the sequence number hasn’t been used before (preventing replay attacks). You can use the hash to check the status of your transaction on the [Aptos Explorer](https://explorer.aptoslabs.com/) or via the [REST API](/build/apis/fullnode-rest-api-reference). ## 6. Waiting for Confirmation [Section titled “6. Waiting for Confirmation”](#6-waiting-for-confirmation) * TypeScript 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 // 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 // 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 npx ts-node transaction.ts ``` You should see output similar to: ```plaintext 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) ``` Note Notice that Alice’s balance decreased by more than 1000 octas. The extra amount is the gas fee paid to process the transaction. Behind the scenes, when checking balances, the SDK queries the CoinStore resource for the AptosCoin (`0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>`) and reads the current stored value. This demonstrates how the SDK abstracts away complex blockchain interactions into simple function calls. * Python 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 # 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 # 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 python transaction.py ``` You should see output similar to: ```plaintext 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) ``` Note Notice that Alice’s balance decreased by more than 1000 octas. The extra amount is the gas fee paid to process the transaction. Behind the scenes, when checking balances, the SDK queries the CoinStore resource for the AptosCoin (`0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>`) and reads the current stored value. ## 7. (Optional) Explore Your Transaction On-Chain [Section titled “7. (Optional) Explore Your Transaction On-Chain”](#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: ```plaintext 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](/_vercel/image?url=_astro%2Fexplorer_devnet.D3PWblc6.png\&w=320\&q=100) 4. Search for your transaction Paste your transaction hash into the search bar in the middle of the page. Caution Do not press enter! There is a known bug where searching with Enter does not work. 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 Note The Explorer is a powerful tool for debugging transactions and understanding blockchain activity. Developers frequently use it to verify their transactions executed as expected and to investigate any issues. ## 8. Next Steps [Section titled “8. Next Steps”](#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 Note The above links are for the Typescript SDK but the principles are the same if you are using Python or Rust. **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 [Section titled “Full Code Sample”](#full-code-sample) The complete code samples below combine all the snippets we’ve covered in this tutorial: * TypeScript ```typescript 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 ```python 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 Caution Account key rotation is an advanced feature that should be used with caution. Most users will never need to use this feature. 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) Caution Some of the following examples use private keys. Do not share your private keys with anyone. ## Proven and unproven key rotations [Section titled “Proven and unproven key rotations”](#proven-and-unproven-key-rotations) The onchain logic for key rotation is implemented through two Move APIs: 1. [`account::rotate_authentication_key`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L326), which executes a “proven” rotation. 2. [`account::rotate_authentication_key_call`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L294), which executes an “unproven” rotation. ### Proven key rotations [Section titled “Proven key rotations”](#proven-key-rotations) The [`account::rotate_authentication_key`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L326) API requires a signed [`account::RotationProofChallenge`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L79), 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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) table is updated with an entry that maps from the new authentication key to the corresponding account address. The [`account::OriginatingAddress`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) 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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L79) 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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) 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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) 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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) 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`](https://github.com/alnoki/aptos-core/blob/5ba4a8d1344b0bb6e22665525a96e787b9a44e55/aptos-move/framework/aptos-framework/sources/account.move#L528) after generating a new account (see below CLI tutorial). ### Unproven key rotations [Section titled “Unproven key rotations”](#unproven-key-rotations) Unlike [`account::rotate_authentication_key`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L326), the [`account::rotate_authentication_key_call`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L294) does *not* require a signed [`account::RotationProofChallenge`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L79). 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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) 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. Note The [`account::rotate_authentication_key_call`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L294) was introduced to support non-standard key algorithms, like passkeys, which cannot produce proofs of knowledge during rotation operations. 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`](https://github.com/alnoki/aptos-core/blob/5ba4a8d1344b0bb6e22665525a96e787b9a44e55/aptos-move/framework/aptos-framework/sources/account.move#L528) 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 [Section titled “Key rotation with the Aptos CLI”](#key-rotation-with-the-aptos-cli) 1. Start a localnet Start a localnet: ```shellscript aptos node run-localnet ``` The localnet is ready when it prints out: ```shellscript Applying post startup steps... Setup is complete, you can now use the localnet! ``` Note If you are on a UNIX-like system, the following command can be used to start a fresh localnet as a background process: ```shellscript mkdir -p localnet-data aptos node run-localnet \ --assume-yes \ --test-dir localnet-data \ --force-restart & export LOCALNET_PID=$! ``` You can then stop the localnet at any point with the following command: ```shellscript kill $LOCALNET_PID ``` 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 aptos key generate \ --assume-yes \ --output-file private-key-a \ --vanity-prefix 0xaaa ``` Example output ```shellscript { "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 aptos account lookup-address \ --public-key-file private-key-a.pub \ --url http://localhost:8080 ``` Example output ```shellscript { "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 aptos init \ --assume-yes \ --network local \ --private-key-file private-key-a \ --profile test-profile-1 ``` Example output ```shellscript 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 aptos config show-profiles --profile test-profile-1 ``` Example output ```shellscript { "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 aptos config show-private-key --profile test-profile-1 ``` Example output ```shellscript { "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 aptos account lookup-address \ --public-key-file private-key-a.pub \ --url http://localhost:8080 ``` Example output ```shellscript { "Result": "aaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b" } ``` Store this address in a shell variable: ```shellscript ADDRESS_A=aaa... ``` Note If you are using a UNIX-like machine that has `jq`, you can easily store the account address via: ```shellscript export ADDRESS_A=$( aptos account lookup-address \ --public-key-file private-key-a.pub \ --url http://localhost:8080 \ | jq -r '.Result' ) echo $ADDRESS_A ``` 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 aptos move view \ --args address:$ADDRESS_A \ --function-id 0x1::account::get_authentication_key \ --url http://localhost:8080 ``` Example output ```shellscript { "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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) table: ```shellscript aptos move view \ --args address:$AUTH_KEY_A \ --function-id 0x1::account::originating_address \ --url http://localhost:8080 ``` Example output ```shellscript { "Result": [ { "vec": [] } ] } ``` 6. Set originating address To ensure an entry in the [`account::OriginatingAddress`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) table for this new account, you can run [`account::set_originating_address`](https://github.com/alnoki/aptos-core/blob/5ba4a8d1344b0bb6e22665525a96e787b9a44e55/aptos-move/framework/aptos-framework/sources/account.move#L528): ```shellscript aptos move run \ --assume-yes \ --function-id 0x1::account::set_originating_address \ --profile test-profile-1 ``` Example output ```shellscript { "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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) table: ```shellscript aptos move view \ --args address:$AUTH_KEY_A \ --function-id 0x1::account::originating_address \ --url http://localhost:8080 ``` Example output ```shellscript { "Result": [ { "vec": [ "0xaaa5131b4d3fcef8d33ee465c4ee65727e36039f283455be87b1164200572e5b" ] } ] } ``` 7. Rotate authentication key Generate a new private key: ```shellscript aptos key generate \ --assume-yes \ --output-file private-key-b \ --vanity-prefix 0xbbb ``` Example output ```shellscript { "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 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 { "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 aptos config show-profiles --profile test-profile-1 aptos config show-profiles --profile test-profile-2 ``` Example output ```shellscript { "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 aptos move view \ --args address:$ADDRESS_A \ --function-id 0x1::account::get_authentication_key \ --url http://localhost:8080 ``` Example output ```shellscript { "Result": [ "0xbbbdb12f4fa23b8fe8711b77f4ab7108f3a22077c5dfe787eed3d048a0b82734" ] } ``` Store the authentication key in a shell variable: ```shellscript AUTH_KEY_B=bbb... ``` Note If you are using a UNIX-like machine that has `jq`, you can easily store the authentication key via: ```shellscript export AUTH_KEY_B=$( aptos move view \ --args address:$ADDRESS_A \ --function-id 0x1::account::get_authentication_key \ --url http://localhost:8080 \ | jq -r '.Result[0]' ) echo $AUTH_KEY_B ``` 9. Look up originating addresses Check the originating address for the new authentication key: ```shellscript aptos move view \ --args address:$AUTH_KEY_B \ --function-id 0x1::account::originating_address \ --url http://localhost:8080 ``` Example output ```shellscript { "Result": [ { "vec": [ "0xaaa8dc0f5e7a6e820f7b1906d99864412b12274ed259ad06bc2c2d8ee7b51e51" ] } ] } ``` Check the originating address for the old authentication key: ```shellscript aptos move view \ --args address:$AUTH_KEY_A \ --function-id 0x1::account::originating_address \ --url http://localhost:8080 ``` Example output ```shellscript { "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 aptos account rotate-key \ --assume-yes \ --new-private-key-file private-key-b \ --profile test-profile-2 \ --skip-saving-profile ``` Example output ```shellscript { "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 aptos key generate \ --assume-yes \ --output-file private-key-c \ --vanity-prefix 0xccc ``` Example output ```shellscript { "Result": { "PrivateKey Path": "private-key-c", "PublicKey Path": "private-key-c.pub", "Account Address:": "0xccc79d46b2963cb87f2ff32c51eb6c6361e8aa108d334d3183c3016389542958" } } ``` Initialize a new profile: ```shellscript aptos init \ --assume-yes \ --network local \ --private-key-file private-key-c \ --profile test-profile-3 ``` Example output ```shellscript 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 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 { "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 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 { "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`](https://github.com/aptos-labs/aptos-core/blob/acb6c891cd42a63b3af96561a1aca164b800c7ee/aptos-move/framework/aptos-framework/sources/account.move#L70) table during the last operation: ```shellscript 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 { "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 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. Note If you are using a UNIX-like machine: ```shell 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 rm private-key-* kill $LOCALNET_PID rm -fr localnet-data ``` 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 [Section titled “TypeScript key rotation example”](#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: Commands to run the example script: ### Navigate to the typescript SDK directory, install dependencies and run [Section titled “Navigate to the typescript SDK directory, install dependencies and run”](#navigate-to-the-typescript-sdk-directory-install-dependencies-and-run) rotate\_key.ts ```shellscript cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript-esm pnpm install && pnpm rotate_key ``` ### rotate\_key.ts output [Section titled “rotate\_key.ts output”](#rotate_keyts-output) ```shell 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 [Section titled “Python key rotation example”](#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: Commands to run the example script: ### Navigate to the python SDK directory, install dependencies and run [Section titled “Navigate to the python SDK directory, install dependencies and run”](#navigate-to-the-python-sdk-directory-install-dependencies-and-run) rotate\_key.ts ```shellscript cd aptos-core/ecosystem/python/sdk poetry install && poetry run python -m examples.rotate-key ``` ### rotate\_key.py output [Section titled “rotate\_key.py output”](#rotate_keypy-output) ```shellscript 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 ``` # Manage Fungible Assets with Multisig 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 [Section titled “Step 1: Pick an SDK”](#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 [Section titled “Step 2: Publish the module”](#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 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 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 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. Note Do not forget to fund the account with faucet before publishing modules. ## Step 3: Start The example [Section titled “Step 3: Start The example”](#step-3-start-the-example) ```shellscript cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript ``` Run the `multisig_managed_coin` example: ```shellscript MODULE_ADDR=${DEFAULT_ACCOUNT_ADDRESS} pnpm run multisig_managed_coin ``` Note This example uses the Aptos devnet, which has historically been reset each Thursday. Make sure devnet is live when you try running the example! If you are running localnet with faucet, you can run the following command instead: ```shellscript export APTOS_NODE_URL=http://0.0.0.0:8080 export APTOS_FAUCET_URL=http://0.0.0.0:8081 export 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 [Section titled “Generate single signer accounts”](#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. ### Create an Aptos framework multisig account with a managed fungible asset [Section titled “Create an Aptos framework multisig account with a managed fungible asset”](#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. ### Mint [Section titled “Mint”](#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. ### Freeze [Section titled “Freeze”](#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. Note Unfreeze is similar that just replace the last argument of `set_primary_stores_frozen_status` function to `false`. ### Force transfer [Section titled “Force transfer”](#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. ### Burn [Section titled “Burn”](#burn) Finally, all the three owners have 1000 meme coins. Let’s burn all the coins! Owner2 makes the proposal and owner1 approves it. ## Conclusion [Section titled “Conclusion”](#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 This reference guide presents various Oracles that you can utilize while building on Aptos. Oracles supply offchain data to the blockchain, enabling smart contracts to access a diverse range of information. ## Pyth Network [Section titled “Pyth Network”](#pyth-network) The [Pyth Network](https://pyth.network/) is one of the largest first-party Oracle network, 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 [Section titled “How to Use Pyth Real-Time Data in Aptos Contracts”](#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 [Section titled “Configuring the Move.toml file”](#configuring-the-movetoml-file) Add the Pyth Contract to your project dependencies in the `Move.toml` file: ```toml [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 [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 [Section titled “Write Contract Code”](#write-contract-code) The code snippet below provides an example module fetching the BTC/USD price from Pyth price feeds: ```rust 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) } } ``` Note The `pyth_price_update` argument contains verified prices from Pyth. Calling `pyth::update_price_feeds` with this value updates the on-chain Pyth price and ensures your application has recent price data. The pyth\_price\_update can be fetched from Hermes; Consult [Fetch Price Updates](/build/fetch-price-updates) for more information on how to fetch the `pyth_price_update`. 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 [Section titled “Additional Resources”](#additional-resources) You may find these additional resources helpful for developing your Aptos application. ## Sponsored Feeds on Aptos [Section titled “Sponsored Feeds on Aptos”](#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 [Section titled “API Reference”](#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 [Section titled “Example Applications”](#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) that use Pyth price feeds to mint an NFT. # Orderless Transactions 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 [Section titled “Process Overview”](#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 [Section titled “SDK Support”](#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 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 [Section titled “Process Overview”](#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 [Section titled “Technical Details”](#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 [Section titled “SDK Support”](#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 Note This guide is currently in progress of being replaced. Please check out the [exchange integration guide](/build/guides/exchanges) for more up-to-date information. 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 [Section titled “Overview”](#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 [Section titled “Getting Started”](#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 [Section titled “SDKs and tools”](#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 [Section titled “Accounts on Aptos”](#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 [Section titled “Transactions”](#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 [Section titled “Generating transactions”](#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 [Section titled “BCS-encoded transactions”](#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 [Section titled “Types of transactions”](#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 [Section titled “Status of a transaction”](#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 [Section titled “Testing transactions or transaction pre-execution”](#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 [Section titled “Viewing current and historical state”](#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 [Section titled “Exchanging and tracking fungible assets”](#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 [Section titled “Transferring FAs between users”](#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 [Section titled “Current balance for Fungible Asset”](#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 [Section titled “Exchanging and tracking coins”](#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 [Section titled “Transferring coins between users”](#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. Caution It is important to note that if an account has not registered a `CoinStore` for a given `T`, then any transfer of type `T` to that account will fail. ### Current balance for a coin [Section titled “Current balance for a coin”](#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 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 [Section titled “Querying transactions”](#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"} ``` Note The `key` field will be going away in favor of `guid` ### Querying events [Section titled “Querying events”](#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. Note When tracking full movement of coins, normally events are sufficient. `0x1::aptos_coin::AptosCoin`, however, requires considering `gas_used` for each transaction sent from the given account since it represents gas in Aptos. To reduce unnecessary overhead, extracting gas fees due to transactions does not emit an event. All transactions for an account can be retrieved from this API: `https://{rest_server_api}/accounts/{address}/transactions` ### Tracking coin balance changes [Section titled “Tracking coin balance changes”](#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`. Note If you always use the events in this manner, you won’t miss any balance changes for an account. By monitoring the events, you will find all balance changes in the `0x1::coin::CoinStore`: 1. Coin mints 2. Coin burns 3. Coin transfers 4. Staking coins 5. Withdrawing staked coins 6. Transfers not derived from `coin::transfer` 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 This guide explains how to build a transaction management harness that can scale on the Aptos blockchain. ## Background [Section titled “Background”](#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 [Section titled “Building a Transaction Manager”](#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 [Section titled “Implementations”](#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 [Section titled “Managing Sequence Numbers”](#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 [Section titled “Managing Transactions”](#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 [Section titled “Workers and Identity”](#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 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. Note This tutorial assumes you are comfortable with using the [Aptos CLI](/build/cli), have Node.js and npm installed, and understand basic JavaScript/TypeScript concepts. If you need more info, check out [Node.js Introduction](https://nodejs.org/en/learn/getting-started/introduction-to-nodejs) or the [Aptos TypeScript SDK](/build/sdks/ts-sdk) documentation. ## Walking Through The Code [Section titled “Walking Through The Code”](#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 [Section titled “Code Walkthrough”](#code-walkthrough) 1. Setup the Client We import and configure the `Aptos` client from the SDK to connect to the specified network: ```tsx 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 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 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 }); ``` Note Always wait for the transaction to complete using `waitForTransaction` before proceeding. This ensures the collection is ready before you try minting. 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 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 }); ``` Note You can change these values to customize your Digital Asset on-chain. 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 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 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 [Section titled “Running An Example”](#running-an-example) ### Getting Started [Section titled “Getting Started”](#getting-started) 1. Set up Your Project Create a new directory for your project and initialize a Node.js project: ```shellscript 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 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 { "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 APTOS_NETWORK=devnet ``` Note By default, we’ll use `devnet`, but you can also choose `testnet` or `mainnet` depending on your needs. 5. Adding index.ts Create an `index.ts` file with the following: ```tsx // 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 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 === 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 [Section titled “Further Reading & Resources”](#further-reading--resources) * [aptos ts-sdk docs](https://aptos.dev/en/build/sdks/ts-sdk) * [Account basics](/network/blockchain/accounts) * [REST API specification](/network/nodes/aptos-api-spec) # Indexer Content for build/indexer could not be fully rendered due to component compatibility issues. # Indexer API Access 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) [Section titled “SDK Access (Primary Method)”](#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) [Section titled “Hasura Explorer (Manual Queries)”](#hasura-explorer-manual-queries) Note For detailed reference material about the contents of these tables, see the [Indexer Table Reference page](/build/indexer/indexer-reference). Choose a network to explore the free Aptos-Hosted Indexer API using the Hasura Explorer: [Mainnet ](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql)Hasura GraphQL Explorer for Aptos Mainnet [Testnet ](https://cloud.hasura.io/public/graphiql?endpoint=https://api.testnet.aptoslabs.com/v1/graphql)Hasura GraphQL Explorer for Aptos Testnet [Devnet ](https://cloud.hasura.io/public/graphiql?endpoint=https://api.devnet.aptoslabs.com/v1/graphql)Hasura GraphQL Explorer for Aptos Devnet ## GraphQL API Endpoints (Direct Access) [Section titled “GraphQL API Endpoints (Direct Access)”](#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 [Section titled “Rate limits”](#rate-limits) Learn more about the rate limits that apply to the Aptos Labs hosted indexer API by reading the [Aptos Build docs](https://build.aptoslabs.com/docs/start/billing). If you need a higher rate limit, consider the following solutions: 1. Get an API Key from [Aptos Build](https://build.aptoslabs.com/). Learn more about API keys at the [Aptos Build docs site](https://build.aptoslabs.com/docs/start/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 Content for build/indexer/indexer-api/account-transactions could not be fully rendered due to component compatibility issues. # Get Aptos Name From Address Content for build/indexer/indexer-api/ans-lookup could not be fully rendered due to component compatibility issues. # Indexer Architecture 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 [Section titled “High Level Breakdown”](#high-level-breakdown) Here is how the Indexer creates that API at a high-level: ![Signed Transaction Flow](/_astro/indexer-architecture-light.DkJkKlqr.svg) ![Signed Transaction Flow](/_astro/indexer-architecture-dark.BhX5FZZG.svg) 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 [Section titled “Detailed Overview”](#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. # Get Fungible Asset Balances Content for build/indexer/indexer-api/fungible-asset-balances could not be fully rendered due to component compatibility issues. # Get Fungible Asset Info Content for build/indexer/indexer-api/fungible-asset-info could not be fully rendered due to component compatibility issues. # Count Number of Active Delegators for a Pool Content for build/indexer/indexer-api/get-delegators could not be fully rendered due to component compatibility issues. # Retrieve NFT Collections Owned by an Account Content for build/indexer/indexer-api/get-nft-collections could not be fully rendered due to component compatibility issues. # Get NFTs Owned by an Account Content for build/indexer/indexer-api/get-nfts could not be fully rendered due to component compatibility issues. # Indexer API Reference The Indexer API allows you to access rich data about tokens, fungible assets, and accounts on-chain using GraphQL queries. **You can access it [here](/build/indexer/indexer-api).** For common queries, check out the sidebar for examples to work from. When building your own, this reference guide should help you determine which tables are most relevant, and how to format your queries. Caution Before relying on a table for production services, check the bottom of this page to see if that table is deprecated. If so, use the note section for guidance on what to do to migrate to a non-deprecated table. Note If you are looking up a table with the `_by_pk` suffix, search for the table name without that suffix. `_by_pk` tables are automatically generated for convenience to allow querying by primary key. # Indexer Table Reference [Section titled “Indexer Table Reference”](#indexer-table-reference) Note Remember to use Ctrl + F to find the table you are interested in! When in doubt, you may also want to query the Hasura tables linked in the [Indexer API Access](/build/indexer/indexer-api) page to see examples of the data inside. ## Filtering (with `where` clauses) [Section titled “Filtering (with where clauses)”](#filtering-with-where-clauses) To ensure your queries filter data efficiently, check out the available indexes for each table. Some indexes are composite B-tree indexes, meaning they consist of multiple columns. B-tree indexes are ordered and perform optimally when queries utilize a left-most prefix of the indexed columns. ## General [Section titled “General”](#general) ### `user_transactions` [Section titled “user\_transactions”](#user_transactions) Transactions filtered to user\_transactions (not system). | Index Name | Indexed Columns | | ------------------------------------------------- | ------------------------ | | user\_transactions\_pkey | version | | user\_transactions\_sender\_sequence\_number\_key | sender, sequence\_number | | ut\_epoch\_index | epoch | | ut\_insat\_index | inserted\_at | | ut\_sender\_seq\_index | sender, sequence\_number | ### `block_metadata_transactions` [Section titled “block\_metadata\_transactions”](#block_metadata_transactions) A type of system transaction emitted once per block, useful for mapping to timestamp or epoch. | Index Name | Indexed Columns | | ------------------------------------------------- | --------------- | | block\_metadata\_transactions\_block\_height\_key | block\_height | | block\_metadata\_transactions\_pkey | version | | bmt\_insat\_index | inserted\_at | ### `account_transactions` [Section titled “account\_transactions”](#account_transactions) *Has an aggregate view for summary data called `account_transactions_aggregate`* | Index Name | Indexed Columns | | --------------------------- | -------------------------------------- | | account\_transactions\_pkey | account\_address, transaction\_version | | at\_insat\_index | inserted\_at | | at\_version\_index | transaction\_version DESC | This table maps accounts and transactions that interact with that account. | Field | Type | Primary Key | Description | | -------------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | delegated\_staking\_activities | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | fungible\_asset\_activities | Join | | References [fungible\_asset\_activities](#fungible_asset_activities). | | token\_activities | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | token\_activities\_aggregate | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | token\_activities\_v2 | Join | | References [token\_activities\_v2](#token_activities_v2). | | token\_activities\_v2\_aggregate | Join | | References [token\_activities\_v2](#token_activities_v2). | | account\_address | String! | Yes | This is an Aptos account address. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | transaction\_version | bigint! | Yes | Blockchain version of the transaction. Ex. 10000000 | ### `ledger_infos` [Section titled “ledger\_infos”](#ledger_infos) This table shares what chain is currently being queried. | Field | Type | Primary Key | Description | | --------- | ---- | ----------- | ------------------------------------------------------------------------------------------------- | | chain\_id | int | Yes | The unique identifier for the chain you are accessing. Ex. 1 (for Mainnet), 2 (for Testnet), etc. | ### `processor_status` [Section titled “processor\_status”](#processor_status) This table shares how current this processor’s data is. gives you latest version processed per “processor” | Field | Type | Primary Key | Description | | ---------------------------- | ------ | ----------- | ---------------------------------------------------------------------------------------- | | last\_success\_version | bigint | Yes | The version number of the last successful processor run. Ex. 5000000 | | last\_transaction\_timestamp | String | | Timestamp of the last processed transaction. Ex. “2024-04-17T02:14:25.68771” | | last\_updated | String | | Timestamp of the last update to this processor’s status. Ex. “2024-04-17T02:14:25.68771” | | processor | String | Yes | Name of the processor. Ex. “transaction\_processor” | ## NFT [Section titled “NFT”](#nft) ### `token_activities_v2` [Section titled “token\_activities\_v2”](#token_activities_v2) *Has an aggregate view for summary data called `token_activities_v2_aggregate`* | Index Name | Indexed Columns | | --------------------------- | ---------------------------------- | | ta2\_from\_type\_index | from\_address, type | | ta2\_insat\_index | inserted\_at | | ta2\_owner\_type\_index | event\_account\_address, type | | ta2\_tid\_index | token\_data\_id | | ta2\_to\_type\_index | to\_address, type | | token\_activities\_v2\_pkey | transaction\_version, event\_index | This table tracks token activities and is especially useful for tracking NFT activity. This includes both v1 and v2 data. | Field | Type | Primary Key | Description | | ----------------------------- | ------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | aptos\_names\_from | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | aptos\_names\_from\_aggregate | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | aptos\_names\_to | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | aptos\_names\_to\_aggregate | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | current\_token\_data | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | after\_value | String | | The value of a token property after the transaction. Ex. “100” | | before\_value | String | | The value of a token property before the transaction. Ex. “50” | | entry\_function\_id\_str | String | | The identifier of the function called in this transaction. Ex. “0x1::aptos\_account::transfer” | | event\_account\_address | String | | This is an Aptos account address related to the event. This address must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | event\_index | bigint | Yes | Index of the event within the transaction. Ex. 1 | | from\_address | String | | This is an Aptos account address from which the token was sent. This address must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | is\_fungible\_v2 | Boolean | | Indicates whether the token is fungible. Soon to be deprecated. Ex. False for NFTs. | | property\_version\_v1 | bigint | | The version of the token’s properties under schema version 1. This field is only for token standard v1. It is always 0 for v2. Ex. 0 | | to\_address | String | | This is an Aptos account address to which the token was sent. This address must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | token\_amount | bigint | | The amount of the token transferred in this activity. Ex. 3 | | token\_data\_id | String | | Unique identifier for this particular token’s data. For token standard v1, this is derived from a combination of creator\_address, collection\_name, and token\_name. This ID must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | token\_standard | String | | Aptos standard that the collection adheres to. Ex. “v1” | | transaction\_timestamp | String | | Timestamp when the transaction occurred. Ex. “2024-04-17T02:14:25.68771” | | transaction\_version | bigint | Yes | Blockchain version of the transaction. Ex. 10000000 | | type | String | | Type of transfer - like “deposit” or “withdrawal”. Ex. “0x3::token::DepositEvent” | ### `nft_metadata_crawler_parsed_asset_uris` [Section titled “nft\_metadata\_crawler\_parsed\_asset\_uris”](#nft_metadata_crawler_parsed_asset_uris) This table allows you to look up the cdn and uris for NFT images / content. | Field | Type | Primary Key | Description | | ---------------------------------- | ------ | ----------- | -------------------------------------------------------------------------------------------------- | | animation\_optimizer\_retry\_count | Int | | Number of retries to optimize animation. Ex. 3 | | asset\_uri | String | Yes | URI of the asset. Ex. “” | | cdn\_animation\_uri | String | | Content Delivery Network URI for animation. Ex. “” | | cdn\_image\_uri | String | | Content Delivery Network URI for image. Ex. “” | | cdn\_json\_uri | String | | Content Delivery Network URI for JSON metadata. Ex. “” | | raw\_animation\_uri | String | | Original URI for animation before CDN optimization. Ex. “” | | raw\_image\_uri | String | | Original URI for image before CDN optimization. Ex. “” | | Index Name | Indexed Columns | | ------------------------- | ------------------- | | nft\_inserted\_at | inserted\_at | | nft\_raw\_animation\_uri | raw\_animation\_uri | | nft\_raw\_image\_uri | raw\_image\_uri | | parsed\_asset\_uris\_pkey | asset\_uri | ### `current_token_ownerships_v2` [Section titled “current\_token\_ownerships\_v2”](#current_token_ownerships_v2) *Has an aggregate view for summary data called `current_token_ownerships_v2_aggregate`* This table tracks who owns which NFTs. This includes both v1 and v2 tokens. Fungible tokens are not tracked as consistently. | Field | Type | Primary Key | Description | | ------------------------------ | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | composed\_nfts\_aggregate | Join | | Aggregate information about the composed NFTs, such as count or other statistics. | | current\_token\_data | Join | | Detailed information about the token’s current data; structure is defined in a related table. | | amount | bigint | | The amount of the token owned. Example: 1 for an NFT. | | composed\_nfts | Array | | An array containing the IDs of NFTs that compose this token, if applicable. | | is\_fungible\_v2 | Boolean | | Indicates whether the token is fungible. Example: true or null | | is\_soulbound\_v2 | Boolean | | Indicates whether the token is soulbound (non-transferable once owned). Example: true or null | | last\_transaction\_timestamp | String | | Timestamp of the last transaction involving the token. Example: “2024-04-17T02:14:25.68771” | | last\_transaction\_version | bigint | | The version number of the last transaction involving the token. Example: 20747031 | | non\_transferrable\_by\_owner | Boolean | | Indicates whether the token is non-transferrable by the owner. Example: true or null | | owner\_address | String | Yes | The Aptos account address that currently owns the token. Addresses must be 66 characters so may be 0 padded. Example: “0xa815a9a09105973084bfc31530e7c8f002846787c2f0521e1e34dc144ad83b89” | | property\_version\_v1 | bigint | Yes | The version number of the token’s properties as of the last update. This field is only for token standard v1. It is always 0 for v2. Example: 0 | | storage\_id | String | Yes | A unique identifier used for storage purposes. IDs must be 66 characters long, so may be 0 padded. Ex. “0xd8d41ff9f67d17d7dee061b5b683b92013b420cb6a30c21fc7c287454792d7a8” | | table\_type\_v1 | String | | The Move function type. Example: “0x3::token::TokenStore” | | token\_data\_id | String | Yes | A unique identifier for the token data, typically a hash or a numeric ID. Ex. “0x3d911af2dc3e47848fbba17b8694cf526942be183b84f8393a6c048232fb976d” | | token\_properties\_mutated\_v1 | Object | | Properties of the token that have been mutated from the original. Often in JSON or similar format. Example: | | token\_standard | String | | The standard used to generate this token. Ex. “v1” or “v2” | | Index Name | Indexed Columns | | ------------------------------------ | ------------------------------------------------------------------- | | curr\_to2\_insat\_index | inserted\_at | | curr\_to2\_owner\_index | owner\_address | | curr\_to2\_wa\_index | storage\_id | | current\_token\_ownerships\_v2\_pkey | token\_data\_id, property\_version\_v1, owner\_address, storage\_id | ### `current_token_datas_v2` [Section titled “current\_token\_datas\_v2”](#current_token_datas_v2) This table tracks the metadata associated with each NFT (Ex. URI, supply, etc.). This tracks both v1 and v2 tokens. | Field | Type | Primary Key | Description | | ------------------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | cdn\_asset\_uris | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | current\_collection | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | current\_token\_ownerships | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | current\_token\_ownerships\_aggregate | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | aptos\_name | String | | This is a name tied to this token using the Aptos Name Service (ANS). Ex. “EpicDragon” | | collection\_id | String | Yes | Identifier for the collection that includes this token. Ex. “0x360f6eeabb4d7a9d2fab1f35b01e02831e3b5c4b73c7fd6c98dcc1c301c817c8” | | decimals | bigint | | Number of decimal places for token value, typically for fungible tokens. Ex. 18 | | description | String | | Description of the token. Ex. “A legendary dragon from the mystical lands.” | | is\_fungible\_v2 | Boolean | | Whether the token is fungible. Ex. False for NFTs | | largest\_property\_version\_v1 | bigint | | The largest version number of the token’s properties under the first schema. Ex. 1 | | last\_transaction\_timestamp | bigint | | Unix timestamp of the last transaction involving this token. Ex. 2024-03-27T07:41:58.800893 | | last\_transaction\_version | bigint | | Blockchain version of the last transaction involving this token. Ex. 30000000 | | maximum | bigint | | Maximum possible quantity of this token, relevant for fungibles. Ex. 1000000 | | supply | bigint | | Current supply of the token in circulation. Ex. 500000 | | token\_data\_id | String | | Unique identifier for the token’s data. Ex. “0xa815a9a09105973084bfc31530e7c8f002846787c2f0521e1e34dc144ad83b89” | | token\_name | String | | The formal name of the token. Ex. “Mystic Dragon” | | token\_properties | Object | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | token\_standard | String | | Aptos standard that the collection adheres to. Ex. “v1” | | token\_uri | String | | URI linking to further information about the token. Ex. “” | | Index Name | Indexed Columns | | ------------------------------- | --------------------------- | | cur\_td2\_cid\_name\_index | collection\_id, token\_name | | cur\_td2\_insat\_index | inserted\_at | | current\_token\_datas\_v2\_pkey | token\_data\_id | ### `current_collections_v2` [Section titled “current\_collections\_v2”](#current_collections_v2) This table tracks the metadata associated with each NFT collection (Ex. collection\_id, creator\_address, etc.). This tracks both v1 and v2 tokens. | Field | Type | Primary Key | Description | | ---------------------------- | ------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cdn\_asset\_uris | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | collection\_id | String | Yes | Unique identifier for the collection. IDs must be 66 characters long, and so may be 0 padded. Ex. “0xa815a9a09105973084bfc31530e7c8f002846787c2f0521e1e34dc144ad83b88” | | collection\_name | String | | The formal name of the collection. Ex. “Mythic Dragons” | | creator\_address | String | | This is an Aptos account address that created the collection. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | current\_supply | bigint | | Current supply of tokens in this collection. Ex. 500 | | description | String | | Description of the collection. Ex. “A collection of rare digital dragons.” | | last\_transaction\_timestamp | String | | Timestamp of the last transaction involving this collection. Ex. “2024-04-17T02:14:25.68771” | | last\_transaction\_version | bigint | | Blockchain version of the last transaction involving this collection. Ex. 3000000002 | | max\_supply | bigint | | Maximum possible quantity of tokens in this collection. If the max supply is 0, there is no limit on the supply. Ex. 1000 | | mutable\_description | String | | Changeable description of the collection. Ex. “Updated collection description.” | | mutable\_uri | Boolean | | True if the uri is changeable by the creator. Ex. True | | table\_handle\_v1 | String | | Legacy identifier handle for the collection in earlier schema versions. Ex. “handle\_12345” | | token\_standard | String | | Aptos standard that the collection adheres to. Ex. “v1” | | total\_minted\_v2 | bigint | | Total number of tokens minted in this collection under schema version 2. Ex. 800 | | uri | String | | This is a URI to where the image live. This can also be JSON data. Ex. “” | | Index Name | Indexed Columns | | ------------------------------ | ---------------------------------- | | cur\_col2\_crea\_cn\_index | creator\_address, collection\_name | | cur\_col2\_insat\_index | inserted\_at | | current\_collections\_v2\_pkey | collection\_id | ### `current_collection_ownership_v2_view` [Section titled “current\_collection\_ownership\_v2\_view”](#current_collection_ownership_v2_view) *Has an aggregate view for summary data called `current_collection_ownership_v2_view_aggregate`* This table maps collections to who owns them and helps count how much of a collection is owned by other accounts. | Field | Type | Primary Key | Description | | -------------------------- | ------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | current\_collection | Join | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see these sub-fields. | | collection\_id | String | Yes | Unique identifier for the collection. IDs must be 66 characters long, and so may be 0 padded. Ex. “0xa815a9a09105973084bfc31530e7c8f002846787c2f0521e1e34dc144ad83b89” | | collection\_name | String | | The formal name of the collection. Ex. “Mythic Dragons” | | collection\_uri | String | | URI linking to further information about the collection. Ex. “” | | creator\_address | String | | This is an Aptos account address that created the collection. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | distinct\_tokens | bigint | | The count of distinct tokens owned within this collection. Ex. 150 | | last\_transaction\_version | bigint | | The version number of the last transaction involving this collection. Ex. 3000000002 | | owner\_address | String | Yes | This is an Aptos account address that currently owns the token. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | single\_token\_uri | String | | URI linking to information about a specific token within the collection. Ex. “” | | token\_standard | String | | Aptos standard that the collection adheres to. Ex. “v1” | ## Fungible Assets [Section titled “Fungible Assets”](#fungible-assets) ### `fungible_asset_metadata` [Section titled “fungible\_asset\_metadata”](#fungible_asset_metadata) This tracks the metadata tied to each fungible asset (ex. decimals of precision). It includes v1 token data. This is a current\_ table. | Field | Type | Primary Key | Description | | ------------------------------------- | ------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | asset\_type | String | Yes | The type of the asset, described by a Move resource. Ex. “0x1::aptos\_coin::AptosCoin” | | creator\_address | String | | This is an Aptos account address that created the asset. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | decimals | bigint | | Number of decimal places for token value, typically for fungible tokens. Ex. 18 | | icon\_uri | String | | URI for the icon of the asset. Ex. “” | | last\_transaction\_timestamp | String | | Timestamp of the last transaction involving this asset. Ex. “2024-04-17T02:14:25.68771” | | last\_transaction\_version | bigint | | Blockchain version of the last transaction involving this asset. Ex. 10000000 | | name | String | | The formal name of the asset. Ex. “Digital Gold” | | project\_uri | String | | URI linking to the project information associated with this asset. Ex. “[https://www.example.com/project\\\_name/](https://www.example.com/project%5C_name/)“ | | supply\_aggregator\_table\_handle\_v1 | String | | Legacy handle for the supply aggregator table from an earlier schema version. Ex. “handle\_67890” | | supply\_aggregator\_table\_key\_v1 | String | | Legacy key for accessing the supply aggregator table in earlier schema versions. Ex. “key\_12345” | | symbol | String | | The trading symbol of the asset. Ex. “DGOLD” | | token\_standard | String | | Standard that the asset adheres to. Ex. “v1” | | Index Name | Indexed Columns | | ------------------------------- | ---------------- | | fam\_creator\_index | creator\_address | | fam\_insat\_index | inserted\_at | | fungible\_asset\_metadata\_pkey | asset\_type | ### `fungible_asset_activities` [Section titled “fungible\_asset\_activities”](#fungible_asset_activities) This tracks the activity of fungible assets. It includes v1 token data. | Field | Type | Primary Key | Description | | ------------------------------ | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | owner\_aptos\_names | Join | | References [owner\_aptos\_names](#current_aptos_names). | | owner\_aptos\_names\_aggregate | Join | | References [owner\_aptos\_names](#current_aptos_names). | | amount | bigint | | The amount of the asset involved in the activity. Ex. 1000 | | asset\_type | String | Yes | The type of the asset, described by a Move resource. For fungible assets, this will be the address of the metadata object. Ex. “0x1::aptos\_coin::AptosCoin” | | block\_height | bigint | | The blockchain id at which this activity occurred. Ex. 1500000 | | entry\_function\_id\_str | String | | The identifier of the function called in this transaction. Ex. “0x1::aptos\_account::transfer” | | event\_index | bigint | | Index of the event within the transaction. Ex. 1 | | gas\_fee\_payer\_address | String | | This is an Aptos account address that paid the gas fee for the transaction. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | is\_frozen | Boolean | | True if this activity is a freeze asset activity. Ex. null | | is\_gas\_fee | Boolean | | Indicates whether this activity involved a gas fee. Ex. True | | is\_transaction\_success | Boolean | | Indicates whether the transaction was successful. Ex. True | | metadata | Object | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see fields for `metadata` in this table. | | owner\_address | String | | This is an Aptos account address that owns the asset. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0xa815a9a09105973084bfc31530e7c8f002846787c2f0521e1e34dc144ad83b89” | | storage\_id | String | | Identifier for the storage used in the transaction. IDs must be 66 characters long, and so may be 0 padded. Ex. “0xa815a9a09105973084bfc31530e7c8f002846787c2f0521e1e34dc144ad83b89” | | storage\_refund\_amount | bigint | | Amount refunded for storage after the transaction. This is always in APT [octas](/network/glossary#Octa). Ex. 50 | | token\_standard | String | | Aptos standard that the collection adheres to. Ex. “v1” | | transaction\_timestamp | String | | Timestamp when the transaction occurred. Ex. “2024-04-17T02:14:25.68771” | | transaction\_version | bigint | | Blockchain version of the transaction. Ex. 2 | | type | String | | Type of the transaction, described by a Move entry function. Ex. “0x1::coin::WithdrawEvent” | | Index Name | Indexed Columns | | --------------------------------- | ---------------------------------- | | faa\_at\_index | asset\_type | | faa\_gfpa\_index | gas\_fee\_payer\_address | | faa\_insat\_idx | inserted\_at | | faa\_owner\_type\_index | owner\_address, type | | faa\_si\_index | storage\_id | | fungible\_asset\_activities\_pkey | transaction\_version, event\_index | ### `current_fungible_asset_balances` [Section titled “current\_fungible\_asset\_balances”](#current_fungible_asset_balances) *Has an aggregate view for summary data called `current_fungible_asset_balances_aggregate`* This tracks the asset balances of each account on-chain. It includes v1 token data. | Field | Type | Primary Key | Description | | ---------------------------- | ------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | amount | bigint | | The amount of the asset owned. Ex. 2000 | | asset\_type | String | | The type of the asset, described by a Move resource. For v2 tokens this is the address of the fungible asset metadata object. For v1 it’s the fully qualified path of the move resource. Ex. “0x1::aptos\_coin::AptosCoin” | | is\_frozen | Boolean | | Indicates whether the account is frozen. Ex. False | | is\_primary | Boolean | | Indicates whether this is the primary balance of the owner. Ex. True | | last\_transaction\_timestamp | String | | Timestamp of the last transaction involving this balance. Ex. “2024-04-17T02:14:25.68771” | | last\_transaction\_version | bigint | | Blockchain version of the last transaction involving this balance. Ex. 30000000 | | metadata | Object | | Use the [Hasura explorer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) to see fields for `metadata` in `current_fungible_asset_balances`. | | owner\_address | String | | This is an Aptos account address that owns the asset. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0xa815a9a09105973084bfc31530e7c8f002846787c2f0521e1e34dc144ad83b89” | | storage\_id | String | Yes | Identifier for the storage associated with this balance. IDs must be 66 characters long, and so may be 0 padded. Ex. “0xa815a9a09105973084bfc31530e7c8f002846787c2f0521e1e34dc144ad83b89” | | token\_standard | String | | Aptos standard that the collection adheres to. Ex. “v1” | | Index Name | Indexed Columns | | ------------------------------------------------- | --------------------------- | | cufab\_insat\_index | inserted\_at | | cufab\_owner\_at\_index | owner\_address, asset\_type | | current\_unified\_fungible\_asset\_balances\_pkey | storage\_id | ## Aptos Naming Service (ANS) [Section titled “Aptos Naming Service (ANS)”](#aptos-naming-service-ans) ### `current_aptos_names` [Section titled “current\_aptos\_names”](#current_aptos_names) *Has an aggregate view for summary data called `current_aptos_names_aggregate`* This view of [`current_ans_lookup_v2`](#current_ans_lookup_v2) helps query by name instead of account. | Field | Type | Primary Key | Description | | -------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | domain | String | | The domain associated with this Aptos name. Ex. “example.crypto” | | domain\_with\_suffix | String | | The full domain name including any suffix. Ex. “example.crypto.aptos” | | expiration\_timestamp | String | | Timestamp when the domain registration expires. Ex. “2024-04-17T02:14:25.68771” | | is\_active | Boolean | | Indicates whether the domain is currently active. Ex. True | | is\_domain\_owner | Boolean | | Indicates whether the registered address is the owner of the domain. Ex. False | | is\_primary | Boolean | | Indicates whether this is the primary domain for the registered address. Ex. True | | last\_transaction\_version | bigint | | The version number of the last transaction involving this domain. Ex. 5000000 | | owner\_address | String | | This is an Aptos account address that owns the domain. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0x123abc456def7890abcdef1234567890abcdef1234” | | registered\_address | String | | This is an Aptos account address registered to the domain. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | subdomain | String | | Any subdomain part of the domain name. Ex. “sub.example” | | token\_name | String | | The name of the token associated with this domain. Ex. “ExampleToken” | | token\_standard | String | | Aptos standard that the collection adheres to. Ex. “v1” | ### `current_ans_lookup_v2` [Section titled “current\_ans\_lookup\_v2”](#current_ans_lookup_v2) This table maps tokens, standards, and addresses to human readable names. | Field | Type | Primary Key | Description | | -------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | domain | String | Yes | The domain associated with this Aptos name. Ex. “example.crypto” | | expiration\_timestamp | String | | Timestamp when the domain registration expires. Ex. “2024-04-17T02:14:25.68771” | | is\_deleted | Boolean | | Indicates whether the domain registration has been deleted. Ex. False | | last\_transaction\_version | bigint | | The version number of the last transaction involving this domain. Ex. 5000000 | | registered\_address | String | | This is an Aptos account address registered to the domain. Addresses must be 66 characters long, and so may be 0 padded. Ex. “0x50bc83f01d48ab3b9c00048542332201ab9cbbea61bda5f48bf81dc506caa78a” | | subdomain | String | Yes | Any subdomain part of the domain name. Ex. “sub.example” | | token\_name | String | | The name of the token associated with this domain. Ex. “ExampleToken” | | token\_standard | String | Yes | Aptos standard that the collection adheres to. Ex. “v1” | | Index Name | Indexed Columns | | ------------------------------ | ---------------------------------- | | ans\_v2\_et\_index | expiration\_timestamp | | ans\_v2\_insat\_index | inserted\_at | | ans\_v2\_ra\_index | registered\_address | | ans\_v2\_tn\_index | token\_name, token\_standard | | current\_ans\_lookup\_v2\_pkey | domain, subdomain, token\_standard | ## Deprecated Tables [Section titled “Deprecated Tables”](#deprecated-tables) The following tables are planned for deprecation, or are already deprecated. See the notes section for any direct replacements or notes on how to migrate if you currently depend on one of these tables. Please do not use any of the below tables for production services. | Table | Notes | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | address\_version\_from\_move\_resources | Replace with account\_transactions | | address\_events\_summary | To query custom events, you should create a [No-Code Indexer](https://build.aptoslabs.com/docs/no-code-indexing) | | address\_version\_from\_events | To query custom events, you should create a [No-Code Indexer](https://build.aptoslabs.com/docs/no-code-indexing) | | coin\_activities | Replace with fungible\_asset\_activities | | coin\_balances | Replace with current\_fungible\_asset\_balances | | coin\_infos | Replace with fungible\_asset\_metadata | | coin\_supply | No replacement; non-realtime APT coin supply is available with this [query](https://github.com/aptos-labs/explorer/blob/main/analytics/apt_supply.sql) | | collection\_datas | Replace with current\_collections\_v2 | | current\_ans\_lookup | Replace with current\_ans\_lookup\_v2 | | current\_coin\_balances | Replace with current\_fungible\_asset\_balances | | current\_collection\_datas | Replace with current\_collections\_v2 | | current\_token\_datas | Replace with current\_token\_datas\_v2 | | current\_token\_ownerships | Replace with current\_token\_ownerships\_v2 | | events\_view | To query custom events, you should create a [No-Code Indexer](https://build.aptoslabs.com/docs/no-code-indexing) | | move\_resources | Replace with account\_transactions | | move\_resources\_view | Replace with account\_transactions | | nft\_marketplace\_v2\_\* | Replace with [NFT Aggregator API](https://aptos.dev/en/build/indexer/nft-aggregator) | | token\_activities | Replace with token\_activities\_v2 | | token\_datas | Replace with current\_token\_datas\_v2 | | token\_ownerships | Replace with current\_token\_ownerships\_v2 | | tokens | Replace with current\_token\_datas\_v2 | | transactions | No replacement; non-realtime data is available in [BigQuery](https://console.cloud.google.com/marketplace/product/bigquery-public-data/crypto-aptos-mainnet-us) | | transactions\_view | No replacement; non-realtime data is available in [BigQuery](https://console.cloud.google.com/marketplace/product/bigquery-public-data/crypto-aptos-mainnet-us) | # Self-Hosted Indexer API This guide will walk you through setting up a self-hosted Indexer API. Caution Currently this guide only explains how to run processor part of the Indexer API. By the end of this guide you will have a running processor that consumes transactions from the Transaction Stream Service, parses them, and stores them in the database. Unfortunately this guide does not explain how to attach an API to this system right now. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) * A running PostgreSQL instance is required, with a valid user and database. In this example we call the user `postgres` and the database `indexer`. * If you wish to use Docker, you must have Docker installed. [Installation Guide](https://docs.docker.com/get-docker/). ## Configuration [Section titled “Configuration”](#configuration) To run the service we need to define a config file. We will start with this template: ```yaml health_check_port: 8084 server_config: processor_config: type: default_processor postgres_connection_string: postgresql://postgres:@localhost:5432/indexer indexer_grpc_data_service_address: 127.0.0.1:50051 indexer_grpc_http2_ping_interval_in_secs: 60 indexer_grpc_http2_ping_timeout_in_secs: 10 auth_token: AUTH_TOKEN ``` From here you will likely want to change the values of some of these fields. Let’s go through some of them. ### `processor_name` [Section titled “processor\_name”](#processor_name) Note A single instance of the service only runs a single processor. If you want to run multiple processors, you must run multiple instances of the service. In this case, it is up to you whether to use the same database or not. This is the processor you want to run. You can see what processors are available [here](https://github.com/aptos-labs/aptos-indexer-processors-v2/tree/main/processor/src/processors). Some examples: * `coin_processor` * `ans_processor` * `token_v2_processor` ### `postgres_connection_string` [Section titled “postgres\_connection\_string”](#postgres_connection_string) This is the connection string to your PostgreSQL database. It should be in the format `postgresql://:@:/`. Caution If you’re running this from a Docker Desktop environment (which you likely are if you’re using MacOS or Windows) you must set `postgres_connection_string` to `postgresql://host.docker.internal:5432/indexer` instead. With Docker Desktop this is how the binary can reach the host network. ### `indexer_grpc_data_service_address` [Section titled “indexer\_grpc\_data\_service\_address”](#indexer_grpc_data_service_address) This is the URL for the Transaction Stream Service. If you are using the Labs-Hosted instance you can find the URLs for each network at [this page](/build/indexer/indexer-api). Make sure to select the correct URL for the network you want to index. If you are running this service locally the value should be `127.0.0.1:50051`. ### `auth_token` [Section titled “auth\_token”](#auth_token) This is the auth token used to connect to the Transaction Stream Service. If you are using the Labs-Hosted instance you can use the API Gateway to get an API key. Learn more at [this page](/build/indexer/indexer-api). ## Run with source code [Section titled “Run with source code”](#run-with-source-code) Clone the repo: ```shellscript # SSH git clone git@github.com:aptos-labs/aptos-indexer-processors-v2.git # HTTPS git clone https://github.com/aptos-labs/aptos-indexer-processors-v2.git ``` Navigate to the directory for the service: ```shellscript cd aptos-indexer-processors cd rust/processor ``` Run the service: ```shellscript cargo run --release -- -c config.yaml ``` ## Run with Docker [Section titled “Run with Docker”](#run-with-docker) To run the service with Docker, use the following command: ```shellscript docker run -it --network host --mount type=bind,source=/tmp/config.yaml,target=/config.yaml aptoslabs/indexer-processor-rust -c /config.yaml ``` This command binds the container to the host network and mounts the config file from the host into the container. This specific invocation assumes that your config file in the host is at `/tmp/config.yaml`. See the image on DockerHub here: . # Get Token Metadata by Name Content for build/indexer/indexer-api/token-metadata could not be fully rendered due to component compatibility issues. # Indexer SDK While the Indexer API is a powerful tool for querying basic on-chain data, it may not always provide the exact data you need. In most cases, you want to index your own contract and to do that, you can create your own custom processor using the Indexer SDK. ## Using the Indexer SDK [Section titled “Using the Indexer SDK”](#using-the-indexer-sdk) Learn how to use the Indexer SDK through guides and documentation. [Quickstart Guide ](/build/indexer/indexer-sdk/quickstart)Get started with the Indexer SDK [Documentation ](/build/indexer/indexer-sdk/documentation)Read documentation about the Indexer SDK ## Example Processors [Section titled “Example Processors”](#example-processors) As a reference, you can see all Aptos-Hosted processors that comprise the Indexer API [here](https://github.com/aptos-labs/aptos-indexer-processors-v2). # Migrate to Indexer SDK This guide contains instructions on how to migrate your legacy custom processor (that’s written in the [old way](https://github.com/aptos-labs/aptos-indexer-processors/blob/aptos-indexer-processors-v1.20.0/rust/processor/src/processors/events_processor.rs)) to Indexer SDK. ## 1. Clone the example repo [Section titled “1. Clone the example repo”](#1-clone-the-example-repo) We use example events processor in `aptos-indexer-processor-example` as a starting point for the migration. ```shellscript git clone https://github.com/aptos-labs/aptos-indexer-processor-example.git ``` ## 2. Migrate your processor config [Section titled “2. Migrate your processor config”](#2-migrate-your-processor-config) Previously, you would create a branch of `aptos-indexer-processors` and update the processor config to include your custom processor. This legacy approach made it very difficult to upgrade your processor. To address this, the SDK no longer depends on `aptos-indexer-processors`. As a result, you’ll need to define your own `IndexerProcessorConfig` and `ProcessorConfig` structs. The `IndexerProcessorConfig` defines the base configuration for all processors that you’ll be running. The `ProcessorConfig` is an enum that contains all the individual processor configs. Update the following files in your project: * [`ProcessorConfig`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/config/processor_config.rs): Replace `EventsProcessor` with your processor. * [`IndexerProcessorConfig`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/config/indexer_processor_config.rs): Update the `.run()` method to include your processor. If you’d like to read more about configuration in the SDK, take a look at the [Create a Processor](/build/indexer/indexer-sdk/documentation/create-processor) guide. ## 3. Migrate processing logic to steps [Section titled “3. Migrate processing logic to steps”](#3-migrate-processing-logic-to-steps) In the old way, you defined your processor’s logic by implementing `ProcessorTrait`’s `process_transactions` method. Example events processor written with the old way: ```rust #[async_trait] impl ProcessorTrait for EventsProcessor { async fn process_transactions( ... ) -> anyhow::Result { // Extract events from transactions let events: Vec = process_events(transactions); // Store the events in the database let tx_result = insert_to_db( self.get_pool(), self.name(), start_version, end_version, &events, &self.per_table_chunk_sizes, ) .await; return tx_result; } } async fn insert_to_db( conn: ArcDbPool, name: &'static str, start_version: u64, end_version: u64, events: &[EventModel], per_table_chunk_sizes: &AHashMap, ) -> Result<(), diesel::result::Error> { tracing::trace!( name = name, start_version = start_version, end_version = end_version, "Inserting to db", ); execute_in_chunks( conn, insert_events_query, events, get_config_table_chunk_size::("events", per_table_chunk_sizes), ) .await?; Ok(()) } ``` With the SDK, we’ve introduced the concept of steps, which represent independent units of processing logic. In the `EventsProcessor` example, the extraction of events and storing them in the database can be broken down into two steps. To migrate your processor to the SDK, you’ll need to define these steps in your processor. You can use the `EventsExtractor` and `EventsStorer` steps in the example as a starting point for defining your own steps. Make the following changes to [`events_extractor.rs`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/processors/events/events_extractor.rs). ```rust // TODO: Update the step name pub struct EventsExtractor where Self: Sized + Send + 'static, {} #[async_trait] impl Processable for EventsExtractor { type Input = Vec; // TODO: Update the output type // This should be the data model you're extracting from the transactions type Output = Vec; type RunType = AsyncRunType; async fn process( &mut self, item: TransactionContext>, ) -> Result>>, ProcessorError> { // TODO: Update extraction logic. // This should be the same as the extraction logic in the old `process_transactions` method let events = item .data .par_iter() .map(|txn| { process_events(txn) }) .flatten() .collect::>(); Ok(Some(TransactionContext { data: events, metadata: item.metadata, })) } } ``` Make the following changes to [`events_storer.rs`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/processors/events/events_processor.rs). ```rust pub struct EventsStorer where Self: Sized + Send + 'static, { conn_pool: ArcDbPool, processor_config: DefaultProcessorConfig, } impl EventsStorer { pub fn new(conn_pool: ArcDbPool, processor_config: DefaultProcessorConfig) -> Self { Self { conn_pool, processor_config, } } } #[async_trait] // TODO: Update step name impl Processable for EventsStorer { // TODO: Update input type for the step. // The input type should match the output type of the extractor step. type Input = Vec; type Output = (); type RunType = AsyncRunType; async fn process( &mut self, events: TransactionContext>, ) -> Result>, ProcessorError> { let per_table_chunk_sizes: AHashMap = AHashMap::new(); let execute_res = execute_in_chunks( self.conn_pool.clone(), // TODO: Update this to the insertion query of your old processor insert_events_query, &events.data, get_config_table_chunk_size::("events", &per_table_chunk_sizes), ) .await; match execute_res { Ok(_) => { Ok(Some(TransactionContext { data: (), metadata: events.metadata, })) }, Err(e) => Err(ProcessorError::DBStoreError { message: format!( "Failed to store events versions {} to {}: {:?}", events.metadata.start_version, events.metadata.end_version, e, ), query: None, }), } } } impl AsyncStep for EventsStorer {} impl NamedStep for EventsStorer { fn name(&self) -> String { "EventsStorer".to_string() } } ``` ## 4. Migrate your processor [Section titled “4. Migrate your processor”](#4-migrate-your-processor) Now that we’ve migrated the processing logic to steps, we need to also migrate the processor to instantiate the steps and connect them together. In [`events_processor.rs`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/processors/events/events_processor.rs), make the following changes: ```rust // TODO: Update processor name pub struct EventsProcessor { pub config: IndexerProcessorConfig, pub db_pool: ArcDbPool, // If you have any other fields in your processor, add them here // You can instantiate them accordingly in the processor's `new` method } ``` In the `run_processor` method, you’ll need to update the code to use the steps you created in [Step 3](#3-migrate-processing-logic-to-steps). ```rust pub async fn run_processor(self) -> Result<()> { {...} // Define processor steps let transaction_stream_config = self.config.transaction_stream_config.clone(); let transaction_stream = TransactionStreamStep::new(TransactionStreamConfig { starting_version: Some(starting_version), ..transaction_stream_config }) .await?; // TODO: Replace the next 2 lines with your steps let events_extractor = EventsExtractor {}; let events_storer = EventsStorer::new(self.db_pool.clone()); let version_tracker = VersionTrackerStep::new( get_processor_status_saver(self.db_pool.clone(), self.config.clone()), DEFAULT_UPDATE_PROCESSOR_STATUS_SECS, ); // Connect processor steps together let (_, buffer_receiver) = ProcessorBuilder::new_with_inputless_first_step( transaction_stream.into_runnable_step(), ) // TODO: Replace the next 2 lines with your steps .connect_to(events_extractor.into_runnable_step(), 10) .connect_to(events_storer.into_runnable_step(), 10) .connect_to(version_tracker.into_runnable_step(), 10) .end_and_return_output_receiver(10); {...} } ``` ## 5. Update your `config.yaml` [Section titled “5. Update your config.yaml”](#5-update-your-configyaml) `IndexerProcessorConfig` reworks the format of the `config.yaml` file. Use the example [`config.yaml`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/config.yaml). ```yaml health_check_port: 8085 server_config: processor_config: # TODO: Update with processor type type: "events_processor" transaction_stream_config: indexer_grpc_data_service_address: "https://grpc.testnet.aptoslabs.com:443" # TODO: Update auth token auth_token: "AUTH_TOKEN" # TODO: Update with processor name request_name_header: "events-processor" db_config: # TODO: Update with your database connection string postgres_connection_string: postgresql://postgres:@localhost:5432/example # backfill_config: # backfill_alias: "events_processor_backfill_1" ``` ## 6. Run your migrated processor [Section titled “6. Run your migrated processor”](#6-run-your-migrated-processor) ```shellscript cd ~/{DIRECTORY_OF_PROJECT}/aptos-indexer-processor-example cargo run --release -- -c config.yaml ``` In your terminal, you should start to see logs like this: ```shellscript {"timestamp":"2025-01-13T21:23:21.785452Z","level":"INFO","message":"[Transaction Stream] Successfully connected to GRPC stream","stream_address":"https://grpc.mainnet.aptoslabs.com/","connection_id":"ec67ecc4-e041-4f17-a2e2-441e7ff21487","start_version":2186504987,"filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/transaction-stream/src/transaction_stream.rs","line_number":349,"threadName":"tokio-runtime-worker","threadId":"ThreadId(4)"} {"timestamp":"2025-01-13T21:23:21.785664Z","level":"INFO","message":"Spawning polling task","step_name":"TransactionStreamStep","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/pollable_async_step.rs","line_number":112,"threadName":"tokio-runtime-worker","threadId":"ThreadId(23)"} {"timestamp":"2025-01-13T21:23:21.785693Z","level":"INFO","message":"Spawning processing task","step_name":"TransactionStreamStep","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/pollable_async_step.rs","line_number":204,"threadName":"tokio-runtime-worker","threadId":"ThreadId(23)"} {"timestamp":"2025-01-13T21:23:21.785710Z","level":"INFO","message":"Spawning processing task","step_name":"FungibleAssetExtractor","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/async_step.rs","line_number":87,"threadName":"tokio-runtime-worker","threadId":"ThreadId(4)"} {"timestamp":"2025-01-13T21:23:21.785912Z","level":"INFO","message":"Spawning processing task","step_name":"FungibleAssetStorer","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/async_step.rs","line_number":87,"threadName":"tokio-runtime-worker","threadId":"ThreadId(4)"} {"timestamp":"2025-01-13T21:23:21.785978Z","level":"INFO","message":"Spawning polling task","step_name":"VersionTrackerStep: ()","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/pollable_async_step.rs","line_number":112,"threadName":"tokio-runtime-worker","threadId":"ThreadId(14)"} {"timestamp":"2025-01-13T21:23:21.786018Z","level":"INFO","message":"Spawning processing task","step_name":"VersionTrackerStep: ()","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/pollable_async_step.rs","line_number":204,"threadName":"tokio-runtime-worker","threadId":"ThreadId(14)"} ``` ## 7. Backfilling with the SDK [Section titled “7. Backfilling with the SDK”](#7-backfilling-with-the-sdk) With the SDK, we’ve made some improvements to the backfilling process. There are two options on backfilling: 1. You can keep following the old way of backfilling, which is to run a second instance of the processor and updating `starting_version` to the backfill version. 2. The SDK introduces an improvement where you can track progress of a backfill and start and stop the backfill as needed. If you’d like to use the new backfilling process, update your `config.yaml` like so: ```yaml health_check_port: 8085 server_config: processor_config: # TODO: Update with processor type type: "events_processor" transaction_stream_config: indexer_grpc_data_service_address: "https://grpc.testnet.aptoslabs.com:443" # TODO: Update with backfill version starting_version: {backfill version} # TODO: Update auth token auth_token: "AUTH_TOKEN" # TODO: Update with processor name request_name_header: "events-processor" db_config: # TODO: Update with your database connection string postgres_connection_string: postgresql://postgres:@localhost:5432/example backfill_config: # TODO: Update with your backfill alias. This should be unique for each backfill backfill_alias: "events_processor_backfill_1" ``` # Testing Processor ### What Is a Processor? [Section titled “What Is a Processor?”](#what-is-a-processor) A processor is a core component of the Aptos Indexer that handles blockchain transaction processing. It validates, transforms, and stores transactions into a database, enabling downstream applications like analytics, indexing, and querying. Testing the processor ensures that all transactions are correctly handled, maintaining data accuracy and consistency. ### What Are We Testing With This? [Section titled “What Are We Testing With This?”](#what-are-we-testing-with-this) * **Transaction correctness**: Ensure that each transaction is processed and stored accurately. * **Schema consistency**: Verify that the database schema is correctly set up and maintained throughout the tests. ### General Flow of how Processor Testing Works [Section titled “General Flow of how Processor Testing Works”](#general-flow-of-how-processor-testing-works) 1. You specify the transactions to test 2. Testing framework SDK spins up a mock gRPC Service with the transactions you specified to return when the processor requests transactions. 3. Processor processes the transactions and writes the output to a database. 4. Optionally, you can generate expected database output for validation. Type of Scenarios it Supports: 1. A single transaction 2. A single batch of multiple transactions Input \[A, B, C] 1. Processor processes A, B, and C 3. Sequential multiple transaction batches: Input \[A, B, C] 1. Processor processes A and B 2. Processor processes C ## Prerequisites [Section titled “Prerequisites”](#prerequisites) 1. Ensure Docker Desktop is running for PostgreSQL container support. * **Docker Desktop Installation**: Install Docker Desktop following [this guide](https://docs.docker.com/desktop/) on your machine. * Start Docker Desktop if it’s not running 2. Identify the transactions to test. * Use imported transactions or write your own custom Move scripts to generate test transactions. Refer to [Importing Transaction Guide](/build/indexer/indexer-sdk/advanced-tutorials/txn-importer) and [Generating Transaction using Move Script Guide](/build/indexer/indexer-sdk/advanced-tutorials/txn-script) for detailed instructions. 3. Import aptos-indexer-testing-framework to your Cargo.toml Note \- This tutorial assumes you are using Postgres as the database. * **Adapting to Other Databases**: * Replace PostgreSQL-specific code with relevant database code you intend to use (e.g., MySQL). * Update schema initialization and query methods. * **References to Processor Tests**: * Example: [Event Processor Tests](https://github.com/aptos-labs/aptos-indexer-processors/blob/main/rust/integration-tests/src/sdk_tests/events_processor_tests.rs#L139). ## Steps to Write a Test [Section titled “Steps to Write a Test”](#steps-to-write-a-test) ### 1. Set Up the Test Environment [Section titled “1. Set Up the Test Environment”](#1-set-up-the-test-environment) Before setting up the test environment, it’s important to understand the configurations being used in this step: **What Are These Configurations?** `generate_file_flag` * If `generate_file_flag` is true, the test will overwrite any saved database outputs from previous test runs. If `generate_file_flag` is false, the test will only compare the actual database output with the expected database output and log differences. `custom_output_path` * An optional configuration to specify a custom path where the expected database output will be stored. If not provided, the test will use the default path defined by DEFAULT\_OUTPUT\_FOLDER. `DEFAULT_OUTPUT_FOLDER` * This constant defines the default folder where the system stores output files for the tests. Example: “sdk\_expected\_db\_output\_files”. Modify this value in your configuration if you prefer a different default directory. ```rust let (generate_file_flag, custom_output_path) = get_test_config(); let output_path = custom_output_path.unwrap_or_else(|| format!("{}/imported_mainnet_txns", DEFAULT_OUTPUT_FOLDER)); // Setup DB and replace as needed let mut db = PostgresTestDatabase::new(); db.setup().await.unwrap(); let mut test_context = SdkTestContext::new(&[CONST_VARIABLE_OF_YOUR_TEST_TRANSACTION]); // Replace with your test transaction if test_context.init_mock_grpc().await.is_err() { panic!("Failed to initialize mock grpc"); }; ``` **Explanation of Each Component:** `get_test_config():` This function fetches the configurations (diff\_flag and custom\_output\_path) for the test. Modify or extend this function if you want to support additional custom flags or configurations. output\_path: Combines DEFAULT\_OUTPUT\_FOLDER with the subfolder imported\_mainnet\_txns if no custom\_output\_path is specified. This ensures all output files are stored in a predictable location. `PostgresTestDatabase::new():` Creates a new PostgreSQL database instance for testing. This database is isolated, ensuring no interference with production or other test environments. `SdkTestContext::new():` Initializes the test context with the transaction(s) you want to test. Replace CONST\_VARIABLE\_OF\_YOUR\_TEST\_TRANSACTION with the appropriate variable or constant representing the transaction(s) to be tested. `init_mock_grpc():` Initializes a mock gRPC service for the test. This allows the processor to simulate transactions without interacting with live blockchain data. ### 2. Configure the Processor [Section titled “2. Configure the Processor”](#2-configure-the-processor) Note * Each test runs in an isolated environment using a PostgreSQL container to prevent interference. ```rust let db_url = db.get_db_url(); let transaction_stream_config = test_context.create_transaction_stream_config(); let postgres_config = PostgresConfig { connection_string: db_url.to_string(), db_pool_size: 100, }; let db_config = DbConfig::PostgresConfig(postgres_config); let default_processor_config = DefaultProcessorConfig { per_table_chunk_sizes: AHashMap::new(), channel_size: 100, deprecated_tables: HashSet::new(), }; let processor_config = ProcessorConfig::DefaultProcessor(default_processor_config); let processor_name = processor_config.name(); ``` ### 3. Create the Processor [Section titled “3. Create the Processor”](#3-create-the-processor) ```rust let processor = DefaultProcessor::new(indexer_processor_config) .await .expect("Failed to create processor"); ``` Note: Replace `DefaultProcessor` with the processor you are testing. ### 4. Setup a Query [Section titled “4. Setup a Query”](#4-setup-a-query) Set up a query to load data from the local database and compare it with expected results, see [example loading function](https://github.com/aptos-labs/aptos-indexer-processors/blob/a8f9c5915f4e3f1f596ed3412b8eb01feca1aa7b/rust/integration-tests/src/diff_test_helper/default_processor.rs#L45) ### 5. Setup a Test Context run function [Section titled “5. Setup a Test Context run function”](#5-setup-a-test-context-run-function) Use the test\_context.run() function to execute the processor, validate outputs using your query, and optionally generate database output files: Note Key Considerations: * Each test runs in an isolated environment using a PostgreSQL container to prevent interference. * Proper handling of versions ensures transactions are processed and validated in the correct order. * Validation logic must detect changes or issues by comparing processor output with the expected baseline. ```rust let txn_versions: Vec = test_context .get_test_transaction_versions() .into_iter() .map(|v| v as i64) .collect(); let db_values = test_context .run( &processor, generate_file_flag, output_path.clone(), custom_file_name, move || { let mut conn = PgConnection::establish(&db_url).unwrap_or_else(|e| { eprintln!("[ERROR] Failed to establish DB connection: {:?}", e); panic!("Failed to establish DB connection: {:?}", e); }); let db_values = match load_data(&mut conn, txn_versions.clone()) { Ok(db_data) => db_data, Err(e) => { eprintln!("[ERROR] Failed to load data {}", e); return Err(e); }, }; if db_values.is_empty() { eprintln!("[WARNING] No data found for versions: {:?}", txn_versions); } Ok(db_values) }, ) ``` ### 6. Run the Processor Test [Section titled “6. Run the Processor Test”](#6-run-the-processor-test) Once you have your test ready, run the following command to generate the expected output for validation: ```shellscript cargo test sdk_tests -- generate-output ``` Arguments: generate-output: Set this true if you want to generate or overwrite saved database output, or false if you want to compare database outputs in diff mode. output-path: it’s an optional argument to specify the output path for the db output. The expected database output will be saved in the specified output\_path or `sdk_expected_db_output_files` by default. *** ## FAQ [Section titled “FAQ”](#faq) ### What Types of Tests Does It Support? [Section titled “What Types of Tests Does It Support?”](#what-types-of-tests-does-it-support) * The testing framework allows you to write tests that compare the database outputs of processors. It helps you catch changes in database output when you’re updating or developing your processor. ### What Is `TestContext`? [Section titled “What Is TestContext?”](#what-is-testcontext) `TestContext` is a struct that manages: * `transaction_batches`: A collection of transaction batches. * `postgres_container`: A PostgreSQL container for test isolation. It initializes and manages the database and transaction context for tests. #### What Does `TestContext.run` Do? [Section titled “What Does TestContext.run Do?”](#what-does-testcontextrun-do) This function executes the processor, applies validation logic, and optionally generates output files. #### Key Features: [Section titled “Key Features:”](#key-features) * Flexible Validation: Accepts a user-provided verification function. * Multi-Table Support: Handles data across multiple tables. * Retries: Uses exponential backoff and timeout for retries. * Optional File Generation: Controlled by a flag. #### Example Usage: [Section titled “Example Usage:”](#example-usage) ```rust pub async fn run( &mut self, processor: &impl ProcessorTrait, txn_version: u64, generate_files: bool, // Flag to control file generation output_path: String, // Output path custom_file_name: Option, // Custom file name verification_f: F, // Verification function ) -> anyhow::Result> where ``` ### How to Generate Expected DB Output? [Section titled “How to Generate Expected DB Output?”](#how-to-generate-expected-db-output) Run the following command: ```shellscript cargo test sdk_tests -- --nocapture generate-output ``` Supported Test Args: 1. `generate-output` 2. `output_path` *** ## Troubleshooting and Tips [Section titled “Troubleshooting and Tips”](#troubleshooting-and-tips) 1. **Isolate Tests**: Use Docker containers for database isolation. 2. **Handle Non-Deterministic Fields**: Use helpers like `remove_inserted_at` to clean up timestamps before validation. 3. **Enable Debugging**: Use `eprintln!` for detailed error logging. #### How to Debug Test Failures? [Section titled “How to Debug Test Failures?”](#how-to-debug-test-failures) run following command to get detailed logs: ```shellscript cargo test sdk_tests -- --nocapture ``` # Aptos Indexer Testing Framework Overview The Aptos Indexer Testing Framework provides two ways to generate test transactions: **by Importing Transactions from Network** and **By writing a Move Scripts**. Both approaches are suited for specific scenarios based on your development and testing requirements, enabling you to test how your system handles various transaction types. ## When to Import transactions [Section titled “When to Import transactions”](#when-to-import-transactions) Imported transactions are primarily used to validate processor logic or database integrity by replaying transactions from live networks. ## When to Use **Move Script** to generate transactions [Section titled “When to Use Move Script to generate transactions”](#when-to-use-move-script-to-generate-transactions) Scripted transactions are primarily used to create and test transaction scenarios that are not yet live on the network. In most cases, you should use transaction importing to test your processor logic. ## Summary [Section titled “Summary”](#summary) Aptos-indexer-transaction-generator tool is an essential tool in the Aptos Indexer Testing Framework. Import transactions for replaying and analyzing real-world transactions, while generating transactions with **Move Scripts** is best for testing new AIPs that may impact processing logic. Choose the method that aligns with your testing goals to ensure a comprehensive validation process. ## Next Steps [Section titled “Next Steps”](#next-steps) For detailed instructions on how to use these methods, refer to the following guides: 1. [Importing Transactions](/build/indexer/indexer-sdk/advanced-tutorials/txn-importer) 2. [Generating Transactions with Move Scripts](/build/indexer/indexer-sdk/advanced-tutorials/txn-script) # Importing Transactions ## Overview [Section titled “Overview”](#overview) This guide explains how to import Aptos transactions for testing using the `aptos-indexer-transaction-generator` tool. These test transactions can be used to test your custom processors and support their local development. ## General Flow of Transaction Importing [Section titled “General Flow of Transaction Importing”](#general-flow-of-transaction-importing) First, identify the transaction versions you need to fetch from the Aptos network. This tool interacts with the [Transaction Stream](https://aptos.dev/en/build/indexer/txn-stream) to retrieve transaction data in JSON format. The transactions are then consolidated into a Rust file, where each transaction is represented as a constant variable. These constants can be seamlessly used as mocked inputs in processor automated tests. During testing, the processor fetches the specified transactions, processes them, and writes the results to a database. You can then verify the outcomes by loading the written data and validating it against the expected data. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) 1. A valid API key to connect to [Transaction Stream](https://aptos.dev/en/build/indexer/txn-stream/aptos-hosted-txn-stream) 2. Clone the [aptos-core](https://github.com/aptos-labs/aptos-core) repository: * Navigate to the `aptos-core/ecosystem/indexer-grpc/indexer-transaction-generator` directory. ## How to Import Test Transactions [Section titled “How to Import Test Transactions”](#how-to-import-test-transactions) ### 1. Specify Versions to Import [Section titled “1. Specify Versions to Import”](#1-specify-versions-to-import) Locate and make a copy of the file: ```shellscript ecosystem/indexer-grpc/indexer-transaction-generator/imported_transactions/imported_transactions.yaml ``` In this file, specify the versions to import from Devnet|Testnet|Mainnet by configuring the appropriate endpoint, API key, and mapping version numbers to descriptive output names. An example configuration is shown below: ```yaml testnet: transaction_stream_endpoint: https://grpc.testnet.aptoslabs.com:443 api_key: TESTNET_API_KEY # <--- Replace this with your API key to generate files locally versions_to_import: # Replace these with the versions you want to import 1: 1_genesis 2: 2_new_block_event 3: 3_empty_txn 278556781: 278556781_v1_coin_register_fa_metadata 1255836496: 1255836496_v2_fa_metadata 5979639459: 5979639459_coin_register 5992795934: 5992795934_fa_activities 5523474016: 5523474016_validator_txn mainnet: transaction_stream_endpoint: https://grpc.mainnet.aptoslabs.com:443 api_key: MAINNET_API_KEY versions_to_import: 308783012: 308783012_fa_transfer ``` ### 2. Run the Command to Import Transactions [Section titled “2. Run the Command to Import Transactions”](#2-run-the-command-to-import-transactions) Navigate to the `indexer-transaction-generator` directory: ```shellscript cd aptos-core/ecosystem/indexer-grpc/indexer-transaction-generator ``` To import the specified transaction versions, execute the following command: ```shellscript cargo run -- --testing-folder /path/to/your/imported_transactions.yaml --output-folder /path/to/your/processor-repo/src --mode=import --network=testnet ``` This command will: 1. Read the configuration from the `imported_transactions.yaml` file located in the folder specified by the —testing-folder flag. 2. Fetch the specified transaction versions from the selected network (Devnet, Testnet or Mainnet). 3. Store the resulting JSON files in the specified output folder (/path/to/your/processor-repo/src/json\_transactions). 4. Generate a Rust file (`generated_transactions.rs`) that converts the generated transaction JSON files into constant variables for use in tests. Note: Replace /path/to/your/processor-repo with the path to your processor repository or preferred storage location. **Explanation of Command Flags** 1. `--testing-folder` What is the —testing-folder flag? The —testing-folder flag specifies the directory containing the imported\_transactions.yaml configuration file. The tool uses this folder to read versions you wish to import. * Ensure the folder path matches the location of your imported\_transactions.yaml file. * By default, this guide assumes the configuration is stored in ./imported\_transactions. Adjust the flag value if you place the file elsewhere. 2. `--output-folder` Specifies the destination directory where the generated transaction JSON files and Rust constants will be saved. * Replace /path/to/your/processor-repo with your processor repository src directory or desired storage location. * Ensure this folder is part of your version control setup if these files need to be shared. 3. `--mode` Specifies that the transaction generator should operate in script mode, meaning it will execute Move scripts and generate corresponding transaction data. By default, the mode is set to import, which fetches transactions from the network. or Use Import Mode to fetch transactions from the network. By default, the mode is set to import. Options available: * import * script 4. `--network` Specifies the network to fetch transactions from. Options available: * devnet * testnet * mainnet ## How to Use the Testing Transactions [Section titled “How to Use the Testing Transactions”](#how-to-use-the-testing-transactions) ### Export the Generated File [Section titled “Export the Generated File”](#export-the-generated-file) Update the `mod.rs` file to include the generated Rust file containing the transaction constants. If `mod.rs` doesn’t exist, create one in the target folder: [Reference mod.rs](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/test-transactions-example/src/json_transactions/mod.rs). ### Export the `json_transactions` Folder [Section titled “Export the json\_transactions Folder”](#export-the-json_transactions-folder) Since the `generated_transactions.rs` reles on the `json_transactions` Ensure the `json_transactions` folder is properly exported in the library file for your tests have direct access to the transaction data. [Reference lib.rs](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/test-transactions-example/src/lib.rs). ### Integrate into Test Cases [Section titled “Integrate into Test Cases”](#integrate-into-test-cases) Use the exported transaction constants directly in your test cases to simulate real transactions and validate processing logic. [Example Crate](https://github.com/aptos-labs/aptos-indexer-processor-example/tree/main/test-transactions-example). ## Next Steps [Section titled “Next Steps”](#next-steps) Once the transaction constants are integrated, you can use them in processor tests to validate functionality. For detailed instructions on writing processor tests, refer to Writing Processor Tests. # Generating Transactions with Move Scripts ## Overview: [Section titled “Overview:”](#overview) This section outlines how to create test transactions with Move scripts. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) 1. Clone the [aptos-core](https://github.com/aptos-labs/aptos-core) repository: * Navigate to the `aptos-core/ecosystem/indexer-grpc/indexer-transaction-generator` directory. ## How to Generate Test Transactions using Move Script [Section titled “How to Generate Test Transactions using Move Script”](#how-to-generate-test-transactions-using-move-script) 1. Set up move\_fixtures folder Before proceeding, ensure you have the `move_fixtures` folder set up in the appropriate location: 1. Location: The `move_fixtures` folder should be created in the `aptos-core/ecosystem/indexer-grpc/indexer-transaction-generator/imported_transactions` directory. This is the folder where Move scripts and their configurations for test transactions will be stored. Note **Note:** Do not create the `move_fixtures` folder in your processor repository. All Move-related files should reside in the `aptos-core` repository under the specified directory. 2. Steps to set up the folder: * if starting fresh, remove all existing files and projects in the `move_fixtures` folder in the aptos-core repo * Create your own Move projects/scripts in the move\_fixtures folder (detailed in the next step) 2. Create Your Move Project and Write your Move Script Create your Move project and write a module to output the scenario that you would like to test in your processor. You can refer to an example [here](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/indexer-grpc/indexer-transaction-generator/imported_transactions/move_fixtures). 3. Set Up Test Accounts 1. These accounts will be used to deploy your module. 2. Set up as many accounts as you need. These accounts will be used to send the scripted transactions. Refer to the guide [here](https://aptos.dev/en/build/cli/setup-cli) to create accounts. 3. Update [`aptos-core/ecosystem/indexer-grpc/indexer-transaction-generator/imported_transactions/testing_accounts.yaml`](https://github.com/aptos-labs/aptos-core/blob/main/ecosystem/indexer-grpc/indexer-transaction-generator/imported_transactions/testing_accounts.yaml) with your accounts. Note **Note:** Do not use real accounts here. Only use **test accounts** created in the CLI specifically for testing. Always select **devnet** when setting up a test account, as it will be required later in the script to configure the account profile and fund it using the faucet. 4. Create a Configuration File Each configuration file defines a sequences of transactions for a test scenario. 1. Create a configuration file in the `move_fixtures` [directory](https://github.com/aptos-labs/aptos-core/blob/main/ecosystem/indexer-grpc/indexer-transaction-generator/imported_transactions/move_fixtures). Name the configuration file according to the test scenario it corresponds to. 2. This configuration file should contain unique transaction names and details for each transaction. The transactions should be listed in the order they are to be executed. The configuration file should be structured like this: * output\_name: This field specifies the name of the output file where the results of the transaction will be saved. * script\_path: This field holds the path to the Move script file that will be executed as part of the transaction. * sender\_address: : This field contains the address of the account that will send the transaction. The number of output is totally up to you, but the output name should be unique for each transaction. Add as many transactions as you need to test your processor. ```yaml transactions: - output_name: simple_user_script1 script_path: simple_user_script sender_address: - output_name: simple_user_script2 script_path: simple_user_script2 sender_address: ``` 5. Generate JSON Files and Rust File Once the Move files and configuration are set up, run the same command used to import transactions but with extra flag `mode`: * testing-folder is where your Move files are stored. * output-folder can be set to any folder where you want to store the generated files. * The `--mode=script` flag specifies that the transaction generator should operate in script mode, meaning it will execute Move scripts and generate corresponding transaction data. By default, the mode is set to import, which fetches transactions from the network. ```shellscript cd ~/aptos-core/ecosystem/indexer-grpc/indexer-transaction-generator cargo run -- --testing-folder ./imported_transactions --output-folder ../indexer-test-transactions/src/ --script ``` This command will: 1. Read the configuration in the `move_fixtures` folder. 2. Execute the specified Move scripts. 3. Output the generated JSON files to the designated folder (`~/aptos-core/ecosystem/indexer-grpc/indexer-test-transactions/src/json_transactions`). 4. Overwrite `generated_transactions.rs` with the new transaction data based on the generated JSON files. This file contains the transaction constants that can be used in tests. 6. Verification Verify that the json\_transactions folder in the target directory contains the generated JSON files with the specified names from the configuration file, and ensure that generated\_transactions.rs has been updated accordingly. ## How to Use Test Transactions [Section titled “How to Use Test Transactions”](#how-to-use-test-transactions) ### Export the Generated File [Section titled “Export the Generated File”](#export-the-generated-file) Update the `mod.rs` file to include the generated Rust file containing the transaction constants. If `mod.rs` doesn’t exist, create one in the target folder: [Reference mod.rs](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/test-transactions-example/src/json_transactions/mod.rs). ### Export the `json_transactions` Folder [Section titled “Export the json\_transactions Folder”](#export-the-json_transactions-folder) Since the `generated_transactions.rs` relies on the `json_transactions` Ensure the `json_transactions` folder is properly exported in the library file for your tests have direct access to the transaction data. [Reference lib.rs](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/test-transactions-example/src/lib.rs). ### Integrate into Test Cases [Section titled “Integrate into Test Cases”](#integrate-into-test-cases) If you decided to output the rust file in a different crate, you can update you cargo.toml to import the crate containing the generated file as a dependency. Otherwise, you can simply import the generated file directly in your test file. [Example](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/integration-tests/Cargo.toml#L19). ## Next Steps [Section titled “Next Steps”](#next-steps) Once the transaction constants are integrated, you can use them in processor tests to validate functionality. For detailed instructions on writing processor tests, refer to Writing Processor Tests. [Example](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/integration-tests/src/sdk_tests/events_processor_tests.rs) # Documentation ## Architecture of the Indexer SDK [Section titled “Architecture of the Indexer SDK”](#architecture-of-the-indexer-sdk) In the Aptos indexing stack, a processor indexes a specific subset of data from the blockchain and writes the data into an external database. Each processor follows this general flow: 1. Receive a stream of transactions from [Transaction Stream](/build/indexer/txn-stream) 2. Extract the relevant data from the transactions and transform it into a standardized schema 3. Store the transformed data into a database 4. Keep track of the transaction versions that have been processed The Indexer SDK allows you to write a processor as a directed graph of independent steps. Each `Step` has an input and output, and the output of each `Step` is connected to the input of the next `Step` by a [Kanal channel](https://github.com/fereidani/kanal). ![Indexer SDK Custom Processor Architecture](/_astro/indexer-custom-processor-light.C1inFVLo.svg) ![Indexer SDK Custom Processor Architecture](/_astro/indexer-custom-processor-dark.B-JOMWQ2.svg) ## When to use the Indexer SDK [Section titled “When to use the Indexer SDK”](#when-to-use-the-indexer-sdk) The Indexer SDK is useful when you want to index a custom contract or you realize you need a new kind of data that isn’t available in the [Indexer API](/build/indexer/indexer-api). The general flow to write a custom processor with the Indexer SDK is: 1. Define your database schema 2. Create a new processor 3. Create `Step`s that extract and transform data into your storage schema 4. Customize your processor by adding and connecting steps 5. Run your processor and see the data indexed into your database ## Benefits of the Indexer SDK [Section titled “Benefits of the Indexer SDK”](#benefits-of-the-indexer-sdk) The Indexer SDK’s architecture simplifies writing custom processors in several ways: 1. You can reuse `Step` implementations across processors which reduces duplication of common data extraction logic. 2. The SDK collects basic performance metrics, like the number of transactions processed, for each `Step`, which enables observability into subcomponents of the processor. 3. Since each `Step` is independent, you can safely customize parts of the processor without breaking the other pieces. For example, you can add additional `Step`’s to pre/post-process data or batch data writes. Each `Step` can also be tested in isolation from the rest of the processor. # Advanced Tutorials # Connecting Steps ## Pre-requisite [Section titled “Pre-requisite”](#pre-requisite) At this point, you’d have already followed the [Creating a Processor](/build/indexer/indexer-sdk/documentation/create-processor) and [Creating a Step](/build/indexer/indexer-sdk/documentation/steps) guides. Our next goal is to put those two pieces together and connect steps within the processor. ## How to connect steps [Section titled “How to connect steps”](#how-to-connect-steps) Now that you have created a step, you can connect it to other steps. To do so, we use a builder class called `ProcessorBuilder` to specify a sequence of steps that make up a processor. 1. After you’ve instantiated your steps, you need to convert them into [`RunnableStep`](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/traits/runnable_step.rs#L6). `RunnableStep` is a trait that wraps around a step. It provides the necessary input and output channels that feed into the step and allows the step to be spawned in a task. The SDK provides a helper function [`.into_runnable_step`](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/traits/into_runnable_step.rs#L13) to convert a step into a `RunnableStep`. 2. Setup your first step with [`ProcessorBuilder::new_with_inputless_first_step`](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/builder/processor_builder.rs#L222). In almost all cases, the first step should be a `TransactionStreamStep`. 3. Connect the previous step to the next step using [`.connect_to`](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/builder/processor_builder.rs#L303). `connect_to` uses trait bounds to ensure at compile time that the output type of the previous step matches the input type of the next step. When calling `.connect_to`, a channel gets created with size `channel_size` and connects the previous and next steps. It also spawns a task that continuously loops the previous step — reading data from its input channel, processing the data, and sending the output to its output channel. 4. Repeat step 3 for each step in your processor. 5. To close off the `ProcessorBuilder`, use [`.end_and_return_output_receiver`](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/builder/processor_builder.rs#L400). This returns an [`InstrumentedAsyncReceiver`](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/instrumented-channel/src/lib.rs#L88) which you can use to process the output of the last step in the graph. Here’s a simple example of connecting two steps: ```rust let (processor_builder, buffer_receiver) = ProcessorBuilder::new_with_inputless_first_step( transaction_stream_step.into_runnable_step(), ) .connect_to(extractor_step.into_runnable_step(), 10) .end_and_return_output_receiver(10); ``` Here’s a [full example](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/processors/events/events_processor.rs#L75) from `aptos-indexer-processor-example`. ## Visualizing the processor [Section titled “Visualizing the processor”](#visualizing-the-processor) As you connect steps, `ProcessorBuilder` in the background is constructing a graphical representation of the steps in your processor using [`petgraph`](https://docs.rs/petgraph/latest/petgraph/). You can see the visual representation of the graph by calling ```rust let dot = processor_builder.graph.dot(); println!("{}", dot); ``` This will output a graph in the [DOT language](https://graphviz.gitlab.io/_pages/doc/info/lang.html) that you can visualize using tools like [Graphviz](https://graphviz.org/). # Creating a Processor This guide will walk you through setting up the basic template for a new processor. ## Pre-requisites [Section titled “Pre-requisites”](#pre-requisites) You’ve already set up your environment and have the Indexer SDK `aptos-indexer-sdk` installed. If you haven’t, follow the [Indexer SDK installation guide](/build/indexer/indexer-sdk/documentation/setup). ## Overview [Section titled “Overview”](#overview) Creating and running a processor will require several pieces: 1. `IndexerProcessorConfig` 2. `ProcessorConfig` 3. The processor itself. This is where you’ll define a processor’s config, the processor setup, and the steps that will be run to index transactions. 4. `main.rs` - The main file that will run the processor. The next section goes through each of these pieces more explicitly and provides code examples. ## How to define `IndexerProcessorConfig` [Section titled “How to define IndexerProcessorConfig”](#how-to-define-indexerprocessorconfig) The `IndexerProcessorConfig` defines the base configuration for all processors that you’ll be running. It should include configuration for things that are shared across multiple processors, like the database configuration and [Transaction Stream](/build/indexer/txn-stream) configuration. `ServerArgs` parses a `config.yaml` file and bootstraps a server with all the common pieces to run a processor. To setup the configuration for your processor and make it work with `ServerArgs`, you’ll need to define a `IndexerProcessorConfig` that implements the `RunnableConfig` trait. It also triggers a run method, which can be invoked in `main.rs`. For basic cases, you can copy the [`IndexerProcessorConfig` from the `aptos-indexer-processor-example`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/config/indexer_processor_config.rs) repository and modify it to fit your needs. ## How to define `ProcessorConfig` [Section titled “How to define ProcessorConfig”](#how-to-define-processorconfig) `ProcessorConfig` is an enum that contains all the individual processor configs. It’s used by `IndexerProcessorConfig.run()` to map the processor name to the right `ProcessorConfig`. You can see a basic example of a `ProcessorConfig` [here](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/config/processor_config.rs). An example of a more complex setup that includes multiple processors and configurations is [`aptos-indexer-processors`](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/processor/src/config/processor_config.rs#L84). ## How to create a processor [Section titled “How to create a processor”](#how-to-create-a-processor) Now that you’ve got the configuration pieces set up, the next step is to create the processor. The processor is represented by a struct and is usually named `{PROCESSOR_NAME}Processor`, like `EventsProcessor` or `TokenV2Processor`, depending on the type of data it’s indexing. ```rust pub struct EventsProcessor { pub config: IndexerProcessorConfig, pub db_pool: ArcDbPool, } ``` The processor’s constructor should be defined like so: ```rust pub async fn new(config: IndexerProcessorConfig) -> Result { // Processor setup code here, if needed } ``` It takes in the `IndexerProcessorConfig` that you’ve defined and performs any setup required to instantiate the processor. Next, your processor needs to implement the [`ProcessorTrait`](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/a56b641a6aaca60092fcc9bbd98252f3cd703299/aptos-indexer-processors-sdk/sdk/src/traits/processor_trait.rs#L4). ```rust #[async_trait::async_trait] impl ProcessorTrait for EventsProcessor { fn name(&self) -> &'static str { self.config.processor_config.name() } async fn run_processor(&self) -> Result<()> { // Processor logic here } } ``` The `run_processor` method is the most important method in the processor. If you’re using a migration-based database, like PostgreSQL, running the migrations can go inside of `run_processor`. This is also where we implement logic to determine the appropriate starting version for the processor, verify the chain ID using [Transaction Stream](/build/indexer/txn-stream), and validate the processor’s configuration. `run_processor` also contains the instantiation of the processor’s `Step`s and the specification of how these `Step`s are connected together by channels. ```rust // Instantiate processor steps let transaction_stream = TransactionStreamStep::new(TransactionStreamConfig { starting_version: Some(starting_version), ..self.config.transaction_stream_config.clone() }) .await?; // ... Instantiate the rest of your processor's steps ... // Connect processor steps let (_, buffer_receiver) = ProcessorBuilder::new_with_inputless_first_step( transaction_stream.into_runnable_step(), ) .connect_to(extractor_step.into_runnable_step(), channel_size) .connect_to(storer_step.into_runnable_step(), channel_size) .connect_to(version_tracker_step.into_runnable_step(), channel_size) .end_and_return_output_receiver(channel_size); // Read the results from the output of the last step loop { match buffer_receiver.recv().await { // Do something with th output } } ``` You can see a full example of a processor that indexes raw Aptos events in [`aptos-indexer-processor-example`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/processors/events/events_processor.rs). As a reference, you can also see all of the processors that make up the [Indexer API](/build/indexer/indexer-api) in [`aptos-indexer-processors`](https://github.com/aptos-labs/aptos-indexer-processors-v2/tree/main/processor/src/processors). ## How to define `main.rs` [Section titled “How to define main.rs”](#how-to-define-mainrs) You may copy the [`main.rs`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/main.rs) file from the `aptos-indexer-processor-example`. These lines of code uses the `ServerArgs` and the `IndexerProcessorConfig` that we’ve defined earlier: ```rust let args = ServerArgs::parse(); args.run::(tokio::runtime::Handle::current()) .await ``` # Defining a Data Schema The first step with indexing is choosing a database and defining a schema for the data that you want to store. ## Schema Considerations [Section titled “Schema Considerations”](#schema-considerations) When designing an indexer data schema, consider the following: * Customizability: A schema serves as an interface for your dApp to access data tailored to your specific contract or application. Ensure your schema is customized to meet your dApp’s unique requirements. * Query Optimization: A well-designed schema can enable more efficient data retrieval, supporting advanced operations such as aggregations, complex filtering, and table joins. * Enhanced Performance: Schema design can significantly improve your dApp’s performance. By using the indexer, a single indexer query can often replace multiple queries to the fullnode. ## Aptos Core Processors [Section titled “Aptos Core Processors”](#aptos-core-processors) All data exposed by the [Indexer API](/build/indexer/indexer-api) is initially indexed using custom processors. Each core processor indexes a specific type of data. You can explore the [full list of processors](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/processor/src/db/schema.rs). The Aptos core processors and the [Quickstart Guide](/build/indexer/indexer-sdk/quickstart) use [PostgreSQL](https://www.postgresql.org/) as the database and [Diesel](https://diesel.rs/) as the ORM. If you’d also like to use PostgreSQL and Diesel, you can follow the instructions in [PostgreSQL Installation](/build/indexer/indexer-sdk/quickstart#postgresql-installation). You’re free to use whatever database and ORM you prefer. Popular alternatives include [SeaORM](https://www.sea-ql.org/SeaORM/) and [SQLx](https://github.com/launchbadge/sqlx). If you need guidance, refer to the tutorials linked above for more information. # Running Your Processor ## Pre-requisites [Section titled “Pre-requisites”](#pre-requisites) Please first read [Creating a Processor](/build/indexer/indexer-sdk/documentation/create-processor), [Creating a Step](/build/indexer/indexer-sdk/documentation/steps), and [Connecting Steps](/build/indexer/indexer-sdk/documentation/connect-steps), which will set up your processor and connect your processor steps. ## How to setup your `config.yaml` [Section titled “How to setup your config.yaml”](#how-to-setup-your-configyaml) To run a processor, you’ll need to create a `config.yaml` file. The format of the `config.yaml` file should follow the format you’ve defined in your `IndexerProcessorConfig`. For example, if you’re using the `IndexerProcessorConfig` from [`aptos-indexer-processor-example`](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/config/indexer_processor_config.rs), a basic `config.yaml` would look like this: ```yaml health_check_port: 8085 server_config: processor_config: type: "events_processor" transaction_stream_config: indexer_grpc_data_service_address: "https://grpc.mainnet.aptoslabs.com:443" starting_version: 0 auth_token: "{AUTH_TOKEN}" request_name_header: "events-processor" db_config: postgres_connection_string: postgresql://postgres:@localhost:5432/example ``` The `processor_config` field should match how `ProcessorConfig` is defined in the `IndexerProcessorConfig`, and the same applies for `db_config` and `DbConfig`. `TransactionStreamConfig` is a config provided by the `transaction-stream` crate. It requires `indexer_grpc_data_service_address`, `auth_token`, and `request_name_header` to be set. To get the `indexer_grpc_data_service_address` and `auth_token`, you can follow the guide [here](/build/indexer/txn-stream/aptos-hosted-txn-stream). `TransactionStreamConfig` also supports more optional fields to modify the connection to [Transaction Stream](/build/indexer/txn-stream), which you can learn more about [here](https://github.com/aptos-labs/aptos-indexer-processor-sdk/tree/main/aptos-indexer-processors-sdk/transaction-stream). ## Running your processor [Section titled “Running your processor”](#running-your-processor) Once your `config.yaml` is setup, you can run your processor with: ```shellscript cd /path/to/your/processor/crate cargo run --release -- -c config.yaml ``` In your terminal, you should start to see logs like this: ```shellscript {"timestamp":"2025-01-13T21:23:21.785452Z","level":"INFO","message":"[Transaction Stream] Successfully connected to GRPC stream","stream_address":"https://grpc.mainnet.aptoslabs.com/","connection_id":"ec67ecc4-e041-4f17-a2e2-441e7ff21487","start_version":2186504987,"filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/transaction-stream/src/transaction_stream.rs","line_number":349,"threadName":"tokio-runtime-worker","threadId":"ThreadId(4)"} {"timestamp":"2025-01-13T21:23:21.785664Z","level":"INFO","message":"Spawning polling task","step_name":"TransactionStreamStep","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/pollable_async_step.rs","line_number":112,"threadName":"tokio-runtime-worker","threadId":"ThreadId(23)"} {"timestamp":"2025-01-13T21:23:21.785693Z","level":"INFO","message":"Spawning processing task","step_name":"TransactionStreamStep","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/pollable_async_step.rs","line_number":204,"threadName":"tokio-runtime-worker","threadId":"ThreadId(23)"} {"timestamp":"2025-01-13T21:23:21.785710Z","level":"INFO","message":"Spawning processing task","step_name":"FungibleAssetExtractor","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/async_step.rs","line_number":87,"threadName":"tokio-runtime-worker","threadId":"ThreadId(4)"} {"timestamp":"2025-01-13T21:23:21.785912Z","level":"INFO","message":"Spawning processing task","step_name":"FungibleAssetStorer","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/async_step.rs","line_number":87,"threadName":"tokio-runtime-worker","threadId":"ThreadId(4)"} {"timestamp":"2025-01-13T21:23:21.785978Z","level":"INFO","message":"Spawning polling task","step_name":"VersionTrackerStep: ()","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/pollable_async_step.rs","line_number":112,"threadName":"tokio-runtime-worker","threadId":"ThreadId(14)"} {"timestamp":"2025-01-13T21:23:21.786018Z","level":"INFO","message":"Spawning processing task","step_name":"VersionTrackerStep: ()","filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e6867c5/aptos-indexer-processors-sdk/sdk/src/traits/pollable_async_step.rs","line_number":204,"threadName":"tokio-runtime-worker","threadId":"ThreadId(14)"} ``` # Initial Setup If you’re creating a custom processor from scratch, we recommend following the [Quickstart Guide](/build/indexer/indexer-sdk/quickstart). The quickstart guide provides a template processor and includes all of this setup. If you’re migrating an existing processor to the Indexer SDK, follow the steps below. Add `aptos-indexer-processor-sdk` to your `Cargo.toml`. ```toml [dependencies] aptos-indexer-processor-sdk = { git = "https://github.com/aptos-labs/aptos-indexer-processor-sdk.git", rev = "aptos-indexer-processor-sdk-v1.0.0" } ``` `aptos-indexer-processor-sdk` includes the following features: 1. `postgres_full` - Interface layer to integrate Postgres with your processor. 2. `testing_framework` - An e2e testing framework for testing processors. If you want to write tests for your processor, add this feature to the crate. # Creating a Step ## What is a step? [Section titled “What is a step?”](#what-is-a-step) A step is a unit of processing logic in the SDK and can be used to define logic for the extraction, transformation, or storing of data. Steps are the building blocks of a processor. The Aptos core processors represent (1) getting a stream of transactions from [Transaction Stream](/build/indexer/txn-stream), (2) extracting the data, (3) writing to a database, and (4) tracking the progress, each as separate steps. There are two types of steps in the SDK: 1. **AsyncStep**: Processes a batch of input items and returns a batch of output items. 2. **PollableAsyncStep**: Does the same as `AsyncStep`, but it also periodically polls its internal state and returns a batch of output items if available. ## How to create a Step [Section titled “How to create a Step”](#how-to-create-a-step) To create a step with the SDK, follow these instructions: 1. Implement the `Processable` trait. This trait defines several important details about the step: the input and output types, the processing logic, and the run type (either `AsyncStepRunType` or `PollableAsyncStepRunType`). ```rust #[async_trait] impl Processable for MyExtractorStep { // The Input is a vec of Transaction type Input = Vec; // The Output is a vec of MyData type Output = Vec; // Depending on the type of step this is, the RunType is either // - AsyncRunType // - PollableAsyncRunType type RunType = AsyncRunType; // Processes a batch of input items and returns a batch of output items. async fn process( &mut self, input: TransactionContext>, ) -> Result>>, ProcessorError> { let transactions = input.data; let data = transactions.iter().map(|transaction| { // Define the processing logic to extract MyData from a Transaction }).collect(); Ok(Some(TransactionContext { data, metadata: input.metadata, })) } } ``` Note In most cases, you’re going to be processing a list of inputs to a list of outputs. To speed up the processing, we recommend using [`rayon`](https://docs.rs/rayon/latest/rayon/) to process sequential computations in parallel. You can see an example of how we use [`rayon.par_iter`](https://docs.rs/rayon/latest/rayon/#basic-usage-and-the-rayon-prelude) to parallelize the processing [here](https://github.com/aptos-labs/aptos-indexer-processor-example/blob/main/aptos-indexer-processor-example/src/processors/events/events_extractor.rs#L30). In the example code above, you’ll notice that the input and output types are wrapped within a `TransactionContext`. `TransactionContext` contains relevant metadata about the batch of data being processed, such as the transaction versions and timestamp, and are used for metrics and logging. 2. Implement the `NamedStep` trait. This is used for logging. ```rust impl NamedStep for MyExtractorStep { fn name(&self) -> String { "MyExtractorStep".to_string() } } ``` 3. Implement either `AsyncStep` trait or `PollableAsyncStep` trait, which defines how the step will be run in the processor. 1. If you’re using `AsyncStep`, add this to your code: ```rust impl AsyncStep for MyExtractorStep {} ``` 2. If you’re creating a `PollableAsyncStep`, you will need to define the poll interval and what the step should do every time it polls. ```rust #[async_trait] impl PollableAsyncStep for MyPollStep where Self: Sized + Send + Sync + 'static, T: Send + 'static, { fn poll_interval(&self) -> std::time::Duration { // Define duration } async fn poll(&mut self) -> Result>>, ProcessorError> { // Define code here on what this step should do every time it polls // Optionally return a batch of output items } } ``` ## Parsing Transactions [Section titled “Parsing Transactions”](#parsing-transactions) When building the extractor step, you’ll need to define how you want to parse your data from transactions. Read more about how to parse your data from transactions [here](/build/indexer/indexer-sdk/documentation/steps/parsing-txns). ## Common SDK steps [Section titled “Common SDK steps”](#common-sdk-steps) The SDK comes with a set of [common steps](https://github.com/aptos-labs/aptos-indexer-processor-sdk/tree/main/aptos-indexer-processors-sdk/sdk/src/common_steps) that you can use to build your processor. 1. `TransactionStreamStep` provides a stream of Aptos transactions to the processor. Read more about it [here](/build/indexer/indexer-sdk/documentation/steps/transaction-stream). 2. `TimedBufferStep` buffers a batch of items and periodically polls to release the items to the next step 3. `VersionTrackerStep` tracks the progress of the processor and checkpoints the processor’s progress. Read more about it [here](/build/indexer/indexer-sdk/documentation/version-tracking). 4. `OrderByVersionStep` orders transaction contextx by their starting versions. It buffers ordered these contexts and releases them at every poll interval. 5. `WriteRateLimitStep` limits the number of bytes written to the database per second. # Parsing Transactions Fundamentally an indexer processor is just something that consumes a stream of a transactions and writes processed data to storage. Let’s dive into what a transaction is and what kind of information you can extract from one. ## What is a transaction? [Section titled “What is a transaction?”](#what-is-a-transaction) A transaction is a unit of execution on the Aptos blockchain. If the execution of the program in a transaction (e.g. starting with an entry function in a Move module) is successful, the resulting change in state will be applied to the ledger. Learn more about the transaction lifecycle at [this page](/network/blockchain/blockchain-deep-dive#life-of-a-transaction). There are four types of transactions on Aptos: * Genesis * Block metadata transactions * State checkpoint transactions * User transactions The first 3 of these are internal to the system and are not relevant to most processors; we do not cover them in this guide. Generally speaking, most user transactions originate from a user calling an entry function in a Move module deployed on chain, for example `0x1::coin::transfer`. In all other cases they originate from [Move scripts](/build/smart-contracts/scripts). You can learn more about the different types of transactions [here](/network/blockchain/txns-states#types-of-transaction-payloads). A user transaction that a processor handles contains a variety of information. At a high level it contains: * The payload that was submitted. * The changes to the ledger resulting from the execution of the function / script. We’ll dive into this in the following sections. ## What is important in a transaction? [Section titled “What is important in a transaction?”](#what-is-important-in-a-transaction) ### Payload [Section titled “Payload”](#payload) The payload is what the user submits to the blockchain when they wish to execute a Move function. Some of the key information in the payload is: * The sender address * The address + module name + function name of the function being executed. * The arguments to the function. There is other potentially interesting information in the payload that you can learn about at [this page](/network/blockchain/txns-states#contents-of-a-transaction). ### Events [Section titled “Events”](#events) Events are emitted during the execution of a transaction. Each Move module can define its own events and choose when to emit the events during execution of a function. For example, in Move you might have the following: ```move struct MemberInvitedEvent has store, drop { member: address, } public entry fun invite_member(member: address) { event::emit_event( &mut member_invited_events, MemberInvitedEvent { member }, ); } ``` If `invite_member` is called, you will find the `MemberInvitedEvent` in the transaction. Note Why emit events? This is a good question! In some cases, you might find it unnecessary to emit events since you can just parse the writesets. However, sometimes it is quite difficult to get all the data you need from the different “locations” in the transaction, or in some cases it might not even be possible, e.g. if you want to index data that isn’t included in the writeset. In these cases, events are a convenient way to bundle together everything you want to index. ### Writesets [Section titled “Writesets”](#writesets) When a transaction executes, it doesn’t directly affect on-chain state right then. Instead, it outputs a set of changes to be made to the ledger, called a writeset. The writeset is applied to the ledger later on after all validators have agreed on the result of the execution. Writesets show the end state of the on-chain data after the transaction has occurred. They are the source of truth of what data is stored on-chain. There are several types of write set changes: * Write module / delete module * Write resource / delete resource * Write table item / delete table item # Transaction Stream Step The `TransactionStreamStep` is a foundational component in the transaction processing pipeline. It establishes a gRPC connection with the `TransactionStream` service, fetches transactions in batches, and outputs them for further processing. This step also manages connection retries and reconnections in case of transient failures. Typically, this is the initial step in a processor, responsible for streaming transactions for downstream steps. ## Key Responsibilities [Section titled “Key Responsibilities”](#key-responsibilities) 1. **Fetch Transactions**: Retrieves transaction batches from a gRPC service. 2. **Manage Connections**: Handles gRPC reconnections to ensure a resilient stream. 3. **Provide Metadata**: Attaches contextual information like versions and timestamps to the transactions. ## Struct Definition [Section titled “Struct Definition”](#struct-definition) The `TransactionStreamStep` struct is defined as follows: ```rust pub struct TransactionStreamStep where Self: Sized + Send + 'static, { transaction_stream_config: TransactionStreamConfig, pub transaction_stream: Mutex, } ``` ## How It Works [Section titled “How It Works”](#how-it-works) * The `TransactionStreamStep` connects to the gRPC `TransactionStream` service. * It continuously polls for new transactions using the `poll` method. * Each batch is wrapped in a `TransactionContext`, which includes metadata such as: * Start and end versions. * Timestamps of transactions. * Batch size in bytes. * If the connection is interrupted, it attempts to reconnect seamlessly. # Version Tracking ## Version Tracking [Section titled “Version Tracking”](#version-tracking) The `VersionTrackerStep` is a [common step in the SDK](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/processor/src/processors/events/events_processor.rs#L125) method as other steps. Upon a successfully processed batch, the `VersionTrackerStep` will [call](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/common_steps/version_tracker_step.rs#L57) the trait implementation of `save_processor_status()`. ### ProcessorStatusSaver [Section titled “ProcessorStatusSaver”](#processorstatussaver) The `ProcessorStatusSaver` trait requires the implementation of the method `save_processor_status` with the following signature: ```rust async fn save_processor_status( &self, last_success_batch: &TransactionContext<()>, ) -> Result<(), ProcessorError>; ``` This method is where checkpointing should be written. If you’re writing to Postgres, you can use the SDK’s Postgres implementation [here](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/postgres/utils/checkpoint.rs#L66). It is possible to checkpoint progress in different ways by using enums. The SDK’s Postgres implementation inserts using a simple [`processor_status` model](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/postgres/models/processor_status.rs). ## Restart Behavior [Section titled “Restart Behavior”](#restart-behavior) Now that the processor successfully writes to the chosen store for version tracking, upon restarting it needs to retrieve the latest successful version from that store. [Here is an example](https://github.com/aptos-labs/aptos-indexer-processor-sdk/blob/main/aptos-indexer-processors-sdk/sdk/src/postgres/utils/checkpoint.rs#L118) of a `get_starting_version()` method that returns the latest processed version saved. This `starting_version: u64` can then be used as below. If there is no checkpoint, the processor will start from the beginning of the chain. ```rust let transaction_stream = TransactionStreamStep::new(TransactionStreamConfig { starting_version: Some(starting_version), ..self.config.transaction_stream_config.clone() }) .await?; ``` ## Backfilling [Section titled “Backfilling”](#backfilling) The SDK does not provide an implementation of `ProcessorStatusSaver` that will save backfill progress. To enable saving backfill progress, `IndexerProcessorConfig`, `ProcessorStatusSaver` and `get_starting_version()` need some updates. Without these changes, it is difficult to run a live processor at the latest transaction version as well as a backfill processor. ### Updates to Config [Section titled “Updates to Config”](#updates-to-config) [Add an additional field](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/processor/src/config/processor_mode.rs#L29) on your `IndexerProcessorConfig` for a `BackfillConfig`. In this implementation, the `BackfillConfig` is part of an enum `ProcessorMode` that is used to determine the mode the processor is running in. In backfill mode, the processor starts from a different version and the progress is saved in a separate table. ### Updates to `config.yaml` [Section titled “Updates to config.yaml”](#updates-to-configyaml) Add the `backfill_config` section to `server_config` in your yaml file to set `backfill_alias`. [Example](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/processor/example-backfill-config.yaml) ### Backfill Processor Status Table [Section titled “Backfill Processor Status Table”](#backfill-processor-status-table) Use a separate table for backfill processor status to avoid write conflicts. This table (`backfill_processor_status_table`) uses `backfill_alias` as the primary key instead of `processor_name` to prevent conflicts with the main `processor_status` table when running head and backfill processors concurrently. Create multiple backfill processors with differing `backfill_alias` and transaction version ranges for a faster backfill. Expand on this [implementation](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/processor/src/db/backfill_processor_status.rs). This model introduces a new state, `BackfillStatus`, which is either `InProgress` or `Complete` which will determine the backfilling restart behavior. ### Updates to ProcessorStatusSaver [Section titled “Updates to ProcessorStatusSaver”](#updates-to-processorstatussaver) Expand your `ProcessorStatusSaver` implementation to include a `Backfill` variant that extracts the `backfill_alias` from the `BackfillConfig`, and the `backfill_start_version` `backfill_end_version` from `IndexerProcessorConfig.transaction_stream_config` [like this](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/processor/src/processors/processor_status_saver.rs#L96). Update the corresponding write query to write to the new `backfill_processor_status` table. ### Updates to get\_starting\_version [Section titled “Updates to get\_starting\_version”](#updates-to-get_starting_version) Add a [statement](https://github.com/aptos-labs/aptos-indexer-processors-v2/blob/main/processor/src/processors/processor_status_saver.rs#L190) in your `get_starting_version` method to query the `backfill_processor_status_table` when the `BackfillConfig` field is present in `IndexerProcessorConfig` . # Quickstart Guide on Aptos Indexer SDK ## What to expect from this guide [Section titled “What to expect from this guide”](#what-to-expect-from-this-guide) This guide will walk you through setting up and running a Rust processor to index events on the Aptos blockchain into PostgreSQL. We provide a template processor that you can customize to index events from your custom contracts. By the end of the guide, you should have a basic understanding of how a processor works and be able to customize the processor for your indexing needs. ## Get started [Section titled “Get started”](#get-started) To get started, clone the [aptos-indexer-processor-sdk](https://github.com/aptos-labs/aptos-indexer-processor-sdk) repo. ```text # HTTPS https://github.com/aptos-labs/aptos-indexer-processor-sdk.git # SSH git@github.com:aptos-labs/aptos-indexer-processor-sdk.git ``` Processors consume transactions from the Transaction Stream Service. In order to use the Labs-Hosted Transaction Stream Service you need an authorization token. Follow [this guide](https://aptos.dev/en/build/indexer/txn-stream/aptos-hosted-txn-stream#authorization-via-api-key) to guide to get a token from the Developer Portal. Create an API Key for `Testnet`, as this tutorial is for `Testnet`. Once you’re done, you should have a token that looks like this: ```text aptoslabs_yj4bocpaKy_Q6RBP4cdBmjA8T51hto1GcVX5ZS9S65dx ``` You also need the following tools: * Rust 1.79: [Installation Guide](https://www.rust-lang.org/tools/install) * Cargo: [Installation Guide](https://doc.rust-lang.org/cargo/getting-started/installation.html#install-rust-and-cargo) We use [PostgreSQL](https://www.postgresql.org/) as our database and [Diesel](https://diesel.rs/guides/getting-started) as our ORM in this tutorial. You’re free to use whatever you want, but this tutorial is geared towards PostgreSQL for the sake of simplicity. We use the following database configuration and tools: ### PostgreSQL Installation (for macOS) [Section titled “PostgreSQL Installation (for macOS)”](#postgresql-installation-for-macos) 1. `brew install libpq` ([this is a postgres C API library](https://formulae.brew.sh/formula/libpq)). Also perform all export commands post-installation ```shellscript export PATH="/opt/homebrew/opt/libpq/bin:$PATH" export LDFLAGS="-L/opt/homebrew/opt/libpq/lib" export CPPFLAGS="-I/opt/homebrew/opt/libpq/include" ``` 2. `brew install postgres` 3. `pg_ctl -D /opt/homebrew/var/postgres start` or `brew services start postgresql` 4. `/opt/homebrew/bin/createuser -s postgres` 5. Ensure you’re able to do: `psql postgres` 6. `cargo install diesel_cli --no-default-features --features postgres` 7. Make sure that you’re in the DB folder (run `cd src/db/postgres` from base directory), run `diesel migration run --database-url postgresql://localhost/postgres` a. If for some reason this database is already being used, try a different db. e.g. `DATABASE_URL=postgres://postgres@localhost:5432/indexer_v2 diesel database reset` * We will use a database hosted on `localhost` on the port `5432`, which should be the default. * When you create your username, keep track of it and the password you use for it. * To easily view your database data, consider using a GUI like [DBeaver](https://dbeaver.io/) *recommended*, [pgAdmin](https://www.pgadmin.org/), or [Postico](https://eggerapps.at/postico2/). ## Set up your environment [Section titled “Set up your environment”](#set-up-your-environment) Make sure to start the `postgresql` service: The command for Linux/WSL might be something like: ```shellscript sudo service postgresql start ``` For mac, if you’re using brew, start it up with: ```shellscript brew services start postgresql ``` ## **Configure your processor** [Section titled “Configure your processor”](#configure-your-processor) Now let’s set up the configuration details for the actual indexer processor we’re going to use. ### **Set up your config.yaml file** [Section titled “Set up your config.yaml file”](#set-up-your-configyaml-file) In the example folder, there is a sample config.yaml file that should look something like this: ```yaml # This is a template yaml for the processor health_check_port: 8085 server_config: transaction_stream_config: indexer_grpc_data_service_address: "https://grpc.mainnet.aptoslabs.com:443" auth_token: "AUTH_TOKEN" request_name_header: "events-processor" starting_version: 0 postgres_config: connection_string: postgresql://postgres:@localhost:5432/example ``` Open the `config.yaml` file and update these fields: * `auth_token` - the auth token you got from the Developer Portal * `postgres_connection_string` - connection string to your PostgreSQL database ### More customization with config.yaml [Section titled “More customization with config.yaml”](#more-customization-with-configyaml) You can customize additional configuration with the `config.yaml` file. To start at a specific ledger version, you can specify the version in the `config.yaml` file with: ```yaml starting_version: ``` To stop processing at a specific ledger version, you can specify the ending version with: ```yaml request_ending_version: ``` If you want to use a different network, change the `indexer_grpc_data_service_address` field to the corresponding desired value: ```yaml # Devnet indexer_grpc_data_service_address: grpc.devnet.aptoslabs.com:443 # Testnet indexer_grpc_data_service_address: grpc.testnet.aptoslabs.com:443 # Mainnet indexer_grpc_data_service_address: grpc.mainnet.aptoslabs.com:443 ``` In this tutorial, we are using `testnet` so update the `indexer_grpc_data_service_address` to `grpc.testnet.aptoslabs.com:443`. ## Create the events processor [Section titled “Create the events processor”](#create-the-events-processor) At a high level, each processor is responsible for receiving a stream of transactions, parsing and transforming the relevant data, and storing the data into a database. ### Define the database schema [Section titled “Define the database schema”](#define-the-database-schema) In `src/db/migrations`, you will see the events migration, which defines the database schema that will be used to store the events. ```sql CREATE TABLE events ( sequence_number BIGINT NOT NULL, creation_number BIGINT NOT NULL, account_address VARCHAR(66) NOT NULL, transaction_version BIGINT NOT NULL, transaction_block_height BIGINT NOT NULL, type TEXT NOT NULL, data JSONB NOT NULL, inserted_at TIMESTAMP NOT NULL DEFAULT NOW(), event_index BIGINT NOT NULL, indexed_type VARCHAR(300) NOT NULL, PRIMARY KEY (transaction_version, event_index) ); ``` When you apply migrations, diesel will re-generate the `schema.rs` file, which looks like this: ```rust diesel::table! { events (transaction_version, event_index) { sequence_number -> Int8, creation_number -> Int8, #[max_length = 66] account_address -> Varchar, transaction_version -> Int8, transaction_block_height -> Int8, #[sql_name = "type"] type_ -> Text, data -> Jsonb, inserted_at -> Timestamp, event_index -> Int8, #[max_length = 300] indexed_type -> Varchar, } } ``` In `schema.rs`, you’ll see two other important tables: * `ledger_infos` which tracks the chain id of the ledger being indexed * `processor_status` which tracks the `last_success_version` of the processor ### Define the processing logic [Section titled “Define the processing logic”](#define-the-processing-logic) The file `src/main.rs` contains the code which defines the events processor. The key components are: 1. `insert_events_query` defines the diesel query to insert events into the database. ```rust fn insert_events_query( items_to_insert: Vec, ) -> impl QueryFragment + diesel::query_builder::QueryId + Send { use crate::schema::events::dsl::*; diesel::insert_into(crate::schema::events::table) .values(items_to_insert) .on_conflict((transaction_version, event_index)) .do_nothing() } ``` 2. `process` is a helper function that wraps around a regular processor. In the background, this powerful function handles connecting to Transaction Stream, processing transactions given a transform function that you define, applying database migrations, and tracking the processor’s status. ```rust process( "events_processor".to_string(), // name of the processor that will be used to track the processor status MIGRATIONS, // migrations to be applied to the database async |transactions, conn_pool| { // transform from transaction to events and insert the events into the database }, ).await?; ``` ## Run the processor [Section titled “Run the processor”](#run-the-processor) With the `config.yaml` you created earlier, you’re ready to run the events processor: ```shellscript cd examples/postgres-basic-events-example cargo run --release -- -c config.yaml ``` You should see the processor start to index Aptos blockchain events! ```text {"timestamp":"2024-08-15T01:06:35.169217Z","level":"INFO","message":"[Transaction Stream] Received transactions from GRPC.","stream_address":"https://grpc.testnet.aptoslabs.com/","connection_id":"5575cb8c-61fb-498f-aaae-868d1e8773ac","start_version":0,"end_version":4999,"start_txn_timestamp_iso":"1970-01-01T00:00:00.000000000Z","end_txn_timestamp_iso":"2022-09-09T01:49:02.023089000Z","num_of_transactions":5000,"size_in_bytes":5708539,"duration_in_secs":0.310734,"tps":16078,"bytes_per_sec":18371143.80788713,"filename":"/Users/reneetso/.cargo/git/checkouts/aptos-indexer-processor-sdk-2f3940a333c8389d/e1e1bdd/rust/transaction-stream/src/transaction_stream.rs","line_number":400,"threadName":"tokio-runtime-worker","threadId":"ThreadId(6)"} {"timestamp":"2024-08-15T01:06:35.257756Z","level":"INFO","message":"Events version [0, 4999] stored successfully","filename":"src/processors/events/events_storer.rs","line_number":75,"threadName":"tokio-runtime-worker","threadId":"ThreadId(10)"} {"timestamp":"2024-08-15T01:06:35.257801Z","level":"INFO","message":"Finished processing events from versions [0, 4999]","filename":"src/processors/events/events_processor.rs","line_number":90,"threadName":"tokio-runtime-worker","threadId":"ThreadId(17)"} ``` ## Customize the processor [Section titled “Customize the processor”](#customize-the-processor) In most cases, you want to index events from your own contracts. The example processor offers a good starting point to creating your own custom processor. To customize the processor to index events from your custom contract, you can make these changes: 1. Change the database schema to a format that better matches your dapp or API. a. Create a new migration with diesel: ```shellscript diesel migration generate {migration_name} ``` b. Add your migration changes to `up.sql` and `down.sql`, then apply the migration: ```shellscript diesel migration run --database-url={YOUR_DATABASE_URL} ``` c. The `schema.rs` file will be updated automatically. You can then create a diesel query that uses the new schema. 2. Update the transform logic in `process()`. You can filter by specific event types and extract specific event data from your custom contract ## Migrate from legacy processors [Section titled “Migrate from legacy processors”](#migrate-from-legacy-processors) If you’re migrating from the legacy processors, you can still start with the same steps above to create a new processor with the Indexer SDK. You’ll also need to follow these: 1. Copy your migration files to `src/db/`. 2. With the legacy processors, the processing logic is defined inside the `process_transactions` method. ```rust // Example with the legacy processors #[async_trait] impl ProcessorTrait for EventsProcessor { async fn process_transactions( ... ) -> anyhow::Result { // Extract events from transactions let events: Vec = process_events(transactions); // Store the events in the database let tx_result = insert_to_db( self.get_pool(), self.name(), start_version, end_version, &events, &self.per_table_chunk_sizes, ) .await; return tx_result; } } ``` Migrate to the SDK by copying over the logic in `process_transactions` method to the SDK `process` transform function. ```rust // Example with SDK processor process( "events_processor".to_string(), MIGRATIONS, async |transactions, conn_pool| { // Extract events from transactions let events: Vec = process_events(transactions); // Store events in the database let execute_res = execute_in_chunks( conn_pool.clone(), insert_events_query, &events, MAX_DIESEL_PARAM_SIZE / EventModel::field_count(), ) .await; }, ) .await?; ``` 3. Update the `config.yaml` file to the new format. Update `starting_version` to the version that is last saved in the `processor_status` table. # Legacy Indexer Caution Deprecation Alert From Now - end of Q2, 2024: We will not be adding any new features to the legacy Indexer. However, we will continue to generally support the community, and will make sure that any changes made on the blockchain level does not break the existing legacy processors. After Q2, 2024: We will remove the indexer crates from the [aptos-core](https://github.com/aptos-labs/aptos-core) repo and the legacy indexer will no longer be supported. Please look at our new [Transaction Stream Service](/build/indexer/txn-stream) and updated [Indexer API](/build/indexer) # Custom Data Model Caution This is documentation for the legacy indexer. To learn how to write a custom processor with the latest indexer stack, see [Custom Processors](/build/indexer/custom-processors). ## Define your own data model [Section titled “Define your own data model”](#define-your-own-data-model) Use this method if you want to develop your custom indexer for the Aptos ledger data. Note When should you use the custom indexer? Currently Aptos-provided indexing service (see above) supports the following core Move modules: * `0x1::coin`. * `0x3::token`. * `0x3::token_transfers`. If you need an indexed database for any other Move modules and contracts, then you should develop your custom indexer. Creating a custom indexer involves the following steps. Refer to the indexing block diagram at the start of this document. 1. Define new table schemas, using an ORM like [Diesel](https://diesel.rs/). In this document Diesel is used to describe the custom indexing steps (“Business logic” and the data queries in the diagram). 2. Create new data models based on the new tables (“Business logic” in the diagram). 3. Create a new transaction processor, or optionally add to an existing processor. In the diagram this step corresponds to processing the ledger database according to the new business logic and writing to the indexed database. 4. Integrate the new processor. Optional if you are reusing an existing processor. In the below detailed description, an example of indexing and querying for the coin balances is used. You can see this in the [`coin_processor.rs`](https://github.com/aptos-labs/aptos-core/blob/main/crates/indexer/src/processors/coin_processor.rs). ### 1. Define new table schemas [Section titled “1. Define new table schemas”](#1-define-new-table-schemas) In this example we use [PostgreSQL](https://www.postgresql.org/) and [Diesel](https://diesel.rs/) as the ORM. To make sure that we make backward-compatible changes without having to reset the database at every upgrade, we use [Diesel migrations](https://docs.rs/diesel_migrations/latest/diesel_migrations/) to manage the schema. This is why it is very important to start with generating a new Diesel migration before doing anything else. Make sure you clone the Aptos-core repo by running `git clone https://github.com/aptos-labs/aptos-core.git` and then `cd` into `aptos-core/tree/main/crates/indexer` directory. Then proceed as below. a. The first step is to create a new Diesel migration. This will generate a new folder under [migrations](https://github.com/aptos-labs/aptos-core/tree/main/crates/indexer/migrations) with `up.sql` and `down.sql` ```shellscript DATABASE_URL=postgres://postgres@localhost:5432/postgres diesel migration generate add_coin_tables ``` b. Create the necessary table schemas. This is just PostgreSQL code. In the code shown below, the `up.sql` will have the new changes and `down.sql` will revert those changes. ```sql -- up.sql -- coin balances for each version CREATE TABLE coin_balances ( transaction_version BIGINT NOT NULL, owner_address VARCHAR(66) NOT NULL, -- Hash of the non-truncated coin type coin_type_hash VARCHAR(64) NOT NULL, -- creator_address::name::symbol coin_type VARCHAR(5000) NOT NULL, amount NUMERIC NOT NULL, transaction_timestamp TIMESTAMP NOT NULL, inserted_at TIMESTAMP NOT NULL DEFAULT NOW(), -- Constraints PRIMARY KEY ( transaction_version, owner_address, coin_type_hash ) ); -- latest coin balances CREATE TABLE current_coin_balances {...} -- down.sql DROP TABLE IF EXISTS coin_balances; DROP TABLE IF EXISTS current_coin_balances; ``` See the [full source for `up.sql` and `down.sql`](https://github.com/aptos-labs/aptos-core/tree/main/crates/indexer/migrations/2022-10-04-073529_add_coin_tables). c. Run the migration. We suggest running it multiple times with `redo` to ensure that both `up.sql` and `down.sql` are implemented correctly. This will also modify the [`schema.rs`](https://github.com/aptos-labs/aptos-core/blob/main/crates/indexer/src/schema.rs) file. ```shellscript DATABASE_URL=postgres://postgres@localhost:5432/postgres diesel migration run DATABASE_URL=postgres://postgres@localhost:5432/postgres diesel migration redo ``` ### 2. Create new data schemas [Section titled “2. Create new data schemas”](#2-create-new-data-schemas) We now have to prepare the Rust data models that correspond to the Diesel schemas. In the case of coin balances, we will define `CoinBalance` and `CurrentCoinBalance` as below: ```rust #[derive(Debug, Deserialize, FieldCount, Identifiable, Insertable, Serialize)] #[diesel(primary_key(transaction_version, owner_address, coin_type))] #[diesel(table_name = coin_balances)] pub struct CoinBalance { pub transaction_version: i64, pub owner_address: String, pub coin_type_hash: String, pub coin_type: String, pub amount: BigDecimal, pub transaction_timestamp: chrono::NaiveDateTime, } #[derive(Debug, Deserialize, FieldCount, Identifiable, Insertable, Serialize)] #[diesel(primary_key(owner_address, coin_type))] #[diesel(table_name = current_coin_balances)] pub struct CurrentCoinBalance { pub owner_address: String, pub coin_type_hash: String, pub coin_type: String, pub amount: BigDecimal, pub last_transaction_version: i64, pub last_transaction_timestamp: chrono::NaiveDateTime, } ``` We will also need to specify the parsing logic, where the input is a portion of the transaction. In the case of coin balances, we can find all the details in `WriteSetChanges`, specifically where the write set change type is `write_resources`. **Where to find the relevant data for parsing**: This requires a combination of understanding the Move module and the structure of the transaction. In the example of coin balance, the contract lives in [coin.move](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move), specifically the coin struct (search for `struct Coin`) that has a `value` field. We then look at an [example transaction](https://api.testnet.aptoslabs.com/v1/transactions/by_version/259518) where we find this exact structure in `write_resources`: ```shellscript "changes": [ { ... "data": { "type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", "data": { "coin": { "value": "49742" }, ... ``` See the full code in [coin\_balances.rs](https://github.com/aptos-labs/aptos-core/blob/main/crates/indexer/src/models/coin_models/coin_balances.rs). ### 3. Create a new processor [Section titled “3. Create a new processor”](#3-create-a-new-processor) Now that we have the data model and the parsing function, we need to call that parsing function and save the resulting model in our Postgres database. We do this by creating (or modifying) a `processor`. We have abstracted a lot already from that class, so the only function that should be implemented is `process_transactions` (there are a few more functions that should be copied, those should be obvious from the example). The `process_transactions` function takes in a vector of transactions with a start and end version that are used for tracking purposes. The general flow should be: * Loop through transactions in the vector. * Aggregate relevant models. Sometimes deduping is required, e.g. in the case of `CurrentCoinBalance`. * Insert the models into the database in a single Diesel transaction. This is important, to ensure that we do not have partial writes. * Return status (error or success). Note See [coin\_processor.rs](https://github.com/aptos-labs/aptos-core/blob/main/crates/indexer/src/processors/coin_processor.rs) for a relatively straightforward example. You can search for `coin_balances` in the page for the specific code snippet related to coin balances. **How to decide whether to create a new processor:** This is completely up to you. The benefit of creating a new processor is that you are starting from scratch, so you will have full control over exactly what gets written to the indexed database. The downside is that you will have to maintain a new fullnode, since there is a 1-to-1 mapping between a fullnode and the processor. ### 4. Integrate the new processor [Section titled “4. Integrate the new processor”](#4-integrate-the-new-processor) This is the easiest step and involves just a few additions. 1. To start with, make sure to add the new processor in the Rust code files: [`mod.rs`](https://github.com/aptos-labs/aptos-core/blob/main/crates/indexer/src/processors/mod.rs) and [`runtime.rs`](https://github.com/aptos-labs/aptos-core/blob/main/crates/indexer/src/runtime.rs). See below: [**mod.rs**](https://github.com/aptos-labs/aptos-core/blob/main/crates/indexer/src/processors/mod.rs) ```rust pub enum Processor { CoinProcessor, ... } ... COIN_PROCESSOR_NAME => Self::CoinProcessor, ``` [**runtime.rs**](https://github.com/aptos-labs/aptos-core/blob/main/crates/indexer/src/runtime.rs) ```rust Processor::CoinProcessor => Arc::new(CoinTransactionProcessor::new(conn_pool.clone())), ``` 2. Create a `fullnode.yaml` with the correct configuration and test the custom indexer by starting a fullnode with this `fullnode.yaml`. **fullnode.yaml** ```yaml storage: enable_indexer: true storage_pruner_config: ledger_pruner_config: enable: false indexer: enabled: true check_chain_id: true emit_every: 1000 postgres_uri: "postgres://postgres@localhost:5432/postgres" processor: "coin_processor" fetch_tasks: 10 processor_tasks: 10 ``` Test by starting an Aptos fullnode by running the below command. You will see many logs in the terminal output, so use the `grep` filter to see only indexer log output, as shown below: ```shellscript cargo run -p aptos-node --features "indexer" --release -- -f ./fullnode_coin.yaml | grep -E "_processor" ``` See the full instructions on how to start an indexer-enabled fullnode in [Indexer Fullnode](/build/indexer/legacy/indexer-fullnode). # Run an Indexer Fullnode Caution This is documentation for the legacy indexer. To learn how to run the underlying infrastructure for the latest indexer stack, see [Transaction Stream Service](/build/indexer/txn-stream). Caution The below installation steps are verified only on macOS with Apple Silicon. They might require minor tweaking when running on other builds. ## Summary [Section titled “Summary”](#summary) To run an indexer fullnode, these are the steps in summary: 1. Make sure that you have all the required tools and packages described below in this document. 2. Follow the instructions to [set up a public fullnode](/network/nodes/full-node/verify-pfn) but do not start the fullnode yet. 3. Edit the `fullnode.yaml` as described below in this document. 4. Run the indexer fullnode per the instructions below. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) Install the packages below. Note, you may have already installed many of these while [preparing your development environment](/network/nodes/building-from-source). You can confirm by running `which command-name` and ensuring the package appears in the output (although `libpq` will not be returned even when installed). > Important: If you are on macOS, you will need to [install Docker following the official guidance](https://docs.docker.com/desktop/install/mac-install/) rather than `brew`. For an Aptos indexer fullnode, install these packages: * [`brew`](https://brew.sh/) - `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` Run the commands emitted in the output to add the command to your path and install any dependencies * [`cargo` Rust package manager](https://www.rust-lang.org/tools/install) - `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` * [`docker`](https://docs.docker.com/get-docker/) - `brew install docker` * [libpq Postgres C API library containing the `pg_ctl` command](https://formulae.brew.sh/formula/libpq) - `brew install libpq` Make sure to perform all export commands after the installation. * [`postgres` PostgreSQL server](https://www.postgresql.org/) - `brew install postgresql` * [`diesel`](https://diesel.rs/) - `brew install diesel` ## Set up the database [Section titled “Set up the database”](#set-up-the-database) 1. Start the PostgreSQL server: `brew services start postgresql` 2. Ensure you can run `psql postgres` and then exit the prompt by entering: `\q` 3. Create a PostgreSQL user `postgres` with the `createuser` command (find it with `which`): ```shellscript /path/to/createuser -s postgres ``` 4. Clone `aptos-core` repository if you have not already: ```shellscript git clone https://github.com/aptos-labs/aptos-core.git ``` 5. Navigate (or `cd`) into `aptos-core/crates/indexer` directory. 6. Create the database schema: ```shellscript diesel migration run --database-url postgresql://localhost/postgres ``` This will create a database schema with the subdirectory `migrations` located in this `aptos-core/crates/indexer` directory. If for some reason this database is already in use, try a different database. For example: `DATABASE_URL=postgres://postgres@localhost:5432/indexer_v2 diesel database reset` ## Start the fullnode indexer [Section titled “Start the fullnode indexer”](#start-the-fullnode-indexer) 1. Follow the instructions to set up a [public fullnode](/network/nodes/full-node/verify-pfn) and prepare the setup, but **do not** yet start the indexer (with `cargo run` or `docker run`). 2. Pull the latest indexer Docker image with: ```shellscript docker pull aptoslabs/validator:nightly_indexer ``` 3. Edit the `./fullnode.yaml` and add the following configuration: ```yaml storage: enable_indexer: true # This is to avoid the node being pruned storage_pruner_config: ledger_pruner_config: enable: false indexer: enabled: true postgres_uri: "postgres://postgres@localhost:5432/postgres" processor: "default_processor" check_chain_id: true emit_every: 500 ``` Note Bootstrapping the fullnode Instead of syncing your indexer fullnode from genesis, which may take a long period of time, you can choose to bootstrap your fullnode using backup data before starting it. To do so, follow the instructions to [restore from a backup](/network/nodes/bootstrap-fullnode/aptos-db-restore). Note: indexers cannot be bootstrapped using [a snapshot](/network/nodes/bootstrap-fullnode) or [fast sync](/network/nodes/configure/state-sync#fast-syncing). 1. Run the indexer fullnode with either `cargo run` or `docker run` depending upon your setup. Remember to supply the arguments you need for your specific node: ```shellscript docker run -p 8080:8080 \ -p 9101:9101 -p 6180:6180 \ -v $(pwd):/opt/aptos/etc -v $(pwd)/data:/opt/aptos/data \ --workdir /opt/aptos/etc \ --name=aptos-fullnode aptoslabs/validator:nightly_indexer aptos-node \ -f /opt/aptos/etc/fullnode.yaml ``` or: ```shellscript cargo run -p aptos-node --features "indexer" --release -- -f ./fullnode.yaml ``` ## Restart the indexer [Section titled “Restart the indexer”](#restart-the-indexer) To restart the PostgreSQL server: 1. [Shut down the server](https://www.postgresql.org/docs/8.1/postmaster-shutdown.html) by searching for the `postmaster` process and killing it: ```shellscript ps -ef | grep -i postmaster ``` 2. Copy the process ID (PID) for the process and pass it to the following command to shut it down: ```shellscript kill -INT PID ``` 3. Restart the PostgreSQL server with: ```shellscript brew services restart postgresql@14 ``` # Migrate to Transaction Stream Service This guide contains information on how to migrate to using the Transaction Stream Service if you are currently running a legacy indexer. The old indexer stack requires running an archival fullnode with additional threads to process the transactions which is difficult and expensive to maintain. Adding more custom logic either requires a bulkier machine, or running several fullnodes that scale linearly. This new way of indexing uses the [Transaction Stream Service](/build/indexer/txn-stream). You can either use the [Labs-Hosted Transaction Stream Service](/build/indexer/txn-stream/aptos-hosted-txn-stream) or [run your own instance of Transaction Stream Service](/build/indexer/txn-stream/self-hosted). ## 1. Clone the repo [Section titled “1. Clone the repo”](#1-clone-the-repo) ```shellscript # SSH git clone git@github.com:aptos-labs/aptos-indexer-processors.git # HTTPS git clone https://github.com/aptos-labs/aptos-indexer-processors.git ``` Navigate to the directory for the service: ```shellscript cd aptos-indexer-processors cd rust/processor ``` ## 2. Migrate processors to Transaction Stream Service [Section titled “2. Migrate processors to Transaction Stream Service”](#2-migrate-processors-to-transaction-stream-service) For each processor you’re migrating, you’ll need to create a config file using the template below. You can find more information about each field of the config file [here](/build/indexer/txn-stream/self-hosted#configuration). ```yaml health_check_port: 8084 server_config: processor_config: type: default_processor postgres_connection_string: indexer_grpc_data_service_address: indexer_grpc_http2_ping_interval_in_secs: 60 indexer_grpc_http2_ping_timeout_in_secs: 10 auth_token: starting_version: 0 # optional ending_version: 0 # optional ``` To connect the processor to the Transaction Stream Service, you need to set the URL for `indexer_grpc_data_service_address`. Choose one of the following options. ### Option A: Connect to Labs-Hosted Transaction Stream Service [Section titled “Option A: Connect to Labs-Hosted Transaction Stream Service”](#option-a-connect-to-labs-hosted-transaction-stream-service) The main benefit of using the Labs-Hosted Transaction Stream Service is that you no longer need to run an archival fullnode to get a stream of transactions. This service is rate-limited. Instructions to connect to Labs-Hosted Transaction Stream can be found [here](/build/indexer/txn-stream/aptos-hosted-txn-stream). ### Option B: Run a Self-Hosted Transaction Stream Service [Section titled “Option B: Run a Self-Hosted Transaction Stream Service”](#option-b-run-a-self-hosted-transaction-stream-service) If you choose to, you can run a self-hosted instance of the Transaction Stream Service and connect your processors to it. Instructions to run a Self-Hosted Transaction Stream can be found [here](/build/indexer/txn-stream/self-hosted). ## 3. (Optional) Migrate custom processors to Transaction Stream Service [Section titled “3. (Optional) Migrate custom processors to Transaction Stream Service”](#3-optional-migrate-custom-processors-to-transaction-stream-service) If you have custom processors written with the old indexer, we highly recommend starting from scratch with a new database. Using a new database ensures that all your custom database migrations will be applied during this migration. ### a. Migrate custom table schemas [Section titled “a. Migrate custom table schemas”](#a-migrate-custom-table-schemas) Migrate your custom schemas by copying over each of your custom migrations to the [`migrations`](https://github.com/aptos-labs/aptos-indexer-processors/tree/main/rust/processor/src/db/postgres/migrations) folder. ### b. Migrate custom processors code [Section titled “b. Migrate custom processors code”](#b-migrate-custom-processors-code) Migrate the code by copying over your custom processors to the [`processors`](https://github.com/aptos-labs/aptos-indexer-processors/tree/main/rust/processor) folder and any relevant custom models to the [`models`](https://github.com/aptos-labs/aptos-indexer-processors/tree/main/rust/processor/src/db/common/models) folder. Integrate the custom processors with the rest of the code by adding them to the following Rust code files. [`mod.rs`](https://github.com/aptos-labs/aptos-indexer-processors/blob/main/rust/processor/src/processors/mod.rs) ```rust pub enum Processor { ... CoinProcessor, ... } impl Processor { ... COIN_PROCESSOR_NAME => Self::CoinProcessor, ... } ``` [`worker.rs`](https://github.com/aptos-labs/aptos-indexer-processors/blob/main/rust/processor/src/worker.rs) ```rust Processor::CoinProcessor => { Arc::new(CoinTransactionProcessor::new(self.db_pool.clone())) }, ``` ## 4. Backfill Postgres database with Diesel [Section titled “4. Backfill Postgres database with Diesel”](#4-backfill-postgres-database-with-diesel) Even though the new processors have the same Postgres schemas as the old ones, we recommend you do a complete backfill (ideally writing to a new DB altogether) because some fields are a bit different as a result of the protobuf conversion. These instructions assume you are familiar with using [Diesel migrations](https://docs.rs/diesel_migrations/latest/diesel_migrations/). Run the full database migration with the following command: ```shellscript DATABASE_URL=postgres://postgres@localhost:5432/postgres diesel migration run ``` ## 5. Run the migrated processors [Section titled “5. Run the migrated processors”](#5-run-the-migrated-processors) To run a single processor, use the following command: ```shellscript cargo run --release -- -c config.yaml ``` If you have multiple processors, you’ll need to run a separate instance of the service for each of the processors. If you’d like to run the processor as a Docker image, the instructions are listed here. ## FAQs [Section titled “FAQs”](#faqs) ### 1. Will the protobuf ever be updated, and what do I need to do at that time? [Section titled “1. Will the protobuf ever be updated, and what do I need to do at that time?”](#1-will-the-protobuf-ever-be-updated-and-what-do-i-need-to-do-at-that-time) The protobuf schema may be updated in the future. Backwards incompatible changes will be communicated in release notes. ### 2. What if I already have custom logic written in the old indexer? Is it easy to migrate those? [Section titled “2. What if I already have custom logic written in the old indexer? Is it easy to migrate those?”](#2-what-if-i-already-have-custom-logic-written-in-the-old-indexer-is-it-easy-to-migrate-those) Since the new indexer stack has the same Postgres schema as the old indexer stack, it should be easy to migrate your processors. We still highly recommend creating a new DB for this migration so that any custom DB migrations are applied. Follow Step 3 in this guide to migrate your custom logic over to the new processors stack. # NFT Aggregator API We’ve built a **universal NFT aggregator** for the Aptos ecosystem - normalized activity across all major marketplaces, including **Tradeport**, **Wapal**, **Bluemove**, **Rarible**, and more. We also maintain historical data for deprecated marketplaces like **Topaz**. At its core, the aggregator captures marketplace events in real-time (like listings, token offers, and collection-wide offers) and converts them into clean, structured data. This allows developers to work with a unified data format — no need to handle different marketplace-specific formats manually. ## What We Provide [Section titled “What We Provide”](#what-we-provide) * **[GraphQL API](/build/indexer/nft-aggregator/graphql-api)** for querying activities * **[Analytics REST API](/build/indexer/nft-aggregator/analytics-api)** for aggregated analytics * Marketplace integration support for new partners Our API supports two main use cases: * **Historical Data tracking:** Listings, NFT Token Offers, NFT Collection Offers — in real-time. * **Analytics & Trends:** Aggregate data, market insights, top traders, and more. ## GraphQL API [Section titled “GraphQL API”](#graphql-api) Query real-time marketplace activity across all integrated marketplaces. Use this for: * Basic historical data * Aggregated data (e.g. how many listings are there for a given collection) You can explore it by hand by viewing the Hasura Explorer below for the network you are interested in. * Hasura Console: For direct GraphQL queries to the Aptos-Labs hosted Indexer API, use these endpoints: * Mainnet Graphql Endpoint: you can find the full API reference [here](/build/indexer/nft-aggregator/graphql-api). ## Analytics REST API [Section titled “Analytics REST API”](#analytics-rest-api) Note Analytics API is currently in beta.\ For custom analytics pipelines, we recommend using our gRPC stream for raw structured events. Get high-level insights and historical data on the NFT market. Use this analytics API for: * Total sales volumes * Top buyers and sellers * Marketplace trends You can find the full API reference [here](/build/indexer/nft-aggregator/analytics-api). ## Integrated Marketplaces [Section titled “Integrated Marketplaces”](#integrated-marketplaces) See the full list of marketplaces currently integrated with the NFT Aggregator [here](/build/indexer/nft-aggregator/marketplaces). ## Add Your Marketplace [Section titled “Add Your Marketplace”](#add-your-marketplace) Note We handle most integrations directly with your support If you’d like your marketplace to be included, please reach out to our team. We support both public integrations and private beta partners. ## Next Steps [Section titled “Next Steps”](#next-steps) Ready to dive deeper? * 👉 [GraphQL API](/build/indexer/nft-aggregator/graphql-api) * 👉 [Analytics REST API](/build/indexer/nft-aggregator/analytics-api) * 👉 [Integrated Marketplaces](/build/indexer/nft-aggregator/marketplaces) # Analytics REST API Note The REST API provides collection and marketplace-level insights across the Aptos ecosystem. Use this API to access: * 📊 Marketplace performance metrics * 🥉 Collection-level sales and volume data * 🏆 Top buyers and sellers * 📈 Historical trends and leaderboard data > **Base URL:** `https://api.mainnet.aptoslabs.com/v1/analytics/nft` *** ## Marketplace Endpoints [Section titled “Marketplace Endpoints”](#marketplace-endpoints) ### **Get Marketplace Total Sales Count** [Section titled “Get Marketplace Total Sales Count”](#get-marketplace-total-sales-count) * **GET** `/nft/marketplace/total_sales_count` * **Parameters:** * `marketplace` *(string, required)* — Marketplace identifier (e.g. `topaz`, `wapal`) * **Description:** Returns the total number of completed sales for a given marketplace. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/marketplace/total_sales_count?marketplace=topaz" ``` *** ## Collection Endpoints [Section titled “Collection Endpoints”](#collection-endpoints) ### **Get Collection Total Sales** [Section titled “Get Collection Total Sales”](#get-collection-total-sales) * **GET** `/nft/collection/total_sales_count` * **Parameters:** * `collection_id` *(string, required)* * **Description:** Returns total number of completed sales for the specified collection. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/collection/total_sales_count?collection_id=123" ``` *** ### **Get Collection Top Buyers** [Section titled “Get Collection Top Buyers”](#get-collection-top-buyers) * **GET** `/nft/collection/top_buyer` * **Parameters:** * `collection_id` *(string, required)* * `limit` *(integer, optional, default: 10)* * `offset` *(integer, optional, default: 0)* * **Description:** Returns top buyers in the collection, ranked by total amount spent. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/collection/top_buyer?collection_id=123&limit=5" ``` *** ### **Get Collection Top Sellers** [Section titled “Get Collection Top Sellers”](#get-collection-top-sellers) * **GET** `/nft/collection/top_seller` * **Parameters:** * `collection_id` *(string, required)* * `limit` *(integer, optional, default: 10)* * `offset` *(integer, optional, default: 0)* * **Description:** Returns top sellers in the collection, ranked by total volume sold. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/collection/top_seller?collection_id=123&limit=5" ``` *** ### **Get Collection Total Sales Volume** [Section titled “Get Collection Total Sales Volume”](#get-collection-total-sales-volume) * **GET** `/nft/collection/total_sales_volume` * **Parameters:** * `collection_id` *(string, required)* * **Description:** Returns total trading volume (in APT) for the collection. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/collection/total_sales_volume?collection_id=123" ``` *** ### **List Collections by Trading Volume** [Section titled “List Collections by Trading Volume”](#list-collections-by-trading-volume) * **GET** `/nft/collection/list_by_volume` * **Parameters:** * `limit` *(integer, max: 10)* * `offset` *(integer)* * `time_period` *(string)* — `1h`, `6h`, `24h`, `7d`, `30d` * **Description:** Returns collections sorted by total sales volume within a selected time period. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/collection/list_by_volume?limit=10&offset=0&time_period=1d" ``` *** ### **List Collections by Number of Sales** [Section titled “List Collections by Number of Sales”](#list-collections-by-number-of-sales) * **GET** `/nft/collection/list_by_sales` * **Parameters:** Same as above * **Description:** Returns collections sorted by total number of sales. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/collection/list_by_sales?limit=10&offset=0&time_period=1d" ``` *** ### **List Collections by Floor Price** [Section titled “List Collections by Floor Price”](#list-collections-by-floor-price) * **GET** `/nft/collection/list_by_floor_price` * **Parameters:** Same as above * **Description:** Returns collections sorted by floor price within the selected time period. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/collection/list_by_floor_price?limit=10&offset=0&time_period=1d" ``` *** ### **Get Number of Unique Token Holders** [Section titled “Get Number of Unique Token Holders”](#get-number-of-unique-token-holders) * **GET** `/nft/collection/unique_holders_count` * **Parameters:** * `collection_id` *(string, required)* * **Description:** Returns the number of unique wallet addresses currently holding at least one token from the specified collection. Only current holders (amount > 0) are counted. * **Example:** ```shellscript curl "https://api.mainnet.aptoslabs.com/v1/analytics/nft/collection/unique_holders_count?collection_id=" ``` # GraphQL API Content for build/indexer/nft-aggregator/graphql-api could not be fully rendered due to component compatibility issues. # Integrated Marketplaces Note Explore all marketplaces currently supported by the NFT Aggregator API.\ For each marketplace, we provide detailed event type mappings, example transactions, and integration notes. [Tradeport ](/build/indexer/nft-aggregator/marketplaces/tradeport)View supported events and example transactions for Tradeport [Bluemove ](/build/indexer/nft-aggregator/marketplaces/bluemove)View supported events and example transactions for Bluemove [Wapal ](/build/indexer/nft-aggregator/marketplaces/wapal)View supported events and example transactions for Wapal [Rarible ](/build/indexer/nft-aggregator/marketplaces/rarible)View supported events and example transactions for Rarible ## Deprecated Marketplaces [Section titled “Deprecated Marketplaces”](#deprecated-marketplaces) These marketplaces are no longer operational, but their historical data remains available through our API. [Topaz (Deprecated) ](/build/indexer/nft-aggregator/marketplaces/topaz)Historical data available for reference ## Notes [Section titled “Notes”](#notes) * ✅ All marketplaces share the same core event schema for consistency. * 📖 Use individual marketplace pages for detailed event-to-type mappings and example transactions. * 🚀 Add your marketplace: Reach out to our team! # Bluemove Note This page details all supported event types and example transactions for the Bluemove marketplace. ## Contract Address [Section titled “Contract Address”](#contract-address) | Contract Version | Account Address | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Mainnet | [`0xd1fd99c1944b84d1670a2536417e997864ad12303d19eac725891691b04d614e`](https://explorer.aptoslabs.com/account/0xd1fd99c1944b84d1670a2536417e997864ad12303d19eac725891691b04d614e/modules/code/events?network=mainnet) | *This address is the on-chain account for Bluemove’s contract deployment.* *** ## Supported Event Types [Section titled “Supported Event Types”](#supported-event-types) | Standard Event Type | Raw On-Chain Event Type (entry function) | Example Txn Version | | ---------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- | | **Offers** | | | | `token_offer_created` | `offer_lib::OfferEvent` | [2299166354](https://explorer.aptoslabs.com/txn/2299166354?network=mainnet) | | `token_offer_cancelled` | `offer_lib::CancelOfferEvent` | [2299053567](https://explorer.aptoslabs.com/txn/2299053567?network=mainnet) | | `token_offer_filled` | `offer_lib::AcceptOfferEvent` | [1809917526](https://explorer.aptoslabs.com/txn/1809917526?network=mainnet) | | **Collection Offers** | | | | `collection_offer_created` | `offer_lib::OfferCollectionEvent` | [2403214712](https://explorer.aptoslabs.com/txn/2403214712?network=mainnet) | | `collection_offer_cancelled` | `offer_lib::CancelOfferCollectionEvent` | [2397017908](https://explorer.aptoslabs.com/txn/2397017908?network=mainnet) | | `collection_offer_filled` | `offer_lib::AcceptOfferCollectionEvent` | [2382717056](https://explorer.aptoslabs.com/txn/2382717056?network=mainnet) | | **Listings** | | | | `listing_created` | `marketplaceV2::ListEvent` | [2404863839](https://explorer.aptoslabs.com/txn/2404863839?network=mainnet) | | `listing_cancelled` | `marketplaceV2::DeListEvent` | [2399933805](https://explorer.aptoslabs.com/txn/2399933805?network=mainnet) | | `listing_filled` | `listings_v2::BuyEvent` | [2396025485](https://explorer.aptoslabs.com/txn/2396025485?network=mainnet) | *** ## Related Docs [Section titled “Related Docs”](#related-docs) * 👉 [NFT Aggregator Table Reference](/build/indexer/nft-aggregator/nft-aggregator-table) * 👉 [GraphQL API: Real-time Activity](/build/indexer/nft-aggregator/graphql-api) * 👉 [Integrated Marketplaces Overview](/build/indexer/nft-aggregator/marketplaces) # Rarible Note This page details all supported event types and example transactions for the Rarible marketplace. ## Contract Address [Section titled “Contract Address”](#contract-address) | Contract Version | Account Address | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Mainnet | [`0x465a0051e8535859d4794f0af24dbf35c5349bedadab26404b20b825035ee790`](https://explorer.aptoslabs.com/account/0x465a0051e8535859d4794f0af24dbf35c5349bedadab26404b20b825035ee790/modules/code/events?network=mainnet) | *This address is the on-chain account for Rarible’s contract deployment.* *** ## Supported Event Types [Section titled “Supported Event Types”](#supported-event-types) | Standard Event Type | Raw On-Chain Event Type (entry function) | Example Txn Version | | ---------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- | | **Offers** | | | | `token_offer_created` | `events::TokenOfferPlaced` | [1647787967](https://explorer.aptoslabs.com/txn/1647787967?network=mainnet) | | `token_offer_cancelled` | `events::TokenOfferCancelled` | *No example provided (optional)* | | `token_offer_filled` | `events::TokenOfferFilled` | [1647790684](https://explorer.aptoslabs.com/txn/1647790684?network=mainnet) | | **Collection Offers** | | | | `collection_offer_created` | `events::CollectionOfferPlaced` | [2202390104](https://explorer.aptoslabs.com/txn/2202390104?network=mainnet) | | `collection_offer_cancelled` | `events::CollectionOfferCanceled` | *No example provided (optional)* | | `collection_offer_filled` | `events::CollectionOfferFilled` | [2205653354](https://explorer.aptoslabs.com/txn/2205653354?network=mainnet) | | **Listings** | | | | `listing_created` | `events::ListingPlaced` | [2417694028](https://explorer.aptoslabs.com/txn/2417694028?network=mainnet) | | `listing_cancelled` | `events::ListingCanceled` | [2403151598](https://explorer.aptoslabs.com/txn/2403151598?network=mainnet) | | `listing_filled` | `events::ListingFilled` | [2395762995](https://explorer.aptoslabs.com/txn/2395762995?network=mainnet) | *** ## Related Docs [Section titled “Related Docs”](#related-docs) * 👉 [NFT Aggregator Table Reference](/build/indexer/nft-aggregator/nft-aggregator-table) * 👉 [GraphQL API: Real-time Activity](/build/indexer/nft-aggregator/graphql-api) * 👉 [Integrated Marketplaces Overview](/build/indexer/nft-aggregator/marketplaces) # Topaz (Deprecated) Caution **Marketplace Deprecated**\ Topaz is no longer operational as a marketplace. However, we continue to include its historical data in our NFT Aggregator for reference. ## Contract Address [Section titled “Contract Address”](#contract-address) | Contract Version | Account Address | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Mainnet | [`0x2c7bccf7b31baf770fdbcc768d9e9cb3d87805e255355df5db32ac9a669010a2`](https://explorer.aptoslabs.com/account/0x2c7bccf7b31baf770fdbcc768d9e9cb3d87805e255355df5db32ac9a669010a2/modules/code/events?network=mainnet) | *This address is the on-chain account for Topaz’s contract deployment.* *** ## Supported Event Types [Section titled “Supported Event Types”](#supported-event-types) | Standard Event Type | Raw On-Chain Event Type (entry function) | Example Txn Version | | ---------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- | | **Offers** | | | | `token_offer_created` | `events::BidEvent` | [1645629583](https://explorer.aptoslabs.com/txn/1645629583?network=mainnet) | | `token_offer_cancelled` | `events::CancelBidEvent` | [86119627](https://explorer.aptoslabs.com/txn/86119627?network=mainnet) | | `token_offer_filled` | `events::SellEvent` | [984827420](https://explorer.aptoslabs.com/txn/984827420?network=mainnet) | | **Collection Offers** | | | | `collection_offer_created` | `events::CollectionBidEvent` | [85566357](https://explorer.aptoslabs.com/txn/85566357?network=mainnet) | | `collection_offer_cancelled` | `events::CancelCollectionBidEvent` | [2787969](https://explorer.aptoslabs.com/txn/2787969?network=mainnet) | | `collection_offer_filled` | `events::FillCollectionBidEvent` | [2367804069](https://explorer.aptoslabs.com/txn/2367804069?network=mainnet) | | **Listings** | | | | `listing_created` | `events::ListEvent` | [1964348978](https://explorer.aptoslabs.com/txn/1964348978?network=mainnet) | | `listing_cancelled` | `events::DelistEvent` | [2331658551](https://explorer.aptoslabs.com/txn/2331658551?network=mainnet) | | `listing_filled` | `events::BuyEvent` | [2379182335](https://explorer.aptoslabs.com/txn/2379182335?network=mainnet) | *** ## Historical Data Access [Section titled “Historical Data Access”](#historical-data-access) While Topaz is no longer operational, all historical marketplace events remain accessible through the NFT Aggregator API. You can filter specifically for Topaz marketplace activity using the `marketplace` field: ```graphql query GetTopazHistoricalActivity { nft_marketplace_activities( where: {marketplace: {_eq: "topaz"}} ) { txn_version standard_event_type token_data_id } } ``` *** ## Related Docs [Section titled “Related Docs”](#related-docs) * 👉 [NFT Aggregator Table Reference](/build/indexer/nft-aggregator/nft-aggregator-table) * 👉 [GraphQL API: Real-time Activity](/build/indexer/nft-aggregator/graphql-api) * 👉 [Integrated Marketplaces Overview](/build/indexer/nft-aggregator/marketplaces) # Tradeport Note This page details all supported event types and example transactions for the Tradeport marketplace, across both V1 and V2 contracts. ## Contract Address [Section titled “Contract Address”](#contract-address) | Contract Version | Account Address | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Mainnet | [`0xe11c12ec495f3989c35e1c6a0af414451223305b579291fc8f3d9d0575a23c26`](https://explorer.aptoslabs.com/account/0xe11c12ec495f3989c35e1c6a0af414451223305b579291fc8f3d9d0575a23c26/modules/code/bluemove_v2?network=mainnet) | *This address is the on-chain account for Tradeport’s contract deployment, used across both V1 and V2 versions.* Note **Important:**\ While most marketplaces aim for a single contract supporting both v1 and v2 tokens, Tradeport is an exception. * The **V1 contract exclusively handles Aptos v1 tokens**. * The **V2 contract exclusively handles Aptos v2 tokens**. * Both contracts share the same account but operate independently. When building queries, make sure to align your token standard with the correct contract version for accurate results. *** ## Supported Event Types [Section titled “Supported Event Types”](#supported-event-types) ### V1 Contract [Section titled “V1 Contract”](#v1-contract) | Standard Event Type | Raw On-Chain Event Type (i.e. entry function) | Example Txn Version | | ---------------------------- | --------------------------------------------- | --------------------------------------------------------------------------- | | **Offers** | | | | `token_offer_created` | `biddings::InsertTokenBidEvent` | [2377828353](https://explorer.aptoslabs.com/txn/2377828353?network=mainnet) | | `token_offer_cancelled` | `biddings::DeleteTokenBidEvent` | [2361990811](https://explorer.aptoslabs.com/txn/2361990811?network=mainnet) | | `token_offer_filled` | `biddings::AcceptTokenBidEvent` | [2332332877](https://explorer.aptoslabs.com/txn/2332332877?network=mainnet) | | **Collection Offers** | | | | `collection_offer_created` | `biddings::InsertCollectionBidEvent` | [2386877471](https://explorer.aptoslabs.com/txn/2386877471?network=mainnet) | | `collection_offer_cancelled` | `biddings::DeleteCollectionBidEvent` | [2386876427](https://explorer.aptoslabs.com/txn/2386876427?network=mainnet) | | `collection_offer_filled` | `biddings::AcceptCollectionBidEvent` | [2386481933](https://explorer.aptoslabs.com/txn/2386481933?network=mainnet) | | **Listings** | | | | `listing_created` | `listings::InsertListingEvent` | [2386786871](https://explorer.aptoslabs.com/txn/2386786871?network=mainnet) | | `listing_cancelled` | `listings::DeleteListingEvent` | [2386786127](https://explorer.aptoslabs.com/txn/2386786127?network=mainnet) | | `listing_filled` | `listings::BuyEvent` | [2386133110](https://explorer.aptoslabs.com/txn/2386133110?network=mainnet) | *** ### V2 Contract [Section titled “V2 Contract”](#v2-contract) | Standard Event Type | Raw On-Chain Event Type (i.e. entry function) | Example Txn Version | | ---------------------------- | --------------------------------------------- | --------------------------------------------------------------------------- | | **Offers** | | | | `token_offer_created` | `biddings_v2::InsertTokenBidEvent` | [2386133936](https://explorer.aptoslabs.com/txn/2386133936?network=mainnet) | | `token_offer_cancelled` | `biddings_v2::DeleteTokenBidEvent` | [2386142672](https://explorer.aptoslabs.com/txn/2386142672?network=mainnet) | | `token_offer_filled` | `biddings_v2::AcceptTokenBidEvent` | [2298838662](https://explorer.aptoslabs.com/txn/2298838662?network=mainnet) | | **Collection Offers** | | | | `collection_offer_created` | `biddings_v2::InsertCollectionBidEvent` | [2386891051](https://explorer.aptoslabs.com/txn/2386891051?network=mainnet) | | `collection_offer_cancelled` | `biddings_v2::DeleteCollectionBidEvent` | [2386889884](https://explorer.aptoslabs.com/txn/2386889884?network=mainnet) | | `collection_offer_filled` | `biddings_v2::AcceptCollectionBidEvent` | [2386021136](https://explorer.aptoslabs.com/txn/2386021136?network=mainnet) | | **Listings** | | | | `listing_created` | `listings_v2::InsertListingEvent` | [2386809975](https://explorer.aptoslabs.com/txn/2386809975?network=mainnet) | | `listing_cancelled` | `listings_v2::DeleteListingEvent` | [2386716658](https://explorer.aptoslabs.com/txn/2386716658?network=mainnet) | | `listing_filled` | `listings_v2::BuyEvent` | [2386455218](https://explorer.aptoslabs.com/txn/2386455218?network=mainnet) | *** ## Related Docs [Section titled “Related Docs”](#related-docs) * 👉 [NFT Aggregator Table Reference](/build/indexer/nft-aggregator/nft-aggregator-table) * 👉 [GraphQL API: Real-time Activity](/build/indexer/nft-aggregator/graphql-api) * 👉 [Integrated Marketplaces Overview](/build/indexer/nft-aggregator/marketplaces) # Wapal Note This page details all supported event types and example transactions for the Wapal marketplace. ## Contract Address [Section titled “Contract Address”](#contract-address) | Contract Version | Account Address | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Mainnet | [`0x584b50b999c78ade62f8359c91b5165ff390338d45f8e55969a04e65d76258c9`](https://explorer.aptoslabs.com/account/0x584b50b999c78ade62f8359c91b5165ff390338d45f8e55969a04e65d76258c9/modules/code/events?network=mainnet) | *This address is the on-chain account for Wapal’s contract deployment.* *** ## Supported Event Types [Section titled “Supported Event Types”](#supported-event-types) | Standard Event Type | Raw On-Chain Event Type (entry function) | Example Txn Version | | ---------------------------- | ---------------------------------------- | --------------------------------------------------------------------------- | | **Offers** | | | | `token_offer_created` | `TokenOfferPlacedEvent` | [2382313982](https://explorer.aptoslabs.com/txn/2382313982?network=mainnet) | | `token_offer_cancelled` | `TokenOfferCanceledEvent` | [2381810159](https://explorer.aptoslabs.com/txn/2381810159?network=mainnet) | | `token_offer_filled` | `TokenOfferFilledEvent` | [2313248448](https://explorer.aptoslabs.com/txn/2313248448?network=mainnet) | | **Collection Offers** | | | | `collection_offer_created` | `CollectionOfferPlacedEvent` | [2382373209](https://explorer.aptoslabs.com/txn/2382373209?network=mainnet) | | `collection_offer_cancelled` | `CollectionOfferCanceledEvent` | [2382373978](https://explorer.aptoslabs.com/txn/2382373978?network=mainnet) | | `collection_offer_filled` | `CollectionOfferFilledEvent` | [2382219668](https://explorer.aptoslabs.com/txn/2382219668?network=mainnet) | | **Listings** | | | | `listing_created` | `ListingPlacedEvent` | [2382251863](https://explorer.aptoslabs.com/txn/2382251863?network=mainnet) | | `listing_cancelled` | `ListingCanceledEvent` | [2381742315](https://explorer.aptoslabs.com/txn/2381742315?network=mainnet) | | `listing_filled` | `ListingFilledEvent` | [2382221134](https://explorer.aptoslabs.com/txn/2382221134?network=mainnet) | *** ## Related Docs [Section titled “Related Docs”](#related-docs) * 👉 [NFT Aggregator Table Reference](/build/indexer/nft-aggregator/nft-aggregator-table) * 👉 [GraphQL API: Real-time Activity](/build/indexer/nft-aggregator/graphql-api) * 👉 [Integrated Marketplaces Overview](/build/indexer/nft-aggregator/marketplaces) # NFT Aggregator Table Reference This page documents the PostgreSQL tables generated and updated by the NFT Aggregator.\ These tables power both the **GraphQL API** and **REST API**, and reflect the live state of marketplace activity on Aptos. For querying, refer to: * **[NFT Aggregator API GraphQL](/build/indexer/nft-aggregator/graphql-api)** * **[NFT Aggregator REST API](/build/indexer/nft-aggregator/analytics-api)** Note When exploring the GraphQL API, you can view these tables in the schema explorer. Tables with `_by_pk` suffixes are automatically generated for primary key lookups. # NFT Aggregator Table Overview [Section titled “NFT Aggregator Table Overview”](#nft-aggregator-table-overview) | Table Name | Description | | ------------------------------------------ | --------------------------------------------- | | `nft_marketplace_activities` | Historical data of all NFT marketplace events | | `current_nft_marketplace_listing` | Latest active listings per token | | `current_nft_marketplace_token_offer` | Latest active offers per token and buyer | | `current_nft_marketplace_collection_offer` | Latest active offers per collection | | `current_collections_v2` | Latest active collections | | `current_token_datas_v2` | Latest active tokens | | `current_token_ownerships_v2` | Latest active token ownerships | | `current_collection_ownerships_v2_view` | Latest active collection ownerships | ## Notes [Section titled “Notes”](#notes) * Use `is_deleted = false` to query **only active** records in current state tables. * The `nft_marketplace_activities` table is your **source of truth** for historical marketplace activity. ## `nft_marketplace_activities` [Section titled “nft\_marketplace\_activities”](#nft_marketplace_activities) Historical table capturing all NFT marketplace events — listings, offers, sales, and more. Has an aggregate view for summary data called `nft_marketplace_activities_aggregate`. **Primary Key:** `txn_version, index, marketplace` ### Indexes [Section titled “Indexes”](#indexes) | Index Name | Columns | | ------------------------- | ------------------------------------------------------------ | | `idx_collection_event_ts` | collection\_id, standard\_event\_type, block\_timestamp DESC | | `idx_token_id` | token\_data\_id | | `idx_buyer` | buyer | | `idx_seller` | seller | | `idx_listing_id` | listing\_id | | `idx_offer_id` | offer\_id | | `idx_timestamp` | block\_timestamp DESC | ### Fields [Section titled “Fields”](#fields) Note Many fields use `Option` types because marketplace events may not emit complete data for all fields. The processor captures what’s available while maintaining type safety. | Field | Type | Description | | --------------------- | ------------------ | --------------------------------------- | | txn\_version | i64 | Blockchain version of the transaction | | index | i64 | Event index in the transaction | | listing\_id | Option\ | Listing ID (if applicable) | | offer\_id | Option\ | Offer ID (if applicable) | | raw\_event\_type | String | Raw marketplace event type | | standard\_event\_type | String | Normalized event type | | creator\_address | Option\ | Collection creator address | | collection\_id | Option\ | Collection identifier | | collection\_name | Option\ | Collection name | | token\_data\_id | Option\ | Token identifier | | token\_name | Option\ | Token name | | price | i64 | Price in Octas | | token\_amount | Option\ | Token amount (for bundles etc.) | | buyer | Option\ | Buyer’s address | | seller | Option\ | Seller’s address | | expiration\_time | Option\ | Listing/offer expiration time | | marketplace | String | Marketplace name | | contract\_address | String | Contract address of the marketplace | | json\_data | serde\_json::Value | Internal raw event payload (not public) | | block\_timestamp | NaiveDateTime | Block timestamp of the event | Caution `json_data` is internal and not exposed in public APIs. ## `current_nft_marketplace_listing` [Section titled “current\_nft\_marketplace\_listing”](#current_nft_marketplace_listing) Tracks current active listings. Updated in real-time. **Primary Key:** `token_data_id, marketplace` ### Indexes [Section titled “Indexes”](#indexes-1) | Index Name | Columns | | ---------------------------------------------------------- | --------------------- | | `idx_current_nft_marketplace_listings_token_data_id` | token\_data\_id | | `idx_current_nft_marketplace_listings_collection_id` | collection\_id | | `idx_current_nft_marketplace_listings_collection_id_price` | collection\_id, price | | `idx_current_nft_marketplace_listings_seller` | seller | ### Fields [Section titled “Fields”](#fields-1) | Field | Type | Description | | ---------------------------- | --------------- | ------------------------------- | | token\_data\_id | String | Token identifier | | listing\_id | Option\ | Listing ID | | collection\_id | Option\ | Collection identifier | | seller | String | Seller address | | price | i64 | Listing price | | token\_amount | i64 | Number of tokens listed | | token\_name | Option\ | Token name | | standard\_event\_type | String | Normalized event type | | is\_deleted | bool | True if the listing is inactive | | marketplace | String | Marketplace name | | contract\_address | String | Marketplace contract address | | last\_transaction\_version | i64 | Last transaction version | | last\_transaction\_timestamp | NaiveDateTime | Last update timestamp | ## `current_nft_marketplace_token_offer` [Section titled “current\_nft\_marketplace\_token\_offer”](#current_nft_marketplace_token_offer) Tracks current active token offers by token and buyer. **Primary Key:** `token_data_id, buyer, marketplace` ### Indexes [Section titled “Indexes”](#indexes-2) | Index Name | Columns | | -------------------------------------------------------- | --------------- | | `idx_current_nft_marketplace_token_offers_token_data_id` | token\_data\_id | | `idx_current_nft_marketplace_token_offers_price` | price | | `idx_current_nft_marketplace_token_offers_buyer` | buyer | ### Fields [Section titled “Fields”](#fields-2) | Field | Type | Description | | ---------------------------- | --------------- | ---------------------------- | | token\_data\_id | String | Token identifier | | offer\_id | Option\ | Offer ID | | buyer | String | Buyer’s address | | collection\_id | String | Collection identifier | | price | i64 | Offer price | | token\_amount | Option\ | Token quantity | | token\_name | Option\ | Token name | | standard\_event\_type | String | Normalized event type | | bid\_key | Option\ | Unique bid key | | is\_deleted | bool | Offer active status | | marketplace | String | Marketplace name | | contract\_address | String | Marketplace contract address | | last\_transaction\_version | i64 | Last transaction version | | last\_transaction\_timestamp | NaiveDateTime | Last update timestamp | ## `current_nft_marketplace_collection_offer` [Section titled “current\_nft\_marketplace\_collection\_offer”](#current_nft_marketplace_collection_offer) Tracks current active collection-wide offers. **Primary Key:** `collection_offer_id` ### Indexes [Section titled “Indexes”](#indexes-3) | Index Name | Columns | | --------------------------------------------------------------------------------- | -------------------------------------- | | `idx_current_nft_marketplace_collection_offers_collection_id` | collection\_id | | `idx_current_nft_marketplace_collection_offers_token_data_id` | token\_data\_id | | `idx_current_nft_marketplace_collection_offers_collection_offer_id_token_data_id` | collection\_offer\_id, token\_data\_id | ### Fields [Section titled “Fields”](#fields-3) | Field | Type | Description | | ---------------------------- | ------------- | ------------------------------- | | collection\_offer\_id | String | Unique collection offer ID | | token\_data\_id | String | Token identifier | | collection\_id | String | Collection identifier | | buyer | String | Buyer’s address | | price | i64 | Offer price | | remaining\_token\_amount | Option\ | Remaining quantity in the offer | | standard\_event\_type | String | Normalized event type | | is\_deleted | bool | Offer active status | | marketplace | String | Marketplace name | | contract\_address | String | Marketplace contract address | | last\_transaction\_version | i64 | Last transaction version | | last\_transaction\_timestamp | NaiveDateTime | Last update timestamp | ## Other Tables [Section titled “Other Tables”](#other-tables) More info on tables (e.g. `current_token_datas_v2`, `current_collections_v2`, `current_token_ownerships_v2`, `current_collection_ownerships_v2_view`) are available [here](/build/indexer/indexer-api/indexer-reference) # Transaction Stream Service The Transaction Stream Service is a service that listens to the Aptos blockchain and emits transactions as they are processed. These docs explain how this system works, how to use the Labs-Hosted instance of the service, and how to deploy it yourself. You can get API access to a transaction stream hosted by Aptos Labs [here](/build/indexer/txn-stream/aptos-hosted-txn-stream). # Hosted Transaction Stream Service If you are running your own instance of the [Indexer API](/build/indexer), or an [Indexer SDK](/build/indexer/indexer-sdk) custom processor, you must have access to an instance of the Transaction Stream Service. This page contains information about how to use the Aptos Labs Hosted Transaction Stream Service. ## Endpoints [Section titled “Endpoints”](#endpoints) All endpoints are in GCP us-central1 unless otherwise specified. * **Mainnet:** grpc.mainnet.aptoslabs.com:443 * **Testnet:** grpc.testnet.aptoslabs.com:443 * **Devnet:** grpc.devnet.aptoslabs.com:443 You can learn about the rate limits for this service by reading the [Aptos Build docs](https://build.aptoslabs.com/docs/start/billing). ## Authorization via API Key [Section titled “Authorization via API Key”](#authorization-via-api-key) In order to use the Labs-Hosted Transaction Stream Service you must have an API key. To get an API key, do the following: 1. Go to . 2. Sign in and select “API Resource”. 3. Create a new key. You will see the API key secret in the first table. You can provide the API key by setting the `Authorization` HTTP header ([MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)). For example, with curl: ```shellscript curl -H 'Authorization: Bearer aptoslabs_yj4donpaKy_Q6RBP4cdBmjA8T51hto1GcVX5ZS9S65dx' ``` Learn more about API keys at the [Aptos Build docs site](https://build.aptoslabs.com/docs/start/api-keys). For more comprehensive information about how to use the Transaction Stream Service, see the docs for the downstream systems: * [Indexer API](/build/indexer/indexer-api) * [Indexer SDK](/build/indexer/indexer-sdk) # Running Locally Note This has been tested on macOS 13 on ARM and Debian 11 on x86\_64. When building a custom processor, you might find it helpful to develop against a local development stack. The Transaction Stream Service is a complicated, multi-component system. To assist with local development, we offer a Python script that wraps a Docker compose file to set up the entire system. This script sets up the following: * Single node testnet with the indexer GRPC stream enabled. * A Redis instance. * Transaction Stream Service, including the following components: * [cache-worker](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/indexer-grpc/indexer-grpc-cache-worker): Pulls transactions from the node and stores them in Redis. * [file-store](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/indexer-grpc/indexer-grpc-file-store): Fetches transactions from Redis and stores them in a filesystem. * [data-service](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/indexer-grpc/indexer-grpc-data-service): Serves transactions via a GRPC stream to downstream clients. It pulls from either the cache or the file store depending on the age of the transaction. * Shared volumes and networking to hook it all up. You can learn more about the Transaction Stream Service architecture [here](/build/indexer/txn-stream) and the Docker compose file [here](https://github.com/aptos-labs/aptos-core/blob/main/docker/compose/indexer-grpc/docker-compose.yaml). ## Prerequisites [Section titled “Prerequisites”](#prerequisites) In order to use the local development script you must have the following installed: * Python 3.8+: [Installation Guide](https://docs.python-guide.org/starting/installation/#python-3-installation-guides). * Poetry: [Installation Guide](https://python-poetry.org/docs/#installation). * Docker: [Installation Guide](https://docs.docker.com/get-docker/). * Docker Compose v2: This should be installed by default with modern Docker installations, verify with this command: ```shellscript docker-compose version --short ``` * grpcurl: [Installation Guide](https://github.com/fullstorydev/grpcurl#installation) * OpenSSL ## Preparation [Section titled “Preparation”](#preparation) Clone the aptos-core repo: ```shellscript # HTTPS git clone https://github.com/aptos-labs/aptos-core.git # SSH git clone git@github.com:aptos-labs/aptos-core.git ``` Navigate to the `testsuite` directory: ```shellscript cd aptos-core cd testsuite ``` Install the Python dependencies: ```shellscript poetry install ``` ## Running the script [Section titled “Running the script”](#running-the-script) ### Starting the service [Section titled “Starting the service”](#starting-the-service) ```shellscript poetry run python indexer_grpc_local.py start ``` You will know this succeeded if the command exits, and you see the following: ```shellscript Attempting to stream from indexer grpc for 10s Stream finished successfully ``` ### Stopping the service [Section titled “Stopping the service”](#stopping-the-service) ```shellscript poetry run python indexer_grpc_local.py stop ``` ### Wiping the data [Section titled “Wiping the data”](#wiping-the-data) When you start, stop, and start the service again, it will re-use the same localnet data. If you wish to wipe the locnet and start from scratch you can run the following command: ```shellscript poetry run python indexer_grpc_local.py wipe ``` ## Using the local service [Section titled “Using the local service”](#using-the-local-service) You can connect to the local Transaction Stream Service, e.g. from a custom processor, using the following configuration values: ```shellscript indexer_grpc_data_service_address: 127.0.0.1:50052 auth_token: dummy_token ``` You can connect to the node at the following address: ```shellscript http://127.0.0.1:8080/v1 ``` ## Debugging [Section titled “Debugging”](#debugging) ### Usage on ARM systems [Section titled “Usage on ARM systems”](#usage-on-arm-systems) If you have a machine with an ARM processor, e.g. an M1/M2 Mac, the script should detect that and set the appropriate environment variables to ensure that the correct images will be used. If you have issues with this, try setting the following environment variable: ```shellscript export DOCKER_DEFAULT_PLATFORM=linux/amd64 ``` Additionally, make sure the following settings are correct in Docker Desktop: * Enabled: Preferences > General > Use Virtualization framework * Enabled: Preferences > General > Use Docker Compose V2 * Disabled: Features in development -> Use Rosetta for x86/amd64 emulation on Apple Silicon This script has not been tested on Linux ARM systems. ### Redis fails to start [Section titled “Redis fails to start”](#redis-fails-to-start) Try setting the following environment variable before running the script: ```shellscript export REDIS_IMAGE_REPO=arm64v8/redis ``` ### Cache worker is crash-looping or `Redis latest version update failed.` in log [Section titled “Cache worker is crash-looping or Redis latest version update failed. in log”](#cache-worker-is-crash-looping-or-redis-latest-version-update-failed-in-log) Wipe the data: ```shellscript poetry run python indexer_grpc_local.py wipe ``` This means historical data will be lost. # Self-Hosted Transaction Stream Service In order to run Self-Hosted Transaction Stream Service, you will need to run the following components. Indexer FN \[]: A FN with indexer grpc functionality enabled. Typically your data service will need to access all historical data, therefore your FN need to sync from genesis in order to bootstrap the whole stack. The pruner can be deleted (through pruner) later on once the data is persisted into file store. GrpcManager \[]: A centrilized component that manages all the components in the stack. It can run in two mode (master and non-master), only 1 master is allowed. When it is running as master mode, it will also pull data from the upstream FN, and persistent the data into file store (which can be a local file system or Gooogle Cloud Storage). DataService \[]: Provides the client facing streaming Grpc service. It can run in 2 modes. The live mode serves data from its local cache, the historical mode serves data from the file store. ## Example Configs [Section titled “Example Configs”](#example-configs) * FN ```plaintext indexer_grpc: enabled: true address: 0.0.0.0:50051 # The address to service Grpc request. processor_task_count: 32 processor_batch_size: 100 output_batch_size: 100 indexer_table_info: table_info_service_mode: IndexingOnly parser_task_count: 10 parser_batch_size: 100 ``` * GrpcManager ```plaintext health_check_port: 8081 # The port for monitoring purpose. server_config: is_master: true # Whether running in master mode. service_config: listen_address: 0.0.0.0:50052 # The port that serves Grpc requests. self_advertised_address: 0.0.0.0:50052 grpc_manager_addresses: # All GrpcManager addresses in the stack, need to point to the server_config.self_advertised_address in GrpcManager config. - >- http://0.0.0.0:50052 fullnode_addresses: # All upstream FN addresses in the stack, need to point to the indexer_grpc.address in FN config. - >- http://0.0.0.0:50051 - >- http://other-fullnode.xyz:50051 file_store_config: file_store_type: GcsFileStore gcs_file_store_bucket_name: indexer gcs_file_store_service_account_key_path: /secrets/indexer-sa-key chain_id: 1 ``` * DataService ```plaintext health_check_port: 8081 # The port for monitoring purpose. server_config: chain_id: 1 self_advertised_address: 0.0.0.0:50053 grpc_manager_addresses: # All GrpcManager addresses in the stack, need to point to the server_config.self_advertised_address in GrpcManager config. - >- http://0.0.0.0:50052 service_config: listen_address: 0.0.0.0:50053 live_data_service_config: # For live data service. enabled: true num_slots: 5000000 # Max number of transactions to cache. size_limit_bytes: 10000000000 # Cache size in bytes. historical_data_service_config: # For historical data service. enabled: true file_store_config: file_store_type: GcsFileStore gcs_file_store_bucket_name: indexer gcs_file_store_service_account_key_path: /secrets/indexer-sa-key ``` ## Usage [Section titled “Usage”](#usage) * Use GrpcManager for routing / load balancing Call GrpcManager.GetDataServiceForRequest first, it will return the address of a data service instance. Then Call DataService.GetTransactions. * Use DataService directly Call DataService.GetTransactions directly. In this case you might want to run both live data service and historical data service together. ## Advanced Usage [Section titled “Advanced Usage”](#advanced-usage) * Do not keep full history If your stream never needs to serve old data and you don’t want to keep the full history, for example you want to start a stream now and only care about data in the future, you can choose to not sync from genesis. In order to do that, first you can start your FN and do a fast sync. Then download the most recent table info database from (for testnet, replace `mainnet` with `testnet`), unzip to the db folder in your FN. Then start your GrpcManager, it will generate the `metadata.json` in your file store (it could be your local file stream or GCS based on your config). Manually update the version to the next version you want to start processing. (the version must be a multiple of 100000 plus 1, e.g. 1000000001, and your FN must have data at this version). Then restart all your binaries, it should start working. # Official SDKs Use these Aptos software development kits (SDKs), in combination with the [Aptos CLI](/build/cli) for your development on the Aptos blockchain. [Typescript SDK ](/build/sdks/ts-sdk)Aptos Typescript SDK (recommended) [Python SDK ](/build/sdks/python-sdk)Aptos Python SDK [Go SDK ](/build/sdks/go-sdk)Aptos Go SDK [C#/.NET SDK ](/build/sdks/dotnet-sdk)Aptos .NET SDK [Rust SDK ](/build/sdks/rust-sdk)Aptos Rust SDK [C++ / Unreal SDK ](/build/sdks/cpp-sdk)Aptos C++ / Unreal SDK [Unity SDK ](/build/sdks/unity-sdk)Aptos Unity SDK [Wallet Adapter ](/build/sdks/wallet-adapter)Aptos Wallet Adapter ## [Community SDKs](/build/sdks/community-sdks) [Section titled “Community 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. [Kotlin SDK ](/build/sdks/community-sdks/kotlin-sdk)Aptos Kotlin Multiplatform SDK by Kaptos [Swift SDK ](/build/sdks/community-sdks/swift-sdk)Aptos Swift SDK by Alcove # Community SDKs Here is a list of community built SDKs 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. ## SDKs [Section titled “SDKs”](#sdks) [Kotlin SDK ](/build/sdks/community-sdks/kotlin-sdk)Aptos Kotlin Multiplatform SDK by Kaptos [Swift SDK ](/build/sdks/community-sdks/swift-sdk)Aptos Swift SDK by Alcove # Kotlin SDK Kaptos is a Kotlin **Multiplatform** SDK for interacting with the Aptos blockchain across various platforms. It offers a **consistent** API for data requests, transaction submissions, and more, facilitating cross-platform app development with shared business logic. The SDK includes **asynchronous** Aptos clients for smooth blockchain interactions. Kaptos also provides **platform-specific** SDKs for JVM, Android, iOS, JS, Linux, macOS, and Windows. ![Android Badge](http://img.shields.io/badge/Platform-Android-brightgreen.svg?logo=android)![iOS Badge](http://img.shields.io/badge/Platform-iOS-orange.svg?logo=apple)![tvOS Badge](http://img.shields.io/badge/Platform-tvOS-lightgrey.svg?logo=apple)![watchOS Badge](http://img.shields.io/badge/Platform-watchOS-lightgrey.svg?logo=apple)![NodeJS Badge](http://img.shields.io/badge/Platform-NodeJS-yellow.svg?logo=javascript)![JVM Badge](http://img.shields.io/badge/Platform-JVM-red.svg?logo=openjdk)![Linux Badge](http://img.shields.io/badge/Platform-Linux-lightgrey.svg?logo=linux)![macOS Badge](http://img.shields.io/badge/Platform-macOS-orange.svg?logo=apple)![Windows Badge](http://img.shields.io/badge/Platform-Windows-blue.svg?logo=windows) ![Maven Central](https://img.shields.io/maven-central/v/xyz.mcxross.kaptos/kaptos.svg?label=Maven%20Central) [![Static Badge](https://img.shields.io/badge/SDK_Reference-Docs)](https://mcxross.github.io/kaptos/) ## Features [Section titled “Features”](#features) * **Type-safe**: The SDK is fully type-safe and provides a rich set of types for all operations. * **Expressive**: Kaptos provides a simple and expressive DSL-style API for building transactions. * **Multiplatform**: Write cross-platform applications with shared business logic. * **Consistent API**: All operations bare a uniform and consistent API across all platforms. * **BCS Support**: The SDK defaults to BCS for serialization and deserialization of transactions. * **Asynchronous**: All blockchain operations are asynchronous. * **Configurable**: The SDK provides highly configurable clients for all platforms. Note Kaptos is currently under development, please give feedback [here](https://github.com/mcxross/kaptos/issues) ## Installation [Section titled “Installation”](#installation) * Multiplatform ```kotlin commonMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos:") } ``` * JVM ```kotlin jvmMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-jvm:") } ``` * Android ```kotlin androidMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-android:") } ``` Note The Android SDK provides flavors for both release and debug builds. To use the debug flavor, add the following to your `build.gradle`: ```kotlin androidMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-android-debug:") } ``` * iOS The SDK is compatible with iosArm64, iosX64, and iosSimulatorArm64. Depending on how your project is configured, you can add the following dependencies: For iosArm64: ```kotlin iosMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-iosArm64:") } ``` For iosX64: ```kotlin iosMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-iosX64:") } ``` * JS ```kotlin jsMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-js:") } ``` * Linux The SDK only supports Linux x64. To add the dependency, use the following: ```kotlin linuxMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-linux:") } ``` * macOS The SDK only supports macOS x64, macOS arm64, and macOS arm64 simulator. To add the dependency, use the following: For macOS x64: ```kotlin macosMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-macosX64:") } ``` For macOS arm64: ```kotlin macosMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-macosArm64:") } ``` * Windows The SDK only supports Windows x64. To add the dependency, use the following: ```kotlin mingwMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos-mingwX64:") } ``` ## Perform a Transaction [Section titled “Perform a Transaction”](#perform-a-transaction) Below is an example of how you can perform a transaction using the Kotlin SDK. The snippet demonstrates how to build a transaction to transfer APT. We then sign and submit the transaction to the blockchain in a single step. APTTransfer.kt ```kt 58 collapsed lines /* * Copyright 2024 McXross * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xyz.mcxross.kaptos.sample import xyz.mcxross.kaptos.Aptos import xyz.mcxross.kaptos.account.Account import xyz.mcxross.kaptos.model.* import xyz.mcxross.kaptos.util.runBlocking const val FUNDING_AMOUNT = 100_000_000L const val SEND_AMOUNT_APT = 0.5f const val UNIT_CONVERSION = 100_000_000 const val SEND_AMOUNT_UNITS = (SEND_AMOUNT_APT * UNIT_CONVERSION) const val SEND_AMOUNT = 1_000_000UL /** * This example demonstrates how to transfer APT from one account to another. * * Each run generates and creates new accounts on-chain using faucet funding. After funding, the APT * balance of each account is printed; if funding fails, an error is thrown. * * Next, a transaction is constructed to send 0.5 APT from Alice to Bob. The transaction is then * signed and submitted using the one-step `signAndSubmitTransaction` method. We wait for the * transaction to complete and print the updated balances of Alice and Bob. If the transaction * fails, an error is thrown. */ fun main() = runBlocking { val aptos = Aptos(AptosConfig(AptosSettings(network = Network.TESTNET))) println("Generating Alice and Bob's accounts") val alice = Account.generate() val bob = Account.generate() aptos.fundAccount(alice.accountAddress, FUNDING_AMOUNT).expect("Failed to fund Alice's account") aptos.fundAccount(bob.accountAddress, FUNDING_AMOUNT).expect("Failed to fund Bob's account") println("Created accounts on chain") println("Alice's balance: ${aptos.getAccountAPTAmount(alice.accountAddress)}") println("Bob's balance: ${aptos.getAccountAPTAmount(bob.accountAddress)}") println("=============================================") println( "Building transaction to send ${SEND_AMOUNT / 100_000_000u} APT to Bob: ${bob.accountAddress}" ) val txn = aptos.buildTransaction.simple( sender = alice.accountAddress, data = entryFunctionData { function = "0x1::coin::transfer" typeArguments = typeArguments { +TypeTagStruct("0x1::aptos_coin::AptosCoin") } functionArguments = functionArguments { +bob.accountAddress +U64(SEND_AMOUNT_UNITS.toULong()) } }, ) 21 collapsed lines // Sign and submit the transaction val commitedTransaction = aptos.signAndSubmitTransaction(alice, txn) val executedTransaction = aptos.waitForTransaction( HexInput.fromString(commitedTransaction.expect("Transaction failed").hash) ) println( "Transaction wait response: $executedTransaction\n=============================================" ) val aliceNewBalance = aptos.getAccountAPTAmount(alice.accountAddress).expect("Alice's account does not exist") val bobNewBalance = aptos.getAccountAPTAmount(bob.accountAddress).expect("Bob's account does not exist") println("Alice's new balance: $aliceNewBalance") println("Bob's new balance: $bobNewBalance") } ``` The SDK also provides pre-built methods for common transaction operations. For example, you can use the `transferCoinTransaction` method to generate a transfer transaction between two accounts as shown below: ```kotlin val txn = aptos.transferCoinTransaction( alice.accountAddress, bob.accountAddress, SEND_AMOUNT ) ``` You can then [sign](/build/sdks/community-sdks/kotlin-sdk/building-transactions#sign-the-transaction) and [submit](/build/sdks/community-sdks/kotlin-sdk/building-transactions#submit-the-transaction) this transaction. ## Examples [Section titled “Examples”](#examples) For more examples on how and what you can do with the Kotlin SDK, check out the following: [Quickstart ](/build/sdks/community-sdks/kotlin-sdk/quickstart)Integrate Aptos in < 5 minutes [Single and Multiplatform Projects ](https://github.com/mcxross/kaptos/tree/master/sample)Explore various demo apps, single and multiplatform projects, on how using the SDK # Creating and Managing Accounts **Kaptos** provides a few ways to generate account credentials, both *legacy* and `SingleKeyAccount`s. You can either generate a new account or derive an account from a private key. ## Legacy Account [Section titled “Legacy Account”](#legacy-account) **Kaptos** offers a straightforward method to generate a legacy account using the `Account` class. By invoking the static method `generate()`, you can create a new legacy account either by passing no arguments or by explicitly setting the `scheme` to `SigningSchemeInput.Ed25519` and the `legacy` property to `true` as shown below. ### Generate a New Account [Section titled “Generate a New Account”](#generate-a-new-account) To create a new legacy account, you can generate a new account credential using the `Account.generate()` method. This method will create a new account with a new key pair. ```kotlin val account = Account.generate() ``` You can also use the `Ed25519Account` class that provides a **nullary** method, `generate()`, to create a new account: ```kotlin val account = Ed25519Account.generate() ``` ### Derive an Account from a Private Key [Section titled “Derive an Account from a Private Key”](#derive-an-account-from-a-private-key) If you have a private key, you can use it to create an `Account` object to manage those credentials. ```kotlin val privateKey = Ed25519PrivateKey("myEd25519privatekeystring") val account = Account.fromPrivateKey(privateKey) ``` ## Single Key Account [Section titled “Single Key Account”](#single-key-account) The SDK offers two ways to generate a single key account: using either the `SingleKeyAccount` or the `Account` class. In both cases, you can create a new account by calling the static `generate()` method. You’ll need to specify the `scheme`, and for the `Account` class, you can optionally set the `legacy` property to `false`. ```kotlin val secp256k1SKAccount = SingleKeyAccount.generate(SigningSchemeInput.Secp256k1) val ed25519SKAccount = SingleKeyAccount.generate(SigningSchemeInput.Ed25519) ``` Using the `Account` class, you can create a new single key account by setting the `scheme` to `SigningSchemeInput.Secp256k1` and optionally setting the `legacy` property to `false`. Alternatively, you can set the `scheme` to `SigningSchemeInput.Ed25519` and ensure the `legacy` property is also set to `false`. ```kotlin val secp256k1SKAccount = Account.generate(scheme = SigningSchemeInput.Secp256k1) val ed25519SKAccount = Account.generate(scheme = SigningSchemeInput.Ed25519, legacy = false) ``` ## On-chain Account Creation [Section titled “On-chain Account Creation”](#on-chain-account-creation) It is also worth noting that Account generation does not create the account on-chain. You must fund the account on-chain to use it for transactions. On test networks, you can fund an account programmatically by asking a “faucet” for test tokens. You can do this as shown below: ```kotlin val fundedAccount = aptos.fundAccount(aliceAccount.accountAddress, FUNDING_AMOUNT) ``` This only works on devnet. On testnet you can mint at the [mint page](/network/faucet). # Building and Sending Transactions Kaptos boasts an expressive and type-safe DSL-style API for building and sending transactions on-chain. This guide will walk you through the process of building and sending transactions using Kaptos. The typical flow for sending a transaction is as follows: 1. Create an account (if you don’t already have one). 2. Build the transaction. 3. Sign the transaction. 4. Submit the transaction. 1) Create an Account To create a new account, you first generate new credentials then fund the account. On devnet, you can fund an account programmatically by asking a “faucet”. ```kotlin val aliceAccount = Account.generate() val bobAccount = Account.generate() ``` OR If you have a private key, you can use it to create an `Account` object to manage those credentials. ```kotlin val privateKey = Ed25519PrivateKey("myEd25519privatekeystring") val account = Account.fromPrivateKey(privateKey) ``` On testnet you can mint at the [mint page](/network/faucet). 2) Build the Transaction Kaptos provides a `buildTransaction.simple` method to build a transaction. You can specify the sender, entry function data like the function name, type arguments, and function arguments. You can also configure the transaction with the gas price and maximum gas amount. However, reasonable defaults are provided for these values in case you don’t specify them. ```kotlin val txn = aptos.buildTransaction.simple( sender = aliceAccount.accountAddress, data = entryFunctionData { function = "0x1::coin::transfer" typeArguments = typeArguments { +TypeTagStruct("0x1::aptos_coin::AptosCoin") } functionArguments = functionArguments { +bobAccount.accountAddress +U64(SEND_AMOUNT) } }, ) ``` 3) Sign the Transaction Once you have built a transaction, you can sign it using the `sign` method. ```kotlin val aliceAuthenticator = aptos.sign( sender = aliceAccount, transaction = txn, ) ``` 4) Submit the Transaction Finally, you can submit the transaction to the network using the `submit` method. ```kotlin val commitedTransaction = aptos.submitTransaction.simple( transaction = signedTransaction, senderAuthenticator = aliceAuthenticator, ) ``` Note You can collapse the signing and submitting steps into one by using the `signAndSubmitTransaction` method. ```kotlin val executedTransaction = aptos.signAndSubmitTransaction( signer = aliceAccount, transaction = commitedTransaction, ) ``` 5) Wait for the Transaction to Execute Then you can wait for the transaction to be executed by using the `waitForTransaction` method. ```kotlin val executedTransaction = aptos.waitForTransaction(HexInput.fromString(commitedTransaction.expect("Transaction failed").hash)) ``` ### Full Kotlin Example [Section titled “Full Kotlin Example”](#full-kotlin-example) The following is a complete example of how to build and send a transaction to transfer APT: ```kotlin const val FUNDING_AMOUNT = 100_000_000L const val SEND_AMOUNT_APT = 0.5f const val UNIT_CONVERSION = 100_000_000 const val SEND_AMOUNT_UNITS = (SEND_AMOUNT_APT * UNIT_CONVERSION) const val SEND_AMOUNT = 1_000_000UL /** * This example demonstrates how to transfer APT from one account to another. * * Each run generates and creates new accounts on-chain using faucet funding. After funding, the APT * balance of each account is printed; if funding fails, an error is thrown. * * Next, a transaction is constructed to send 0.5 APT from Alice to Bob. The transaction is then * signed and submitted using the one-step `signAndSubmitTransaction` method. We wait for the * transaction to complete and print the updated balances of Alice and Bob. If the transaction * fails, an error is thrown. */ fun main() = runBlocking { val aptos = Aptos(AptosConfig(AptosSettings(network = Network.DEVNET))) println("Generating Alice and Bob's accounts") val alice = Account.generate() val bob = Account.generate() aptos.fundAccount(alice.accountAddress, FUNDING_AMOUNT).expect("Failed to fund Alice's account") aptos.fundAccount(bob.accountAddress, FUNDING_AMOUNT).expect("Failed to fund Bob's account") println("Created accounts on chain") println("Alice's balance: ${aptos.getAccountAPTAmount(alice.accountAddress)}") println("Bob's balance: ${aptos.getAccountAPTAmount(bob.accountAddress)}") println("=============================================") println( "Building transaction to send ${SEND_AMOUNT / 100_000_000u} APT to Bob: ${bob.accountAddress}" ) val txn = aptos.buildTransaction.simple( sender = alice.accountAddress, data = entryFunctionData { function = "0x1::coin::transfer" typeArguments = typeArguments { +TypeTagStruct("0x1::aptos_coin::AptosCoin") } functionArguments = functionArguments { +bob.accountAddress +U64(SEND_AMOUNT_UNITS.toULong()) } }, ) // Sign and submit the transaction val commitedTransaction = aptos.signAndSubmitTransaction(alice, txn) val executedTransaction = aptos.waitForTransaction( HexInput.fromString(commitedTransaction.expect("Transaction failed").hash) ) println( "Transaction wait response: $executedTransaction\n=============================================" ) val aliceNewBalance = aptos.getAccountAPTAmount(alice.accountAddress).expect("Alice's account does not exist") val bobNewBalance = aptos.getAccountAPTAmount(bob.accountAddress).expect("Bob's account does not exist") println("Alice's new balance: $aliceNewBalance") println("Bob's new balance: $bobNewBalance") } ``` # Client Configuration Whilst **Kaptos** offers a **consistent interface** for interacting with Aptos across all supported platforms, it also features both shared and platform-specific configuration options for its various clients. These configuration options allow you to customize the behavior of the client to suit your needs. They can be set by creating a `ClientConfig` object and passing it to the `AptosSettings` object when creating an `Aptos` client as shown below: ```kotlin val clientConfig = ClientConfig(followRedirects = false, retryOnServerErrors = 3) val client = Aptos(AptosConfig(AptosSettings(clientConfig = clientConfig))) ``` This page will guide you through the available configuration options for each platform. ### Shared Configuration [Section titled “Shared Configuration”](#shared-configuration) `followRedirects` — A boolean value that determines whether the client should follow redirects. The default value is `true`. `retryOnServerErrors` — The number of times to retry the request if a server error occurs. The default value is `—1`, which means no retries. `requestTimeout` — The timeout in milliseconds for the request. The default value is `10_000`. `maxRetries` — The maximum number of times to retry the request. The default value is `-1`, which means no retries. `agent` — A `String` that specifies the user agent to use for the connection. Defaults to `Kaptos/{PLATFORM}`. `likeAgent` — A `UserAgent` enum value that specifies the user agent to use for the connection. `proxy` — A `String` that specifies the proxy server to use for the connection. `cache` — A boolean value that determines whether to cache the response. The default value is `false`. ### JVM Configuration [Section titled “JVM Configuration”](#jvm-configuration) `pipelining` — A boolean value that determines whether the client should use pipelining. The default value is `false`. `pipelineMaxSize` — The maximum number of requests to pipeline. The default value is `20`. `maxConnectionsPerRoute` — The maximum number of connections per route. The default value is `100`. `maxConnectionsCount` — The maximum number of connections. The default value is `100`. `connectTimeoutMillis` — The timeout in milliseconds for establishing a connection to the server. The default value is `10_000`. `keepAliveTime` — The time in milliseconds to keep a connection alive. The default value is `5_000`. `connectAttempts` — The number of times to attempt to connect to the server. The default value is `5`. `connectTimeout` — The timeout in milliseconds for establishing a connection to the server. The default value is `10_000`. ### Android Configuration [Section titled “Android Configuration”](#android-configuration) `followSslRedirects` — A boolean value that determines whether the client should follow SSL redirects. The default value is `true`. `connectTimeoutMillis` — The timeout in milliseconds for establishing a connection to the server. The default value is `10_000`. `readTimeoutMillis` — The timeout in milliseconds for reading data from the server. The default value is `10_000`. `writeTimeoutMillis` — The timeout in milliseconds for writing data to the server. The default value is `10_000`. `maxRetries` — The maximum number of times to retry the request. The default value is `-1`, which means no retries. `connectTimeout` — The timeout in milliseconds for establishing a connection to the server. The default value is `10_000`. ### Apple (iOS, macOS) Configuration [Section titled “Apple (iOS, macOS) Configuration”](#apple-ios-macos-configuration) Apple platforms currently do not have any platform-specific configuration options. ### Web Configuration [Section titled “Web Configuration”](#web-configuration) Web platforms currently do not have any platform-specific configuration options. ### Linux Configuration [Section titled “Linux Configuration”](#linux-configuration) `connectTimeout` — The timeout in milliseconds for establishing a connection to the server. The default value is `10_000`. ### Windows Configuration [Section titled “Windows Configuration”](#windows-configuration) `connectTimeout` — The timeout in milliseconds for establishing a connection to the server. The default value is `10_000`. # Fetch Data via the Kotlin SDK You can use the Aptos client to fetch all sorts of data from on-chain such as information about the network itself or account-specific information. ```kotlin val modules = aptos.getAccountModules("0x123").expect("Failed to fetch account modules") val option = aptos.getChainTopUserTransactions(10) ``` Kaptos returns an `Option` type for all network requests. This allows you to handle both successful and failed requests in a more idiomatic way. ```kotlin val ledgerInfo = aptos.getLedgerInfo() when (ledgerInfo) { is Some -> println("Ledger Info: ${ledgerInfo.value}") is None -> println("Failed to fetch ledger info") } ``` If you trust the result exists, you can use the `.expect` function to unwrap the value. ```kotlin val ledgerInfo = aptos.getLedgerInfo().expect("Failed to fetch ledger info") ``` ### Using Move View Functions [Section titled “Using Move View Functions”](#using-move-view-functions) You can call view functions which return custom data from on-chain by using the `.view` method on the `Aptos` object. The user specifies the return type of the view function as a type parameter. For example, you can look up the supply of tokens as follows: ```kotlin val inputViewFunctionData = InputViewFunctionData( "0x1::coin::supply", listOf(TypeTagStruct("0x1::aptos_coin::AptosCoin")), emptyList(),) val view = aptos .view>>(inputViewFunctionData) .expect("Failed to fetch view") ``` # Swift SDK **AptosKit** is a Swift SDK for interacting with the Aptos blockchain. It provides a simple and easy-to-use interface for interacting with the Aptos blockchain. The SDK is a Swift package export for [Kaptos](/build/sdks/community-sdks/kotlin-sdk), a Kotlin SDK for the Aptos blockchain. It is build by making its iOS binary available as a dependency to iOS developers working on native Swift projects. Note **AptosKit** is currently under development, please give feedback [here](https://github.com/mcxross/swift-aptos/issues) ## Installation [Section titled “Installation”](#installation) To install **AptosKit**, add the following to your `Package.swift` file: ```swift dependencies: [ .package(url: "https://github.com/mcxross/swift-aptos.git", .upToNextMajor(from: )) ] ``` ## Example [Section titled “Example”](#example) [Quickstart ](/build/sdks/community-sdks/kotlin-sdk/for-ios-devs/getting-started)Integrate Aptos in < 5 minutes [iOS Demo ](https://github.com/mcxross/swift-aptos/tree/main/iOSDemo/iOSDemo)Sample iOS app using AptosKit # For iOS Developers This guide will walk you through the process of setting up **AptosKit**, and fetching data on the Aptos blockchain. 1. Install the SDK **AptosKit** is available as a Swift package. To add it to your project, add the following to your `Package.swift` file: ```swift dependencies: [ .package(url: "https://github.com/mcxross/swift-aptos.git", .upToNextMajor(from: )) ] ``` 2. Import the SDK Import the SDK in your Swift file: ```swift import AptosKit ``` 3. Create the ClientConfig object This object is used to configure the client behavior. You can set `maxRetries`, `requestTimeout`, and `retryOnServerErrors` properties. ```swift let config = ClientConfig( followRedirects: true, agent: "AptosClient", likeAgent: nil, requestTimeout: 5000, retryOnServerErrors: 3, maxRetries: 5, cache: false, proxy: nil ) ``` 4. Create the AptosSettings object This object is used to configure the Aptos network connection. You can set `network`, `fullnode`, and `faucet` properties. ```swift let aptosSettings = AptosSettings( network: .devnet, fullNode: nil, faucet: nil, indexer: nil, client: nil, clientConfig: config, fullNodeConfig: nil, indexerConfig: nil, faucetConfig: nil ) ``` 5. Create the AptosConfig object ```swift let aptosConfig = AptosConfig(settings: aptosSettings) ``` 6. Create the Aptos object This object is used to interact with the Aptos blockchain. It serves as the entry point for all interactions with the blockchain. ```swift let aptos = Aptos(config: aptosConfig, graceFull: false) ``` 7. Fetch the chain ID ```swift let chainId = try await aptos.getChainId() ``` Congratulations! You have successfully set up the **AptosKit** SDK and fetched the chain ID from the Aptos blockchain. ## Complete Example [Section titled “Complete Example”](#complete-example) ```swift import SwiftUI import AptosKit struct ContentView: View { @State private var chainId: String? = nil var body: some View { VStack { if let chainId = chainId { Text("Chain ID: \(chainId)") } else { Text("Fetching Chain ID...") } } .padding() .onAppear { fetchChainId() } } private func fetchChainId() { DispatchQueue.main.async { Task { do { let clientConfig = ClientConfig( followRedirects: true, agent: "AptosClient", likeAgent: nil, requestTimeout: 5000, retryOnServerErrors: 3, maxRetries: 5, cache: false, proxy: nil ) let aptosSettings = AptosSettings( network: .devnet, fullNode: nil, faucet: nil, indexer: nil, client: nil, clientConfig: clientConfig, fullNodeConfig: nil, indexerConfig: nil, faucetConfig: nil ) let aptosConfig = AptosConfig(settings: aptosSettings) let aptos = Aptos(config: aptosConfig, graceFull: false) let chainId = try await aptos.getChainId() self.chainId = chainId.expect(message: "Failed...")?.stringValue ?? "null" } catch { print("Failed to get chain ID: \(error)") self.chainId = "Error" } } } } } ``` # Kotlin SDK Quickstart This guide will walk you through the process of setting up Kaptos, fetching data, and sending a transaction on the Aptos blockchain. 1. Install the SDK Kaptos is available for both multiplatform and single-platform development. Artifacts are published at Sonatype Maven Central and can be added to your project using Gradle as shown below: #### Multiplatform Development [Section titled “Multiplatform Development”](#multiplatform-development) In your `build.gradle.kts` file, and in your `commonMain` source set block, add as follows: ```kotlin kotlin { sourceSets { commonMain.dependencies { implementation("xyz.mcxross.kaptos:kaptos:") } } } ``` #### Single-platform Development [Section titled “Single-platform Development”](#single-platform-development) Depending on your target platform, Kaptos provides different artifacts in the form of `kaptos-jvm`, `kaptos-android`, `kaptos-iosArm64`, and `kaptos-js`. For example, to add the JVM artifact to your project, add the following dependency: ```kotlin dependencies { implementation("xyz.mcxross.kaptos:kaptos-jvm:") } ``` To add the Android artifact, use: ```kotlin dependencies { implementation("xyz.mcxross.kaptos:kaptos-android:") } ``` 2. Set up the Aptos client You can use the `Aptos` object to handle everything that requires a connection to the Aptos network. ```kotlin val aptos = Aptos() ``` If you want to pass in a custom configuration, you can do so by passing in a AptosConfig object that takes in an AptosSettings object. The AptosSettings object allows you to specify the network you want to connect to, the fullnode URL, and other settings. ```kotlin val settings = AptosSettings(network = Network.MAINNET, clientConfig = ClientConfig(maxRetries = 10)) val aptosConfig = AptosConfig(settings = settings) val aptos = Aptos(aptosConfig) ``` Note Kaptos offers common configurations for all platforms while also providing platform-specific settings. For instance, you can configure both connection and request timeouts on Linux, whereas on iOS, you can only set request timeouts. 3. Fetch data from on-chain Once you have an `Aptos` object, you can use it to fetch data from the Aptos blockchain. For example, you can fetch the ledger information like so: ```kotlin val ledgerInfo = aptos.getLedgerInfo() ``` 4. Send Transactions To interact with the ledger and change its state, you must send transactions. To do this, you need an existing account. You can create an account by generating a new account key pair and funding the account on-chain. Once you have an account, you can sign transactions to demonstrate authority, allowing you to perform actions such as transferring tokens, triggering Move modules, or trading NFTs. Here’s how you can build a transaction to transfer APT: 1. Create an Account To create a new account, you first generate new credentials then fund the account. On devnet networks, you can fund an account programmatically by asking a “faucet” ```kotlin val aliceAccount = Account.generate() val bobAccount = Account.generate() ``` On testnet you can mint at the [mint page](/network/faucet). 2. Build the Transaction ```kotlin val txn = aptos.buildTransaction.simple( sender = aliceAccount.accountAddress, data = entryFunctionData { function = "0x1::coin::transfer" typeArguments = typeArguments { +TypeTagStruct("0x1::aptos_coin::AptosCoin") } functionArguments = functionArguments { +bobAccount.accountAddress +U64(SEND_AMOUNT) } },) ``` 3. Sign the Transaction Once you have built a transaction, you can sign it using the `sign` method. ```kotlin val aliceAuthenticator = aptos.sign( sender = aliceAccount, transaction = txn, ) ``` 4. Submit the Transaction Finally, you can submit the transaction to the network using the `submitTransaction.simple` method. ```kotlin val commitedTransaction = aptos.submitTransaction.simple( transaction = signedTransaction, senderAuthenticator = aliceAuthenticator, ) ``` 5. Wait for the Transaction to Execute Then you can wait for the transaction to be executed by using the `waitForTransaction` method. ```kotlin val executedTransaction = aptos.waitForTransaction(HexInput.fromString(commitedTransaction.expect("Transaction failed").hash)) ``` # Sponsored Transactions (Fee Payer) The Kotlin SDK provides support for sponsored transactions also known as fee payer transactions. The standard flow for sending a sponsored transaction is as follows: 1. Determine upon **operation** by creating a **Transaction** 2. The **sender signs** the transaction 3. The **fee payer** signs the transaction 4. **Submit** the transaction ## Determine Upon Operation [Section titled “Determine Upon Operation”](#determine-upon-operation) As we’d already seen in the previous section, you can build a transaction by yourself using the `buildTransaction.simple` method or use the pre-built transaction builders like `transferCoinTransaction`. However, in the case of sponsored transactions, you need to specify the optional `withFeePayer` parameter as `true` in all cases. ```kotlin val txn = aptos.buildTransaction.simple( sender = alice.accountAddress, data = entryFunctionData { function = "0x1::coin::transfer" typeArguments = typeArguments { +TypeTagStruct("0x1::aptos_coin::AptosCoin") } functionArguments = functionArguments { +bob.accountAddress +U64(SEND_AMOUNT_UNITS.toULong()) } }, withFeePayer = true, ) ``` OR ```kotlin val txn = aptos.transferCoinTransaction( sender = alice, receiver = bob.accountAddress, amount = SEND_AMOUNT_UNITS, withFeePayer = true, ) ``` ## Sign the Transaction [Section titled “Sign the Transaction”](#sign-the-transaction) Once you have built a transaction, you (the sender) can sign it using the `sign` method. ```kotlin val aliceAuthenticator = aptos.sign( sender = alice, transaction = txn, ) ``` ## Sign the Transaction as Fee Payer [Section titled “Sign the Transaction as Fee Payer”](#sign-the-transaction-as-fee-payer) To sign the transaction as a fee payer, you can use the `signAsFeePayer` method. ```kotlin val signerAuthenticator = aptos.signAsFeePayer( feePayer = sponsor, transaction = txn, ) ``` ## Submit the Transaction [Section titled “Submit the Transaction”](#submit-the-transaction) Finally, you can submit the transaction to the network using the `submit` method. ```kotlin val committedTxn = aptos.submitTransaction.simple( transaction = txn, senderAuthenticator = aliceAuthenticator, feePayerAuthenticator = signerAuthenticator, ) ``` Note You can collapse the fee payer signing and submitting steps into one by using the `signAndSubmitAsFeePayer` method. ```kotlin val committedTxn = aptos.signAndSubmitAsFeePayer(sponsor, aliceAuthenticator, txn) ``` # Swift SDK There is a Swift SDK for Aptos, built by Alcove [here](https://github.com/ALCOVE-LAB/aptos-swift-sdk) ## Installing the Swift SDk [Section titled “Installing the Swift SDk”](#installing-the-swift-sdk) ```swift .package(url: "https://github.com/ALCOVE-LAB/aptos-swift-sdk.git", branch: "main") ``` ## Using the Swift SDk [Section titled “Using the Swift SDk”](#using-the-swift-sdk) ### Creating a client [Section titled “Creating a client”](#creating-a-client) You can create a client by importing the aptos-swift-sdk, and createing a `Client` ```swift import Aptos let client = Aptos(aptosConfig: .devnet) ``` You can configure the network with the AptosConfig.Network, or use a preexisting AptosConfig.devnet, AptosConfig.testnet, or AptosConfig.mainnet ### Creating a private key [Section titled “Creating a private key”](#creating-a-private-key) You can create a new Ed25519 account’s private key by calling Account.generate(). ```swift let account = Account.generate() ``` Derive from private key ```swift let privateKey = try Ed25519PrivateKey("myEd25519privatekeystring") // or let singleKeyPrivateKey = try Secp256k1PrivateKey(Secp256k1.privateKey) let newAccount: Account.Ed25519Account = try Account.fromPrivateKey(privateKey) let singleKeyAccount: Account.SingleKeyAccount = try Account.fromPrivateKey(singleKeyPrivateKey) ``` Derive from path ```swift let path = "m/44'/637'/0'/0'/1" let mnemonic = "various float stumble..." let newAccount = try Account.fromDerivationPath(Wallet.path, mnemonic: Wallet.mnemonic) ``` ### Funding accounts [Section titled “Funding accounts”](#funding-accounts) You can create and fund an account with a faucet on devnet ```swift let account = Account.generate() let txn = try await client.faucet.fundAccount(accountAddress: account.accountAddress, amount: 100_000_000) ``` On testnet you can mint at the [mint page](/network/faucet). ### Sending a transaction [Section titled “Sending a transaction”](#sending-a-transaction) You can send a AptosCoin via a transaction ```swift let txn: TransactionResponse let senderAccount = Account.generate() _ = try await aptos.faucet.fundAccount(accountAddress: senderAccount.accountAddress, amount: 100_000_000) let bob = Account.generate() // Build transaction let rawTxn = try await aptos.transaction.build.simple( sender: senderAccount.accountAddress, data: InputEntryFunctionData( function: "0x1::aptos_account::transfer", functionArguments: [bob.accountAddress, 100] ) ) // Sign let authenticator = try await aptos.transaction.sign.transaction( signer: senderAccount, transaction: rawTxn ) // Submit let response = try await aptos.transaction.submit.simple( transaction: rawTxn, senderAuthenticator: authenticator ) // Wait txn = try await aptos.transaction.waitForTransaction(transactionHash: response.hash) // Read let transaction = try await aptos.transaction.getTransactionByHash(txn.hash) ``` ### Testing [Section titled “Testing”](#testing) To run the SDK tests, simply run from the root of this repository: > Note: for a better experience, make sure there is no aptos local node process up and running (can check if there is a ?process running on port 8080). ```swift swift test ``` # Unity SDK (Legacy) Caution This SDK is currently unmaintained. You can use the official [Unity SDK](/build/sdks/unity-official-sdk) for the latest features. The [Aptos Unity SDK](https://github.com/aptos-labs/Aptos-Unity-SDK) is a .NET implementation of the [Aptos SDK](/build/sdks), compatible with .NET Standard 2.0 and .NET 4.x for Unity. The goal of this SDK is to provide a set of tools for developers to build multi-platform applications (mobile, desktop, web, VR) using the Unity game engine and the Aptos blockchain infrastructure. See the post [Aptos Labs brings Web3 to Gaming with its new SDK for Unity developers](https://medium.com/aptoslabs/aptos-labs-brings-web3-to-gaming-with-its-new-sdk-for-unity-developers-e6544bdf9ba9) and the [Technical details](https://github.com/aptos-labs/Aptos-Unity-SDK#technical-details) section of the Unity SDK README for all the features offered to game developers by the Aptos Unity SDK. ## User flows [Section titled “User flows”](#user-flows) The Aptos Unity SDK supports these use cases: * *Progressive onboarding flow* in which users can log into a game by email. In this flow, transactions are proxied, and Aptos uses a distributed key system. The users can then onboard to a full custodial wallet if desired. * *In-game non-custodial wallet integration* in which game developers have the option to allow users to create full non-custodial wallets in the games. * *Off-game non-custodial wallet integration* in which game developers may allow users to connect to a desktop wallet or a mobile wallet within the game or create burner wallets from the parent wallet seamlessly. ## Prerequisites [Section titled “Prerequisites”](#prerequisites) ### Supported Unity versions [Section titled “Supported Unity versions”](#supported-unity-versions) | Supported Version: | Tested | | ------------------ | ------ | | 2021.3.x | ✅ | | 2022.2.x | ✅ | | Windows | macOS | iOS | Android | WebGL | | ------- | ----- | --- | ------- | ----- | | ✅ | ✅ | ✅ | ✅ | ✅ | ### Dependencies [Section titled “Dependencies”](#dependencies) > As of Unity 2021.x.x, Newtonsoft Json is a common dependency. Prior versions of Unity require installing Newtonsoft. * [Chaos.NaCl.Standard](https://www.nuget.org/packages/Chaos.NaCl.Standard/) * Microsoft.Extensions.Logging.Abstractions.1.0.0 — required by NBitcoin.7.0.22 * Newtonsoft.Json * NBitcoin.7.0.22 * [Portable.BouncyCastle](https://www.nuget.org/packages/Portable.BouncyCastle) * Zxing ## Install the Unity SDK [Section titled “Install the Unity SDK”](#install-the-unity-sdk) You may install the Unity SDK either through our `unitypackage` or the [Unity Package Manager](https://docs.unity3d.com/Manual/Packages.html). ### Install by `unitypackage` [Section titled “Install by unitypackage”](#install-by-unitypackage) 1. Start Unity. 2. Download the latest `Aptos.Unity.unitypackage` file from the [Unity Asset Store](https://assetstore.unity.com/packages/decentralization/aptos-sdk-244713). 3. Click **Assets** → **Import Packages** → **Custom Package** and select the downloaded file. ### Install by Unity Package Manager [Section titled “Install by Unity Package Manager”](#install-by-unity-package-manager) 1. Open the [Unity Package Manager](https://docs.unity3d.com/Manual/upm-ui.html) window. 2. Click the add **+** button in the top status bar. 3. Select *Add package from git URL* from the dropdown menu. 4. Enter the URL ** and click **Add**. # Aptos C++ / Unreal SDK There is a C++ / Unreal SDK for Aptos, built by Var Meta [here](https://github.com/VAR-META-Tech/Aptos-Cpp-SDK/) ## Installing the SDK [Section titled “Installing the SDK”](#installing-the-sdk) There are installation instructions, as well as usage instructions [here](https://github.com/VAR-META-Tech/Aptos-Cpp-SDK/?tab=readme-ov-file#installation-guide) # .NET SDK Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. Integrate Aptos Web3 capabilities within your .NET applications. The goal of this SDK is to provide a set of tools for developers to build multi-platform applications across compatible game engines and platforms. **Supported Features** * Binary Canonical Serialization (BCS) encoding and decoding * Ed25519, SingleKey, MultiKey, and Keyless signer support * Utilities for transaction building, signing, and submission * Abstractions over the Aptos Fullnode and Indexer APIs * Aptos Names (ANS) support for resolution and lookup ## Installation [Section titled “Installation”](#installation) The .NET SDK is available on [NuGet](https://www.nuget.org/packages/Aptos). You can install the .NET SDK using the following command: ```shellscript dotnet add package Aptos ``` ## Gaming Integrations [Section titled “Gaming Integrations”](#gaming-integrations) Begin using the Aptos .NET SDK in your game engine of choice. [Godot Integration ](/build/sdks/dotnet-sdk/godot-integration)Begin integrating into Godot projects. [Unity Integration ](/build/sdks/dotnet-sdk/unity-integration)Begin integrating into Unity projects. ### Compatibility [Section titled “Compatibility”](#compatibility) | .NET Version | Supported | Target Game Engines | | ----------------- | --------- | ------------------- | | .NET Standard 2.1 | ✅ | Unity | | .NET 6.0 | ✅ | Godot | | .NET 7.0 | ✅ | Godot (Android) | | .NET 8.0 | ✅ | Godot (iOS) | ## Resources [Section titled “Resources”](#resources) [Getting Started ](/build/sdks/dotnet-sdk/getting-started)Begin developing using the Aptos .NET SDK. [Full API Reference ](https://aptos-labs.github.io/aptos-dotnet-sdk/)The full API reference for the Aptos .NET SDK. # Ed25519 Accounts Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. The Aptos .NET SDK provides a simple way to create and manage Ed25519 accounts. In this guide we will provide snippets of creating or importing existing accounts. ## Creating Ed25519Accounts [Section titled “Creating Ed25519Accounts”](#creating-ed25519accounts) Ed25519Accounts are created to sign transactions and interact with the blockchain. ### Using a Private Key [Section titled “Using a Private Key”](#using-a-private-key) To generate an account from a private key, you will need to create the `Ed25519PrivateKey` object and pass into the `Ed25519Account` constructor. The private key can be given a `string` or `byte[]` representation. ```csharp var privateKey = new Ed25519PrivateKey("0x1234...abcdef"); var account = new Ed25519Account(privateKey); ``` ### Using a Mneomonic Phrase [Section titled “Using a Mneomonic Phrase”](#using-a-mneomonic-phrase) To generate an account from a phrase, you can use `Ed25519Account.FromDerivationPath` and pass in the phrase and the derivation path. The derivation path that is typically used throughout the Aptos ecosystem is `m/44'/637'/0'/0'/0'`. ```csharp var account = Ed25519Account.FromDerivationPath( "m/44'/637'/0'/0'/0'", "apple banana cat dog elephant fox ..." ); ``` ### Generating a Random Account [Section titled “Generating a Random Account”](#generating-a-random-account) To create a random account, you can use the `Account.Generate()` method. ```csharp var account = Account.Generate(); ``` ## Additional Resources [Section titled “Additional Resources”](#additional-resources) [Ed25519Account Reference ](https://aptos-labs.github.io/aptos-dotnet-sdk/docs/Aptos.Ed25519Account.html)The full API reference for the Ed25519Account class. # Keyless Accounts Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. The Aptos .NET SDK provides an implementation of Keyless accounts to derive accounts from social provider logins. In this guide, we will provide snippets of creating accounts. ## Creating KeylessAccounts [Section titled “Creating KeylessAccounts”](#creating-keylessaccounts) KeylessAccounts are created to sign transactions and interact with the blockchain using social provider logins. To create a Keyless account, you will need to follow a few steps to obtain the necessary components of a Keyless account. Note We plan on creating end-to-end guides on integrating Keyless with Unity and Godot. They are currently in development. 1. Create a Ephemeral Key Pair The first step to creating a Keyless account is to create an ephemeral key pair. This is an ephemeral key used to sign transactions. It’s important to store this key pair in a secure location in the application as it will be used to sign transactions. ```csharp var ephemeralKeyPair = EphemeralKeyPair.Generate(); ``` 2. Obtaining an OpenID Connect (OIDC) Identity Token To obtain an `id_token` (OIDC Identity Token), you will need to authenticate with a social provider. At the end of the authorization flow, the user should be redirected to your application with an `id_token`. You will need to store this `id_token` in a secure location in the application. **It’s important that the `id_token` has a nonce field that matches the `nonce` field inside the `EphemeralKeyPair`.** **Example:** ```csharp var nonce = ephemeralKeyPair.Nonce; var authorizationUrl = "https://accounts.google.com/o/oauth2/v2/auth&nonce=" + nonce; ``` 3. Deriving a Keyless Account Once the user has the following components, they should be able to derive a Keyless account. * `id_token`: Obtained from the authorization flow. * `EphemeralKeyPair`: Created in the previous steps. **It’s important that the `nonce` field inside the `EphemeralKeyPair` matches the `nonce` field inside the `id_token` to ensure that the user can sign transactions.** ```csharp var client = new AptosClient(Networks.Mainnet); var keylessAccount = await client.Keyless.DeriveAccount(idToken, ephemeralKeyPair); ``` 4. Sign and Submit transactions After deriving a Keyless account, you can sign and submit transactions using the `AptosClient`. ```csharp // 1. Build the transaction var transaction = await client.Transaction.Build( sender: keylessAccount, data: new GenerateEntryFunctionPayloadData( function: "0x1::aptos_account::transfer_coins", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [account.Address, "100000"] ) ); // 2. Sign and submit the transaction var submittedTransaction = await client.Transaction.SignAndSubmitTransaction(keylessAccount, transaction); // 3. (Optional) Wait for the transaction to be committed var committedTransaction = await client.Transaction.WaitForTransaction(submittedTransaction.Hash); ``` ## Additional Resources [Section titled “Additional Resources”](#additional-resources) [KeylessAccount Reference ](https://aptos-labs.github.io/aptos-dotnet-sdk/docs/Aptos.KeylessAccount.html)The full API reference for the KeylessAccount class. # Multikey Accounts Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. The Aptos .NET SDK provides an implementation of Multikey accounts to create accounts from a combination of multiple key pairs. This is useful for Multisig accounts. In this guide, we will provide snippets of creating accounts. ## Creating a MultiKeyAccount [Section titled “Creating a MultiKeyAccount”](#creating-a-multikeyaccount) MultiKeyAccount’s are created to sign transactions where the account is controlled by multiple private keys. ### Create a MultiKeyAccount [Section titled “Create a MultiKeyAccount”](#create-a-multikeyaccount) To create a MultiKey account, you will need the following components: * `PublicKeys`: The public keys of the accounts that control the MultiKey account. * `SignaturesRequired`: The minimum number of signers required to sign transactions. * `Signers`: The account signers that will be used to sign transactions. The number of signers should be equal to or greater than the `SignaturesRequired`. 1. Create your Accounts Create your accounts, they can be different types of accounts. ```csharp var account1 = Ed25519Account.Generate(); var account2 = SingleKeyAccount.Generate(PublicKeyVariant.Secp256k1Ecdsa); ``` 2. Create a MultiKey Verifying Key Create a MultiKey verifying key using the `PublicKeys` and `SignaturesRequired`. In this example, we have two accounts controlling the MultiKey and we require 2 signers to sign transactions. ```csharp var multiKey = new MultiKey( publicKeys: [account1.PublicKey, account2.PublicKey], signaturesRequired: 2, ); ``` 3. Create the MultiKey Account Create the MultiKey account using the `PublicKeys`, `SignaturesRequired`, and `Signers`. ```csharp var multikeyAccount = new MultiKeyAccount( multiKey: multiKey, signers: [account1, account2] ); ``` 4. Sign and Submit transactions After creating a MultiKey account, you can sign and submit transactions using the `AptosClient`. ```csharp // 1. Build the transaction var transaction = await client.Transaction.Build( sender: multikeyAccount, data: new GenerateEntryFunctionPayloadData( function: "0x1::aptos_account::transfer_coins", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [account.Address, "100000"] ) ); // 2. Sign and submit the transaction var submittedTransaction = await client.Transaction.SignAndSubmitTransaction(multikeyAccount, transaction); // 3. (Optional) Wait for the transaction to be committed var committedTransaction = await client.Transaction.WaitForTransaction(submittedTransaction.Hash); ``` ## Additional Resources [Section titled “Additional Resources”](#additional-resources) [MultiKeyAccount Reference ](https://aptos-labs.github.io/aptos-dotnet-sdk/docs/Aptos.MultiKeyAccount.html)The full API reference for the MultiKeyAccount class. # Examples Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. The Aptos .NET SDK provides a number of examples to help you get started with the SDK. You can find the examples in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. [Aptos .NET SDK Examples ](https://github.com/aptos-labs/aptos-dotnet-sdk/tree/main/Aptos.Examples)Example applications for the .NET SDK. 1. Install .NET To run the examples, you will need to install the .NET SDK. You can download the .NET SDK from the [dotnet.microsoft.com](https://dotnet.microsoft.com/en-us/download) website. 2. Clone the Repository Clone the repository by running the following command: ```shellscript git clone https://github.com/aptos-labs/aptos-dotnet-sdk.git ``` 3. Running the Examples You can run the examples by navigating to the `Aptos.Examples` directory and running the `dotnet run --framework net8.0` command. ```shellscript cd Aptos.Examples dotnet run --framework net8.0 ``` 4. Selecting an Example When running the examples, you will be prompted to select an example. You can select the example by entering the number of the example you want to run or navigating with the arrow keys. ![examples-demonstration](https://i.imgur.com/YS140Zb.png) ## Additional Resources [Section titled “Additional Resources”](#additional-resources) [Getting Started ](/build/sdks/dotnet-sdk/getting-started)Begin developing using the Aptos .NET SDK. [Full API Reference ](https://aptos-labs.github.io/aptos-dotnet-sdk/)The full API reference for the Aptos .NET SDK. # Quickstart Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. If you have not already installed the Aptos .NET SDK, follow one of the guides below to get started. [Godot Integration ](/build/sdks/dotnet-sdk/godot-integration)Integrate the Aptos .NET SDK with a Godot project. [Unity SDK ](/build/sdks/dotnet-sdk/unity-integration)Integrate the Aptos .NET SDK with a Unity project. 1. Set up your AptosClient Set up your Aptos client by adding the `Aptos` namespace and instantiating an `AptosClient`. You can use a predefined configuration from `Networks` or configuring your own. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Mainnet); var client = new AptosClient(config); } } ``` 2. Query the Blockchain Now that you have the client setup, you can query the blockchain! ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Mainnet); var client = new AptosClient(config); var ledgerInfo = client.Block.GetLedgerInfo(); Console.WriteLine(ledgerInfo.BlockHeight); } } ``` 3. Sign and Submit Transactions To interact with the blockchain, you will need to create a signer and build a transaction. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Mainnet); var client = new AptosClient(config); // 1. Create a signer var signer = Account.Generate(); // 2. Build the transaction var transaction = await client.Transaction.Build( sender: account, data: new GenerateEntryFunctionPayloadData( function: "0x1::aptos_account::transfer_coins", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [account.Address, "100000"] ) ); // 3. Sign and submit the transaction var pendingTransaction = client.Transaction.SignAndSubmitTransaction(account, transaction); // 4. (Optional) Wait for the transaction to be committed var committedTransaction = await client.Transaction.WaitForTransaction(pendingTransaction); } } ``` 4. Smart Contract View Functions Call view functions to query smart contracts. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Mainnet); var client = new AptosClient(config); // Call the view function by specifying the function name, arguments, and type arguments var values = await client.Contract.View( new GenerateViewFunctionPayloadData( function: "0x1::coin::name", functionArguments: [], typeArguments: ["0x1::aptos_coin::AptosCoin"] ) ); // Returns a list of return values: ["Aptos Coin"] Console.WriteLine("APT Name: " + values[0]); } } ``` ## Additional Resources [Section titled “Additional Resources”](#additional-resources) [Full API Reference ](https://aptos-labs.github.io/aptos-dotnet-sdk)The full API reference for the Aptos .NET SDK. # Godot Integration Caution This integration is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. This guide will walk you through the process of integrating the Aptos .NET SDK. To install the Aptos SDK into your Godot project, you will need to add the Aptos SDK into your Godot project’s `.csproj` file. 1. Find the .csproj In the root of your Godot project, find the `.csproj` file. This file is used to configure your Godot project and is used by the Godot build system. You can find this file by clicking on `res://` in the Godot editor and selecting `Open in File Manager`. > If you can’t find the `.csproj` file, you can create a `.cs` file and build the application one time and it should be generated. ![Open in File Manager](https://i.imgur.com/evxv8mY.png) 2. Add the Aptos NuGet package Add the following line to the `` section of the `.csproj` file. If it doesn’t exist, create it the `` section. ```xml ``` It should look something like this: ```xml net6.0 net7.0 net8.0 true AptosSDKExample ``` 3. Use the Aptos SDK Import the `Aptos` namespace in your C# script and use the SDK. ```csharp using Godot; using System; using Aptos; public partial class Example : Node { public override void _Ready() { PrintLedgerInfo(); } async void PrintLedgerInfo() { var client = new AptosClient(Networks.Mainnet); var ledgerInfo = await client.Block.GetLedgerInfo(); GD.Print(ledgerInfo.BlockHeight); } } ``` ## Next Steps [Section titled “Next Steps”](#next-steps) You’ve successfully integrated the Aptos .NET SDK into your Godot project. Now you can start building your game and interacting with the Aptos blockchain. Below are some resources to help you get started. [Getting Started ](/build/sdks/dotnet-sdk/getting-started)Begin developing using the Aptos .NET SDK. # View Functions Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. View functions allow you to query smart contracts on the blockchain. They are defined in smart contracts as entry functions with the `view` modifier. In this guide, we will provide snippets of view functions and how they are typed and used. ## Dynamically Typed View Functions [Section titled “Dynamically Typed View Functions”](#dynamically-typed-view-functions) When you don’t care about the return type of a view function, you can use the `View` function without any type arguments. The Move function we will be calling: ```move public fun balance(owner: address): u64 ``` And to call the view function, we will use the `View` function from the `ContractClient`. ```csharp using Aptos; class Program { static void Main(string[] args) { var client = new AptosClient(Networks.Mainnet); // Call the view function by specifying the function name, arguments, and type arguments var values = await client.Contract.View( new GenerateViewFunctionPayloadData( function: "0x1::coin::balance", functionArguments: ["0x1"], typeArguments: ["0x1::aptos_coin::AptosCoin"] ) ); // Returns a list of return values: ["100"] ulong balance = ulong.Parse(values[0]); } } ``` ## Simple Typed View Functions [Section titled “Simple Typed View Functions”](#simple-typed-view-functions) For view functions with common return types, you can type the return values by passing in a type argument. The Move function we will be calling: ```move public fun get_current_epoch_proposal_counts(validator_index: u64): (u64, u64) ``` And to call the view function, we will use the `View` function from the `ContractClient` with the type arguments. ```csharp using Aptos; class Program { static void Main(string[] args) { var client = new AptosClient(Networks.Mainnet); // Call the view function by specifying the function name, arguments, and type arguments var values = await client.Contract.View>( new GenerateViewFunctionPayloadData( function: "0x1::stake::get_current_epoch_proposal_counts", functionArguments: [(ulong)0], typeArguments: [] ) ); // Returns a list of return values: ["100", "100"] ulong successfulProposals = values[0]; ulong failedProposals = values[1]; } } ``` ## Complex Typed View Functions [Section titled “Complex Typed View Functions”](#complex-typed-view-functions) For view functions with complex return types, you can leverage `Newtonson.Json` to deserialize the return values. By default, all types passed into the View function leverage `JsonConvert.DeserializeObject()` from `Newtonson.Json` to deserialize the return values. You can override the deserialization behavior by creating a custom `JsonConverter`. The Move function we will be calling: ```move public fun supply(): Option ``` Create your own `JsonConverter` to deserialize the return values. ```csharp using Aptos; using Newtonsoft.Json; [JsonConverter(typeof(CoinSupplyConverter))] class CoinSupply(ulong value) { public ulong Value; } class CoinSupplyConverter : JsonConverter { public override CoinSupply ReadJson(JsonReader reader, Type objectType, CoinSupply existingValue, bool hasExistingValue, JsonSerializer serializer) { // The return type of the view function is an Option -> [{ "vec": [] }] or [{ "vec": ["100"] }] JArray array = JArray.Load(reader); var option = array[0]; // If the Option is None if (option["vec"].Count == 0) return null; // If the Option is Some ulong value = ulong.Parse(option["vec"][0]); return new CoinSupply(value); } } ``` And to call the view function, we will use the `View` function from the `ContractClient` with the type arguments. ```csharp using Aptos; using Newtonsoft.Json; class Program { static void Main(string[] args) { var client = new AptosClient(Networks.Mainnet); // Call the view function by specifying the function name, arguments, and type arguments CoinSupply coinSupply = await client.Contract.View( new GenerateViewFunctionPayloadData( function: "0x1::coin::supply", functionArguments: [], typeArguments: ["0x1::aptos_coin::AptosCoin"] ) ); ulong coinSupply = coinSupply.Value; } } ``` # Basic Transactions Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. This section covers the basic transaction types that can be built and submitted to the Aptos blockchain. 1. Set up your AptosClient Set up your Aptos client by adding the `Aptos` namespace and instantiating an `AptosClient`. You can use a predefined configuration from `Networks` or configuring your own. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Mainnet); var client = new AptosClient(config); } } ``` 2. Set up an Account To create a transaction, you will need an account to sign the transaction. This can be done using a private key, mnemonic, or a combination of both. In this example, we will generate a random new account. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Devnet); var client = new AptosClient(config); // 1. Create an account and fund it. var account = Account.Generate(); await client.Faucet.FundAccount(account.Address, 100_000_000); } } ``` 3. Build the Transaction To interact with the blockchain, you will need to build a transaction. The `AptosClient` can be used to build a transaction payload that can be signed and submitted to chain. In the transaction, we can specify the sender, entry function, and arguments. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Devnet); var client = new AptosClient(config); var account = Account.Generate(); // 2. Build the transaction var transaction = await client.Transaction.Build( sender: account, data: new GenerateEntryFunctionPayloadData( function: "0x1::aptos_account::transfer_coins", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [account.Address, "100000"] ) ); } } ``` 4. Sign and Submit Transactions Once the transaction is built, it can be signed and submitted to the blockchain. The `AptosClient` can be used to sign and submit the transaction. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Devnet); var client = new AptosClient(config); var account = Account.Generate(); var transaction = await client.Transaction.Build( sender: account, data: new GenerateEntryFunctionPayloadData( function: "0x1::aptos_account::transfer_coins", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [account.Address, "100000"] ) ); // 3. Sign the transaction var submittedTransaction = await client.Transaction.SignAndSubmitTransaction(signer, transaction); } } ``` 5. (Optional) Wait for the Transaction to Execute After the transaction has been submitted, it will have to process before its committed to the blockchain. The `AptosClient` can be used to wait for the transaction to be processed and executed. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Devnet); var client = new AptosClient(config); var account = Account.Generate(); var transaction = await client.Transaction.Build( sender: account, data: new GenerateEntryFunctionPayloadData( function: "0x1::aptos_account::transfer_coins", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [account.Address, "100000"] ) ); var submittedTransaction = await client.Transaction.SignAndSubmitTransaction(account, transaction); // 4. Wait for the transaction to be processed var transactionResult = await client.Transaction.WaitForTransaction(submittedTransaction.Hash); } } ``` # Sponsored Transactions Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. This section covers how to do sponsored transactions with the Aptos .NET SDK. **It’s important that you understand the basics of building transactions. If not, refer to the guide below for more information.** [Basic Transactions ](/build/sdks/dotnet-sdk/transactions/basic-transactions)Learn how to build basic transactions with the Aptos .NET SDK. ## Create a Sponsored Transaction [Section titled “Create a Sponsored Transaction”](#create-a-sponsored-transaction) 1. Set up your AptosClient Set up your Aptos client by adding the `Aptos` namespace and instantiating an `AptosClient`. You can use a predefined configuration from `Networks` or configuring your own. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Mainnet); var client = new AptosClient(config); } } ``` 2. Set up the Accounts To create a sponsored transaction, it’s important that there is a sponsor and a user. In this example: * **The user:** Will be the account that is sending APT to the recipient. * **The sponsor:** Will be the account that pays for **gas fees** of the transaction. ```csharp using Aptos; class Program { static void Main(string[] args) { var config = new AptosConfig(Aptos.Networks.Devnet); var client = new AptosClient(config); // 1. Create accounts and fund it them. var user = Account.Generate(); var recipient = Account.Generate(); var sponsor = Account.Generate(); await client.Faucet.FundAccount(user.Address, 100_000_000); await client.Faucet.FundAccount(sponsor.Address, 100_000_000); } } ``` 3. Build the Transaction You can now build the transaction using the `AptosClient`. In the transaction, its important that you enable the `withFeePayer` flag to enable the sponsored transactions. ```csharp using Aptos; class Program { static void Main(string[] args) { ... // 2. Build the transaction var transaction = await client.Transaction.Build( sender: account, data: new GenerateEntryFunctionPayloadData( function: "0x1::aptos_account::transfer_coins", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [account.Address, "100000"] ), // It's important to set this flag to true to enable sponsored transactions withFeePayer: true ); } } ``` 4. Sign the Transaction using both accounts Have both the user and the sponsor sign the transaction. This ensures that the sponsor has agreed to pay for the transaction and the user has agreed to execute the transaction. When signing with the sponsor, the `SignAsFeePayer` method is used instead. ```csharp using Aptos; class Program { static void Main(string[] args) { ... // 3. Sign the transaction with the user var userSignature = client.Transaction.SignTransaction(transaction); // 4. Sign the transaction with the sponsor var feePayerSignature = client.Transaction.SignAsFeePayer(feePayer, transaction); } } ``` 5. Submit the Transaction Once the transaction is signed by both the user and the sponsor, it can be submitted to the blockchain. The `AptosClient` can be used to submit the transaction. ```csharp using Aptos; class Program { static void Main(string[] args) { ... // 5. Submit the transaction var submitTransactionData = new SubmitTransactionData(transaction, userSignature, feePayerSignature); var submittedTransaction = await client.Transaction.SubmitTransaction(submitTransactionData); } } ``` 6. Wait for the Transaction to Execute After the transaction has been submitted, it will have to process before its committed to the blockchain. The `AptosClient` can be used to wait for the transaction to be processed and executed. ```csharp using Aptos; class Program { static void Main(string[] args) { ... // 6. Wait for the transaction to be processed var transactionResult = await client.Transaction.WaitForTransaction(submittedTransaction.Hash); } } ``` 7. Print Balances After the transaction has committed to the blockchain, you can print the balances of the user and the recipient. ```csharp using Aptos; class Program { static void Main(string[] args) { ... // 7. Print balances var userBalance = await client.Account.GetCoinBalance(user.Address); var feePayerBalance = await client.Account.GetCoinBalance(feePayer.Address); var recipientBalance = await client.Account.GetCoinBalance(recipient.Address); Console.WriteLine($"User {user.Address} has {userBalance?.Amount ?? 0} APT"); Console.WriteLine($"FeePayer {feePayer.Address} has {feePayerBalance?.Amount ?? 0} APT"); Console.WriteLine($"Recipient {recipient.Address} has {recipientBalance?.Amount ?? 0} APT"); } } ``` The result should look like this: ```shellscript User 0xffd89f1e2fef8c67cfb1b99d58ea799281f1d1a0a178db49c3eacab2fe7c0735 has 99900000 APT FeePayer 0x842ca7d995255ee73186a6793d6bde7c983c528be7b1a25e1614f4eddb744d4c has 99900100 APT Recipient 0x823010a52a589ef528d14ebee4a4af56a00f0ae8afba135c9268581a960e21d7 has 100000 APT ``` The user sent 0.001 APT to the recipient leaving the user with 0.999 APT. The sponsor paid for the gas fees of the transaction leaving the sponsor with 0.999001 APT. # Unity Integration Caution This integration is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/aptos-dotnet-sdk](https://github.com/aptos-labs/aptos-dotnet-sdk) repository. This guide will walk you through the process of integrating the Aptos .NET SDK. 1. Install the Aptos Unity SDK #### Option 1: Import via Unity Package Manager (UPM) [Section titled “Option 1: Import via Unity Package Manager (UPM)”](#option-1-import-via-unity-package-manager-upm) 1. Open Package Manager window (Window | Package Manager) 2. Click + button on the upper-left of a window, and select *Add package from git URL…* 3. Enter the following URL and click Add button ```shellscript https://github.com/aptos-labs/unity-sdk.git?path=/Packages/com.aptoslabs.aptos-unity-sdk ``` #### Option 2: Import via `unitypackage` [Section titled “Option 2: Import via unitypackage”](#option-2-import-via-unitypackage) 1. Go to the [`aptos-labs/unity-sdk Releases`](https://github.com/aptos-labs/unity-sdk/releases) and download the latest release. 2. Drag and drop the `.unitypackage` file into your Unity project. 2. Use the Aptos SDK Import the `Aptos` namespace in your C# script and use the SDK. ```csharp using UnityEngine; using Aptos; class Example : MonoBehaviour { public void Start() { PrintLedgerInfo(); } async void PrintLedgerInfo() { var client = new AptosUnityClient(Networks.Mainnet); var ledgerInfo = await client.Block.GetLedgerInfo(); Debug.Log(ledgerInfo.BlockHeight); } } ``` ## Next Steps [Section titled “Next Steps”](#next-steps) You’ve successfully integrated the Aptos .NET SDK into your Unity project. Now you can start building your game and interacting with the Aptos blockchain. Below are some resources to help you get started. [Getting Started ](/build/sdks/dotnet-sdk/getting-started)Begin developing using the Aptos .NET SDK. [Unity SDK ](/build/sdks/unity-sdk)Overview of the Unity SDK. [Aptos Wallet Starter ](https://github.com/aptos-labs/aptos-unity-starter)Example Unity project with an integration of the Aptos Unity SDK. # Go SDK ## Installing the Go SDK [Section titled “Installing the Go SDK”](#installing-the-go-sdk) Aptos provides an official Go SDK in the [aptos-go-sdk GitHub](https://github.com/aptos-labs/aptos-go-sdk) repository. To use the Go SDK, get the main package here: ```shellscript go get github.com/aptos-labs/aptos-go-sdk ``` ## Usage [Section titled “Usage”](#usage) [Fetching Data ](/build/sdks/go-sdk/fetch-data-via-sdk)Learn how to fetch data with the Go SDK [Submitting Transactions ](/build/sdks/go-sdk/building-transactions)Learn how to submit transactions with the Go SDK [Examples ](/build/sdks/go-sdk/go-examples)Explore Go examples provided in the SDK # Go SDK - Creating and Managing Accounts There are several ways to generate account credentials using the Go SDK. You can use: * `aptos.NewEd25519Account()` * `aptos.NewSecp256k1Account()` * `aptos.NewEd25519SingleSenderAccount()` * `aptos.NewAccountFromSigner()` `Account.NewEd25519Account()` is the most commonly used method to create keys for a new account. It defaults to `ED25519` key types, but you can also specify which signing scheme you would prefer like so: ```go // To derive an ed25519 account account1 := aptos.NewEd25519Account() // To derive a secp256k1 account account2 := aptos.NewSecp256k1Account() ``` Once you have generated credentials, you **must** fund it for the network to know it exists. In devnet environments this can be done with a faucet by running the following command: ```go client, err = aptos.NewClient(aptos.DevnetConfig) if err != nil { panic("Failed to create client:" + err.Error()) } // Fund an account with 1 Devnet APT client.Fund(account1.Address, 100_000_000) ``` On testnet you can mint at the [mint page](/network/faucet). ## Other Ways To Represent Accounts [Section titled “Other Ways To Represent Accounts”](#other-ways-to-represent-accounts) If you have a private key, or equivalent representation, you can use that to create an `Account` struct to manage those credentials while using the Go SDK. Here are several examples that show how to do so with specific encoding schemes. ### Derive an account from private key [Section titled “Derive an account from private key”](#derive-an-account-from-private-key) The SDK supports deriving an account from a private key with `NewAccountFromSigner()` method. In addition, this method supports deriving an account from a private key and account address. This method uses a local calculation and therefore is used to derive an `Account` that has not had its authentication key rotated. ```go // to derive an account with a Ed25519 key scheme privateKey := &aptos.Ed25519PrivateKey{} err := privateKey.FromHex(privateKeyHex) if err != nil { panic("Failed to parse private key:" + err.Error()) } account := aptos.NewAccountFromSigner(privateKey) // to derive an account with a Single Sender Ed25519 key scheme privateKey := &aptos.Ed25519PrivateKey{} err := privateKey.FromHex(privateKeyHex) if err != nil { panic("Failed to parse private key:" + err.Error()) } singleSigner := &crypto.SingleSigner{Signer: privateKey} account := aptos.NewAccountFromSigner(singleSigner) // to derive an account with a Single Sender Secp256k1 key scheme privateKey := &aptos.Secp256k1PrivateKey{} err := privateKey.FromHex(privateKeyHex) if err != nil { panic("Failed to parse private key:" + err.Error()) } singleSigner := &crypto.SingleSigner{Signer: privateKey} account := aptos.NewAccountFromSigner(singleSigner) // to derive an account with a private key and account address address := &aptos.AccountAddress{} err := address.ParseStringRelaxed(addressHex) if err != nil { panic("Failed to parse address:" + err.Error()) } privateKey := &aptos.Ed25519PrivateKey{} err := privateKey.FromHex(privateKeyHex) if err != nil { panic("Failed to parse private key:" + err.Error()) } account := aptos.NewAccountFromSigner(privateKey, address.AuthKey()) ``` # Go SDK - Building Transactions Transactions allow you to change on-chain data or trigger events. Generally, transactions follow 5 steps from building to executing on chain: building, simulating, signing, submitting, and waiting. Note For these examples, `client` is an instance of the [`Client`](https://pkg.go.dev/github.com/aptos-labs/aptos-go-sdk#Client) object. 1. Build Building a transaction is how you specify: 1. **The `Sender` account.**\ This account normally pays the gas fees for this transaction. See [Sponsoring Transactions](/build/sdks/go-sdk/building-transactions/sponsoring-transactions) to learn how to have another account pay for transaction fees. 2. **The `Function` being called on-chain.**\ This is the identifier for the smart contract entry function on-chain that will trigger when you execute this transaction. 3. **The `ArgTypes` and `Args`.**\ This is any data the function needs to run. This can be made for a single account like so: ```go // 1. Build transaction accountBytes, err := bcs.Serialize(&bob.Address) if err != nil { panic("Failed to serialize bob's address:" + err.Error()) } amountBytes, err := bcs.SerializeU64(TransferAmount) if err != nil { panic("Failed to serialize transfer amount:" + err.Error()) } rawTxn, err := client.BuildTransaction(alice.AccountAddress(), aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ Module: aptos.ModuleId{ Address: aptos.AccountOne, Name: "aptos_account", }, Function: "transfer", ArgTypes: []aptos.TypeTag{}, Args: [][]byte{ accountBytes, amountBytes, }, }}, ) ``` Note All arguments `Args` must be serialized to bytes before being passed in. They must be serialized with [Binary Canonical Serialization (BCS)](/build/sdks/go-sdk/building-transactions/bcs-format) #### Building Options [Section titled “Building Options”](#building-options) You can customize the way your transaction executes by passing in `options` when building. Some of the most commonly used options are: 1. `MaxGasAmount` - This caps the amount of gas you are willing to pay for to execute this transaction. 2. `GasUnitPrice` - You can specify a higher than minimum price per gas to be executed with higher priority by the Aptos network. 3. `ExpirationSeconds` - This gives a concrete time the transaction must execute by or it will be canceled. The SDK provides sensible defaults for these values if they are not specified explicitly. 2. Simulate (Optional) Every transaction on the Aptos chain has a gas fee associated with how much work the network machines have to do when executing the transaction. In order to estimate the cost associated with that, you can simulate transactions before committing them. Note This simulation only requires the `PublicKey` of an account since it will not impact the actual state of the ledger. You can execute the simulation by using `aptos.SimulateTransaction` like so: ```go // 2. Simulate transaction (optional) // This is useful for understanding how much the transaction will cost // and to ensure that the transaction is valid before sending it to the network // This is optional, but recommended simulationResult, err := client.SimulateTransaction(rawTxn, alice) // If the fee looks ok, continue to signing! ``` 3. Sign Once the transaction is built and the fees seem reasonable, you can sign the transaction with `rawTransaction.SignedTransaction()`. The signature must come from the `sender` account. ```go // 3. Sign transaction signedTxn, err := rawTxn.SignedTransaction(alice) ``` 4. Submit Now that the transaction is signed, you can submit it to the network using `client.SubmitTransaction()` like so: ```go // 4. Submit transaction submitResult, err := client.SubmitTransaction(signedTxn) ``` 5. Wait Finally, you can wait for the result of the transaction by using `client.WaitForTransaction()` and specifying the hash of the transaction you just submitted like so: ```go // 5. Wait for the transaction to complete txnHash := submitResult.Hash _, err = client.WaitForTransaction(txnHash) ``` ## Full Go Example [Section titled “Full Go Example”](#full-go-example) ```go // transfer_coin is an example of how to make a coin transfer transaction in the simplest possible way package main import ( "fmt" "github.com/aptos-labs/aptos-go-sdk" "github.com/aptos-labs/aptos-go-sdk/bcs" ) const FundAmount = 100_000_000 const TransferAmount = 1_000 // example This example shows you how to make an APT transfer transaction in the simplest possible way func example(networkConfig aptos.NetworkConfig) { // Create a client for Aptos client, err := aptos.NewClient(networkConfig) if err != nil { panic("Failed to create client:" + err.Error()) } // Create accounts locally for alice and bob alice, err := aptos.NewEd25519Account() if err != nil { panic("Failed to create alice:" + err.Error()) } bob, err := aptos.NewEd25519Account() if err != nil { panic("Failed to create bob:" + err.Error()) } fmt.Printf("\n=== Addresses ===\n") fmt.Printf("Alice: %s\n", alice.Address.String()) fmt.Printf("Bob:%s\n", bob.Address.String()) // Fund the sender with the faucet to create it on-chain err = client.Fund(alice.Address, FundAmount) if err != nil { panic("Failed to fund alice:" + err.Error()) } aliceBalance, err := client.AccountAPTBalance(alice.Address) if err != nil { panic("Failed to retrieve alice balance:" + err.Error()) } bobBalance, err := client.AccountAPTBalance(bob.Address) if err != nil { panic("Failed to retrieve bob balance:" + err.Error()) } fmt.Printf("\n=== Initial Balances ===\n") fmt.Printf("Alice: %d\n", aliceBalance) fmt.Printf("Bob:%d\n", bobBalance) // 1. Build transaction accountBytes, err := bcs.Serialize(&bob.Address) if err != nil { panic("Failed to serialize bob's address:" + err.Error()) } amountBytes, err := bcs.SerializeU64(TransferAmount) if err != nil { panic("Failed to serialize transfer amount:" + err.Error()) } rawTxn, err := client.BuildTransaction(alice.AccountAddress(), aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ Module: aptos.ModuleId{ Address: aptos.AccountOne, Name: "aptos_account", }, Function: "transfer", ArgTypes: []aptos.TypeTag{}, Args: [][]byte{ accountBytes, amountBytes, }, }}, ) if err != nil { panic("Failed to build transaction:" + err.Error()) } // 2. Simulate transaction (optional) // This is useful for understanding how much the transaction will cost // and to ensure that the transaction is valid before sending it to the network // This is optional, but recommended simulationResult, err := client.SimulateTransaction(rawTxn, alice) if err != nil { panic("Failed to simulate transaction:" + err.Error()) } fmt.Printf("\n=== Simulation ===\n") fmt.Printf("Gas unit price: %d\n", simulationResult[0].GasUnitPrice) fmt.Printf("Gas used: %d\n", simulationResult[0].GasUsed) fmt.Printf("Total gas fee: %d\n", simulationResult[0].GasUsed*simulationResult[0].GasUnitPrice) fmt.Printf("Status: %s\n", simulationResult[0].VmStatus) // 3. Sign transaction signedTxn, err := rawTxn.SignedTransaction(alice) if err != nil { panic("Failed to sign transaction:" + err.Error()) } // 4. Submit transaction submitResult, err := client.SubmitTransaction(signedTxn) if err != nil { panic("Failed to submit transaction:" + err.Error()) } txnHash := submitResult.Hash // 5. Wait for the transaction to complete _, err = client.WaitForTransaction(txnHash) if err != nil { panic("Failed to wait for transaction:" + err.Error()) } // Check balances aliceBalance, err = client.AccountAPTBalance(alice.Address) if err != nil { panic("Failed to retrieve alice balance:" + err.Error()) } bobBalance, err = client.AccountAPTBalance(bob.Address) if err != nil { panic("Failed to retrieve bob balance:" + err.Error()) } fmt.Printf("\n=== Intermediate Balances ===\n") fmt.Printf("Alice: %d\n", aliceBalance) fmt.Printf("Bob:%d\n", bobBalance) // Now do it again, but with a different method resp, err := client.BuildSignAndSubmitTransaction(alice, aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ Module: aptos.ModuleId{ Address: aptos.AccountOne, Name: "aptos_account", }, Function: "transfer", ArgTypes: []aptos.TypeTag{}, Args: [][]byte{ accountBytes, amountBytes, }, }}, ) if err != nil { panic("Failed to sign transaction:" + err.Error()) } _, err = client.WaitForTransaction(resp.Hash) if err != nil { panic("Failed to wait for transaction:" + err.Error()) } aliceBalance, err = client.AccountAPTBalance(alice.Address) if err != nil { panic("Failed to retrieve alice balance:" + err.Error()) } bobBalance, err = client.AccountAPTBalance(bob.Address) if err != nil { panic("Failed to retrieve bob balance:" + err.Error()) } fmt.Printf("\n=== Final Balances ===\n") fmt.Printf("Alice: %d\n", aliceBalance) fmt.Printf("Bob:%d\n", bobBalance) } func main() { example(aptos.DevnetConfig) } ``` ## Summary [Section titled “Summary”](#summary) Building and sending transactions on-chain involves the following 5 steps: 1. **Build** the transaction. 2. **Simulate** the cost. (Optional) 3. **Sign** the transaction (if the simulated cost seems ok). 4. **Submit** the transaction to the network. 5. **Wait** for the chain to validate and update. ## Explore Advanced Transaction Features [Section titled “Explore Advanced Transaction Features”](#explore-advanced-transaction-features) Transactions have a couple of additional features which let them adapt to your needs which you can learn about here: 1. [Multi-Agent Signatures](/build/sdks/go-sdk/building-transactions/multi-agent-transactions) - Allowing multiple accounts to be used for a single contract. 2. [Sponsoring Transactions](/build/sdks/go-sdk/building-transactions/sponsoring-transactions) - Have another account pay gas fees for this transaction. 3. [Batch Submit Transactions](/build/sdks/go-sdk/building-transactions/batching-transactions) - How to send multiple transactions quickly from a single account. 4. [Binary Canonical Serialization (BCS)](/build/sdks/go-sdk/building-transactions/bcs-format) - The format used to serialize data for Aptos transactions. # Go SDK - Batching Transactions The Go SDK has a built-in way to send many transactions concurrently, and order them. This can be a convenient tool when trying to execute multiple transactions quickly from the same account. This can be done with `client.BuildSignAndSubmitTransactions` as can be seen in the below example. ## Full Go Example [Section titled “Full Go Example”](#full-go-example) ```go // sending_concurrent_transactions shows how to submit transactions serially or concurrently on a single account package main import ( "github.com/aptos-labs/aptos-go-sdk" "github.com/aptos-labs/aptos-go-sdk/api" "time" ) func setup(networkConfig aptos.NetworkConfig) (*aptos.Client, aptos.TransactionSigner) { client, err := aptos.NewClient(networkConfig) if err != nil { panic("Failed to create client:" + err.Error()) } sender, err := aptos.NewEd25519Account() if err != nil { panic("Failed to create sender:" + err.Error()) } err = client.Fund(sender.Address, 100_000_000) if err != nil { panic("Failed to fund sender:" + err.Error()) } return client, sender } func payload() aptos.TransactionPayload { receiver := aptos.AccountAddress{} err := receiver.ParseStringRelaxed("0xBEEF") if err != nil { panic("Failed to parse address:" + err.Error()) } amount := uint64(100) p, err := aptos.CoinTransferPayload(nil, receiver, amount) if err != nil { panic("Failed to serialize arguments:" + err.Error()) } return aptos.TransactionPayload{Payload: p} } func sendManyTransactionsSerially(networkConfig aptos.NetworkConfig, numTransactions uint64) { client, sender := setup(networkConfig) responses := make([]*api.SubmitTransactionResponse, numTransactions) payload := payload() senderAddress := sender.AccountAddress() sequenceNumber := uint64(0) for i := uint64(0); i < numTransactions; i++ { rawTxn, err := client.BuildTransaction(senderAddress, payload, aptos.SequenceNumber(sequenceNumber)) if err != nil { panic("Failed to build transaction:" + err.Error()) } signedTxn, err := rawTxn.SignedTransaction(sender) if err != nil { panic("Failed to sign transaction:" + err.Error()) } submitResult, err := client.SubmitTransaction(signedTxn) if err != nil { panic("Failed to submit transaction:" + err.Error()) } responses[i] = submitResult sequenceNumber++ } // Wait on last transaction response, err := client.WaitForTransaction(responses[numTransactions-1].Hash) if err != nil { panic("Failed to wait for transaction:" + err.Error()) } if response.Success == false { panic("Transaction failed due to " + response.VmStatus) } } func sendManyTransactionsConcurrently(networkConfig aptos.NetworkConfig, numTransactions uint64) { client, sender := setup(networkConfig) payload := payload() // start submission goroutine payloads := make(chan aptos.TransactionBuildPayload, 50) results := make(chan aptos.TransactionSubmissionResponse, 50) go client.BuildSignAndSubmitTransactions(sender, payloads, results) // Submit transactions to goroutine go func() { for i := uint64(0); i < numTransactions; i++ { payloads <- aptos.TransactionBuildPayload{ Id: i, Type: aptos.TransactionSubmissionTypeSingle, Inner: payload, } } close(payloads) }() // Wait for all transactions to be processed for result := range results { if result.Err != nil { panic("Failed to submit and wait for transaction:" + result.Err.Error()) } } } // example This example shows you how to improve performance of the transaction submission // // Speed can be improved by locally handling the sequence number, gas price, and other factors func example(networkConfig aptos.NetworkConfig, numTransactions uint64) { println("Sending", numTransactions, "transactions Serially") startSerial := time.Now() sendManyTransactionsSerially(networkConfig, numTransactions) endSerial := time.Now() println("Serial:", time.Duration.Milliseconds(endSerial.Sub(startSerial)), "ms") println("Sending", numTransactions, "transactions Concurrently") startConcurrent := time.Now() sendManyTransactionsConcurrently(networkConfig, numTransactions) endConcurrent := time.Now() println("Concurrent:", time.Duration.Milliseconds(endConcurrent.Sub(startConcurrent)), "ms") println("Concurrent is", time.Duration.Milliseconds(endSerial.Sub(startSerial)-endConcurrent.Sub(startConcurrent)), "ms faster than Serial") } func main() { example(aptos.DevnetConfig, 100) } ``` # Go SDK - Binary Canonical Serialization (BCS) Format All transaction arguments for the Aptos Go SDK are encoded as bytes in Binary Canonical Serialization (BCS) format. This is the format the Aptos chain recognizes, with specific types (ex. Instead of an uint64 or big.Int, it uses types like `u64` or `u128`) You can directly use the BCS format to build transactions by specifying argument types explicitly like so: ```go accountBytes, err := bcs.Serialize(&bob.Address) if err != nil { panic("Failed to serialize bob's address:" + err.Error()) } amountBytes, err := bcs.SerializeU64(TransferAmount) if err != nil { panic("Failed to serialize transfer amount:" + err.Error()) } rawTxn, err := client.BuildTransaction(alice.AccountAddress(), aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ Module: aptos.ModuleId{ Address: aptos.AccountOne, Name: "aptos_account", }, Function: "transfer", ArgTypes: []aptos.TypeTag{}, Args: [][]byte{ accountBytes, amountBytes, }, }}, ) ``` You can learn more about BCS by exploring the [BCS GitHub repo](https://github.com/aptos-labs/bcs). # Go SDK - Multi-Agent Transactions Multi-agent transactions allow multiple accounts to participate in the logic of a Move contract. This can be used to require multiple parties agree to a transaction before executing or to use resources from multiple accounts. ## Writing Multi-Agent Transactions [Section titled “Writing Multi-Agent Transactions”](#writing-multi-agent-transactions) Creating and executing a multi-agent transaction follows a similar flow to the [regular transaction flow](/build/sdks/go-sdk/building-transactions), but with several notable differences. Note Instead of `client.BuildTransaction`, multi-agent and sponsored transactions use `client.BuildTransactionMultiAgent`. 1. Build the transaction by including aptos.AdditionalSigners with a list of each additional signers. Note Make sure to replace the `Function` field below with your entry function that requires multiple agents to sign. ```go transaction, err := client.BuildTransactionMultiAgent(alice.AccountAddress(), aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ // Replace module and function with your multi-agent function Module: aptos.ModuleId{ Address: aptos.AccountOne, Name: "aptos_account", }, Function: "transfer", ArgTypes: []aptos.TypeTag{}, Args: [][]byte{ accountBytes, amountBytes, }, }, AdditionalSigners: []aptos.AccountAddress{bob.AccountAddress()}, }) ``` 2. Sign once for each signer. You will combine these signatures in the next step. ```go aliceAuth, err := rawTxn.Sign(alice) if err != nil { panic("Failed to sign transaction as sender:" + err.Error()) } bobAuth, err := rawTxn.Sign(bob) if err != nil { panic("Failed to sign transaction as second signer:" + err.Error()) } ``` 3. Combine the signatures with the raw transaction to create a multi-agent signed transaction. ```go signedTxn, ok := rawTxn.ToMultiAgentSignedTransaction(aliceAuth, []crypto.AccountAuthenticator{bobAuth}) ``` 4. Submit the transaction by combining all agent signatures via the aptos.AdditionalSigners parameter. ```go submitResponse, err := client.SubmitTransaction(signedTxn) ``` 5. Lastly, wait for the transaction to resolve. ```go txnResult, err := client.WaitForTransaction(submitResponse.Hash) ``` ## Common Errors [Section titled “Common Errors”](#common-errors) `NUMBER_OF_SIGNER_ARGUMENTS_MISMATCH` - This happens when you are attempting to do multi-agent signing for a function which does not require that number of accounts. For example, if you try using multiple signatures for a `0x1::aptos_account::transfer` function - it only expects one address, and so produces an error when more than one is provided. # Go SDK - Simulating Transactions Simulating transactions allows you to preview the cost and effect of submitting a transaction without paying fees. You can use this to estimate fees, test a transaction, or to check what the output might be. To simulate a transaction, you must pass in a transaction and which account would be the signer: ```go // transfer_coin is an example of how to make a coin transfer transaction in the simplest possible way package main import ( "fmt" "github.com/aptos-labs/aptos-go-sdk" "github.com/aptos-labs/aptos-go-sdk/bcs" ) const FundAmount = 100_000_000 const TransferAmount = 1_000 // example This example shows you how to make an APT transfer transaction in the simplest possible way func example(networkConfig aptos.NetworkConfig) { // Create a client for Aptos client, err := aptos.NewClient(networkConfig) if err != nil { panic("Failed to create client:" + err.Error()) } // Create accounts locally for alice and bob alice, err := aptos.NewEd25519Account() if err != nil { panic("Failed to create alice:" + err.Error()) } bob, err := aptos.NewEd25519Account() if err != nil { panic("Failed to create bob:" + err.Error()) } fmt.Printf("\n=== Addresses ===\n") fmt.Printf("Alice: %s\n", alice.Address.String()) fmt.Printf("Bob:%s\n", bob.Address.String()) // Fund the sender with the faucet to create it on-chain err = client.Fund(alice.Address, FundAmount) if err != nil { panic("Failed to fund alice:" + err.Error()) } aliceBalance, err := client.AccountAPTBalance(alice.Address) if err != nil { panic("Failed to retrieve alice balance:" + err.Error()) } bobBalance, err := client.AccountAPTBalance(bob.Address) if err != nil { panic("Failed to retrieve bob balance:" + err.Error()) } fmt.Printf("\n=== Initial Balances ===\n") fmt.Printf("Alice: %d\n", aliceBalance) fmt.Printf("Bob:%d\n", bobBalance) // 1. Build transaction accountBytes, err := bcs.Serialize(&bob.Address) if err != nil { panic("Failed to serialize bob's address:" + err.Error()) } amountBytes, err := bcs.SerializeU64(TransferAmount) if err != nil { panic("Failed to serialize transfer amount:" + err.Error()) } rawTxn, err := client.BuildTransaction(alice.AccountAddress(), aptos.TransactionPayload{ Payload: &aptos.EntryFunction{ Module: aptos.ModuleId{ Address: aptos.AccountOne, Name: "aptos_account", }, Function: "transfer", ArgTypes: []aptos.TypeTag{}, Args: [][]byte{ accountBytes, amountBytes, }, }}, ) if err != nil { panic("Failed to build transaction:" + err.Error()) } // 2. Simulate transaction // This is useful for understanding how much the transaction will cost // and to ensure that the transaction is valid before sending it to the network // This is optional, but recommended simulationResult, err := client.SimulateTransaction(rawTxn, alice) if err != nil { panic("Failed to simulate transaction:" + err.Error()) } fmt.Printf("\n=== Simulation ===\n") fmt.Printf("Gas unit price: %d\n", simulationResult[0].GasUnitPrice) fmt.Printf("Gas used: %d\n", simulationResult[0].GasUsed) fmt.Printf("Total gas fee: %d\n", simulationResult[0].GasUsed*simulationResult[0].GasUnitPrice) fmt.Printf("Status: %s\n", simulationResult[0].VmStatus) } func main() { example(aptos.DevnetConfig) } ``` Look [here](/build/sdks/go-sdk/building-transactions) to see the full example of how to build, simulate, and submit a transaction. You can also learn how to simulate more advanced transactions by looking at the following guides: * [Sponsored Transactions](/build/sdks/go-sdk/building-transactions/sponsoring-transactions) * [Multi-Agent Transactions](/build/sdks/go-sdk/building-transactions/multi-agent-transactions) # Go SDK - Sponsoring Transactions Normally, the account that is executing a transaction pays for the gas fees. You can allow another account to cover those charges by sponsoring a transaction. This can be used to help manage fees from a central account when working with complicated smart contracts. ## How To Sponsor a Transaction [Section titled “How To Sponsor a Transaction”](#how-to-sponsor-a-transaction) 1. Build the transaction with the parameter FeePayer(). ```go rawTxn, err := client.BuildTransactionMultiAgent( alice.Address, aptos.TransactionPayload{ Payload: transferPayload, }, aptos.FeePayer(&sponsor.Address), ) ``` Note The `FeePayer()` function is used to specify the account that will pay the gas fees for the transaction. You can use `AccountZero` to indicate that the fee payer is not known ahead of time. 2. Sign the transaction with BOTH the sender and the feePayer. 1. Sign with the sender account using `rawTxn.Sign()`. 2. Sign with the sponsor account using `rawTxn.Sign()`. ```go aliceAuth, err := rawTxn.Sign(alice) if err != nil { panic("Failed to sign transaction as sender:" + err.Error()) } sponsorAuth, err := rawTxn.Sign(sponsor) if err != nil { panic("Failed to sign transaction as sponsor:" + err.Error()) } ``` 3. Submit the transaction by combining both signatures. ```go signedFeePayerTxn, ok = rawTxn.ToFeePayerSignedTransaction( aliceAuth, sponsorAuth, []crypto.AccountAuthenticator{}, ) if !ok { panic("Failed to build fee payer signed transaction") } // Submit and wait for it to complete submitResult, err = client.SubmitTransaction(signedFeePayerTxn) if err != nil { panic("Failed to submit transaction:" + err.Error()) } ``` 4. Wait for the transaction to execute. ```go // Wait for the transaction _, err = client.WaitForTransaction(txnHash) ``` ## Go Sponsored Transaction Code Sample [Section titled “Go Sponsored Transaction Code Sample”](#go-sponsored-transaction-code-sample) ```go // sponsored_transaction is an example of how to make a sponsored transaction in Aptos. package main import ( "fmt" "github.com/aptos-labs/aptos-go-sdk" "github.com/aptos-labs/aptos-go-sdk/crypto" ) const FundAmount = 100_000_000 const TransferAmount = 1_000 // example This example shows you how to make an APT transfer transaction in the simplest possible way func example(networkConfig aptos.NetworkConfig) { // Create a client for Aptos client, err := aptos.NewClient(networkConfig) if err != nil { panic("Failed to create client:" + err.Error()) } // Create accounts locally for alice and bob alice, err := aptos.NewEd25519Account() if err != nil { panic("Failed to create alice:" + err.Error()) } bob, err := aptos.NewEd25519Account() if err != nil { panic("Failed to create bob:" + err.Error()) } sponsor, err := aptos.NewEd25519Account() if err != nil { panic("Failed to create sponsor:" + err.Error()) } fmt.Printf("\n=== Addresses ===\n") fmt.Printf("Alice: %s\n", alice.Address.String()) fmt.Printf("Bob:%s\n", bob.Address.String()) fmt.Printf("Sponsor:%s\n", sponsor.Address.String()) // Fund the alice with the faucet to create it on-chain err = client.Fund(alice.Address, FundAmount) if err != nil { panic("Failed to fund alice:" + err.Error()) } // And the sponsor err = client.Fund(sponsor.Address, FundAmount) if err != nil { panic("Failed to fund sponsor:" + err.Error()) } aliceBalance, err := client.AccountAPTBalance(alice.Address) if err != nil { panic("Failed to retrieve alice balance:" + err.Error()) } bobBalance, err := client.AccountAPTBalance(bob.Address) if err != nil { panic("Failed to retrieve bob balance:" + err.Error()) } sponsorBalance, err := client.AccountAPTBalance(sponsor.Address) if err != nil { panic("Failed to retrieve sponsor balance:" + err.Error()) } fmt.Printf("\n=== Initial Balances ===\n") fmt.Printf("Alice: %d\n", aliceBalance) fmt.Printf("Bob: %d\n", bobBalance) fmt.Printf("Sponsor: %d\n", sponsorBalance) // Build transaction transferPayload, err := aptos.CoinTransferPayload(&aptos.AptosCoinTypeTag, bob.Address, TransferAmount) if err != nil { panic("Failed to build transfer payload:" + err.Error()) } rawTxn, err := client.BuildTransactionMultiAgent( alice.Address, aptos.TransactionPayload{ Payload: transferPayload, }, aptos.FeePayer(&sponsor.Address), ) if err != nil { panic("Failed to build transaction:" + err.Error()) } // Sign transaction aliceAuth, err := rawTxn.Sign(alice) if err != nil { panic("Failed to sign transaction as sender:" + err.Error()) } sponsorAuth, err := rawTxn.Sign(sponsor) if err != nil { panic("Failed to sign transaction as sponsor:" + err.Error()) } signedFeePayerTxn, ok := rawTxn.ToFeePayerSignedTransaction( aliceAuth, sponsorAuth, []crypto.AccountAuthenticator{}, ) if !ok { panic("Failed to build fee payer signed transaction") } // Submit and wait for it to complete submitResult, err := client.SubmitTransaction(signedFeePayerTxn) if err != nil { panic("Failed to submit transaction:" + err.Error()) } txnHash := submitResult.Hash println("Submitted transaction hash:", txnHash) // Wait for the transaction _, err = client.WaitForTransaction(txnHash) if err != nil { panic("Failed to wait for transaction:" + err.Error()) } aliceBalance, err = client.AccountAPTBalance(alice.Address) if err != nil { panic("Failed to retrieve alice balance:" + err.Error()) } bobBalance, err = client.AccountAPTBalance(bob.Address) if err != nil { panic("Failed to retrieve bob balance:" + err.Error()) } sponsorBalance, err = client.AccountAPTBalance(sponsor.Address) if err != nil { panic("Failed to retrieve sponsor balance:" + err.Error()) } fmt.Printf("\n=== Intermediate Balances ===\n") fmt.Printf("Alice: %d\n", aliceBalance) fmt.Printf("Bob: %d\n", bobBalance) fmt.Printf("Sponsor: %d\n", sponsorBalance) fmt.Printf("\n=== Now do it without knowing the signer ahead of time ===\n") rawTxn, err = client.BuildTransactionMultiAgent( alice.Address, aptos.TransactionPayload{ Payload: transferPayload, }, aptos.FeePayer(&aptos.AccountZero), // Note that the Address is 0x0, because we don't know the signer ) if err != nil { panic("Failed to build transaction:" + err.Error()) } // Alice signs the transaction, without knowing the sponsor aliceAuth, err = rawTxn.Sign(alice) if err != nil { panic("Failed to sign transaction as sender:" + err.Error()) } // The sponsor has to add themselves to the transaction to sign, note that this would likely be on a different // server ok = rawTxn.SetFeePayer(sponsor.Address) if !ok { panic("Failed to set fee payer") } sponsorAuth, err = rawTxn.Sign(sponsor) if err != nil { panic("Failed to sign transaction as sponsor:" + err.Error()) } signedFeePayerTxn, ok = rawTxn.ToFeePayerSignedTransaction( aliceAuth, sponsorAuth, []crypto.AccountAuthenticator{}, ) if !ok { panic("Failed to build fee payer signed transaction") } // Submit and wait for it to complete submitResult, err = client.SubmitTransaction(signedFeePayerTxn) if err != nil { panic("Failed to submit transaction:" + err.Error()) } txnHash = submitResult.Hash println("Submitted transaction hash:", txnHash) // Wait for the transaction _, err = client.WaitForTransaction(txnHash) if err != nil { panic("Failed to wait for transaction:" + err.Error()) } aliceBalance, err = client.AccountAPTBalance(alice.Address) if err != nil { panic("Failed to retrieve alice balance:" + err.Error()) } bobBalance, err = client.AccountAPTBalance(bob.Address) if err != nil { panic("Failed to retrieve bob balance:" + err.Error()) } sponsorBalance, err = client.AccountAPTBalance(sponsor.Address) if err != nil { panic("Failed to retrieve sponsor balance:" + err.Error()) } fmt.Printf("\n=== Final Balances ===\n") fmt.Printf("Alice: %d\n", aliceBalance) fmt.Printf("Bob: %d\n", bobBalance) fmt.Printf("Sponsor: %d\n", sponsorBalance) } func main() { example(aptos.DevnetConfig) } ``` # Go SDK - Fetch Data You can use the `Aptos` client to get on-chain data using a variety of helper functions. Specifically, many of the functions listed in the [reference docs](https://pkg.go.dev/github.com/aptos-labs/aptos-go-sdk) will retrieve data from on-chain e.g. `Account`, `AccountResources`, `Transactions`. Here’s an example showing how to fetch common data you may need in your application: ```go client, err := aptos.NewClient(aptos.DevnetConfig) if err != nil { panic("Failed to create client:" + err.Error()) } address := aptos.AccountAddress{} err := address.ParseStringRelaxed("0x123") if err != nil { panic("Failed to parse address:" + err.Error()) } accountInfo, err := client.Account(address) resources, err := client.AccountResources(address) transactions, err := client.Transactions() ``` Note Many have optional inputs such as `ledgerVersion` to specify which ledger version to query state. The `Aptos` client can out of the box query both network data from [fullnodes](https://api.mainnet.aptoslabs.com/v1/spec#/) and the [Indexer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) API which contains aggregated and enriched data. If you want to use a custom query for Indexer API data, you can use `client.QueryIndexer()` like so: ```go var out []CoinBalance var q struct { Current_coin_balances []struct { CoinType string `graphql:"coin_type"` Amount uint64 OwnerAddress string `graphql:"owner_address"` } `graphql:"current_coin_balances(where: {owner_address: {_eq: $address}})"` } variables := map[string]any{ "address": address.StringLong(), } err := ic.Query(&q, variables) if err != nil { return nil, err } for _, coin := range q.Current_coin_balances { out = append(out, CoinBalance{ CoinType: coin.CoinType, Amount: coin.Amount, }) } ``` Note Note that all values in the GraphQL must be capitalized and CamelCased. To convert to direct database field names, use the `graphql` tag. ## Using Move View Functions [Section titled “Using Move View Functions”](#using-move-view-functions) You can call view functions which return custom data from on-chain by using `client.View`. For example, you can look up the network you are using with the `chain_id` view function: ```go viewResponse, err := client.View(&aptos.ViewPayload { Module: aptos.ModuleId{Address: aptos.AccountAddress{}, Name: "chain_id"}, Function: "get", ArgTypes: []aptos.TypeTag{}, Args: [][]byte{}, ) chainId := viewResponse[0] ``` ## Ensuring Fresh Indexer Data [Section titled “Ensuring Fresh Indexer Data”](#ensuring-fresh-indexer-data) Behind the scenes, some requests use the [Indexer API](/build/indexer) to access data which has been processed or aggregated. That extra parsing can take a bit of time, so the data may lag slightly behind the latest ledger. If you want to ensure that the data is fresh, you can wait on a specific version from the indexer. ```go // Wait on processorName to reach version 12345 err := client.WaitOnIndexer("processorName", 12345) ``` # Go SDK - Examples For sample code which explains the core concepts of how to use the SDK, see: * [Fetching Data](/build/sdks/go-sdk/fetch-data-via-sdk) * [Building, Simulating, and Submitting Transactions](/build/sdks/go-sdk/building-transactions) Below are additional resources which may be more suited for your individual use case. ## Code Snippets [Section titled “Code Snippets”](#code-snippets) The [`examples` folder](https://github.com/aptos-labs/aptos-go-sdk/tree/main/examples) in the SDK repo has many code snippets you can customize to your needs. ### How to run examples [Section titled “How to run examples”](#how-to-run-examples) To run one of the example scripts: 1. Clone the ```shellscript git clone https://github.com/aptos-labs/aptos-go-sdk.git ``` 2. From the top-level of the package, install all dependencies. ```shellscript go install ``` 3. Build the package. ```shellscript go build ./... ``` 4. Run an example ```shellscript go run examples/transfer_coin/main.go ``` ## Helpful Reference Code [Section titled “Helpful Reference Code”](#helpful-reference-code) * [SDK source code](https://github.com/aptos-labs/aptos-go-sdk/tree/main) - This has in-line comments explaining what each function does. * [SDK reference docs](https://pkg.go.dev/github.com/aptos-labs/aptos-go-sdk) - These are another way to view the in-line documentation with built-in search. # Python SDK Aptos provides a lightly maintained official Python SDK. It is available on [PyPi](https://pypi.org/project/aptos-sdk/) with the source code in the [aptos-python-sdk GitHub repository](https://github.com/aptos-labs/aptos-python-sdk). Much of the functionality of the Python SDK mirrors the [Typescript SDK](/build/sdks/ts-sdk). The primary purpose of the Python SDK is to help Python developers to quickly become familiar with Aptos and as an accompaniment to Aptos tutorials. ## Installing Python SDK [Section titled “Installing Python SDK”](#installing-python-sdk) The Python SDK can either be installed via `pip`, from source, or embedded: ### Install with pip [Section titled “Install with pip”](#install-with-pip) To install via `pip`: ```shellscript pip3 install aptos-sdk ``` The `aptos-sdk` will be installed in the local site packages directory. For example, on macOS, you will find the `aptos-sdk` in the `~/Library/Python/3.8/lib/python/site-packages/aptos_sdk` directory. ### Install from the source code [Section titled “Install from the source code”](#install-from-the-source-code) To install from source: ```shellscript git clone https://github.com/aptos-labs/aptos-python-sdk pip3 install . --user ``` ### Install by embedding [Section titled “Install by embedding”](#install-by-embedding) To embed the Python SDK into your existing Python project: ```shellscript cd /path/to/python/project cp -r /path/to/aptos-python-sdk aptos-sdk ``` ## Using the Python SDK [Section titled “Using the Python SDK”](#using-the-python-sdk) See the [Developer Tutorials](/build/guides) for code examples showing how to use the Python SDK. # Rust SDK ## Installation [Section titled “Installation”](#installation) Aptos provides an official lightly supported Rust SDK in the [Aptos-core GitHub](https://github.com/aptos-labs/aptos-core/tree/main/sdk) repository. To use the Rust SDK, add the following dependency and patches on the git repo directly in your `Cargo.toml`, like this: ```toml [dependencies] aptos-sdk = { git = "https://github.com/aptos-labs/aptos-core", branch = "devnet" } [patch.crates-io] merlin = { git = "https://github.com/aptos-labs/merlin" } x25519-dalek = { git = "https://github.com/aptos-labs/x25519-dalek", branch = "zeroize_v1" } ``` You must also create a `.cargo/config.toml` file with this content: ```toml [build] rustflags = ["--cfg", "tokio_unstable"] ``` The source code for the official Rust SDK is available in the [aptos-core GitHub repository](https://github.com/aptos-labs/aptos-core/tree/main/sdk). ## Using Rust SDK [Section titled “Using Rust SDK”](#using-rust-sdk) See the [Developer Tutorials](/build/guides) for code examples showing how to use the Rust SDK. # TypeScript SDK [![Github Repo Stars](https://img.shields.io/github/stars/aptos-labs/aptos-ts-sdk)](https://github.com/aptos-labs/aptos-ts-sdk) [![NPM Version](https://img.shields.io/npm/v/%40aptos-labs%2Fts-sdk)](https://www.npmjs.com/package/@aptos-labs/ts-sdk) [![Node Version](https://img.shields.io/node/v/%40aptos-labs%2Fts-sdk)](https://www.npmjs.com/package/@aptos-labs/ts-sdk) [![NPM bundle size](https://img.shields.io/bundlephobia/min/%40aptos-labs/ts-sdk)](https://www.npmjs.com/package/@aptos-labs/ts-sdk) [![Static Badge](https://img.shields.io/badge/SDK_Reference-Docs)](https://aptos-labs.github.io/aptos-ts-sdk/@aptos-labs/ts-sdk-latest) The TypeScript SDK allows you to connect, explore, and interact on the Aptos blockchain. You can use it to request data, send transactions, set up test environments, and more! ```shellscript npm i @aptos-labs/ts-sdk ``` ## Examples [Section titled “Examples”](#examples) [Quickstart ](/build/sdks/ts-sdk/quickstart)See the quickstart to get a working demo in < 5 minutes [20+ Examples ](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/examples/typescript)Explore all of the TypeScript examples provided in the SDK repository [Comprehensive Tests ](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/tests/e2e)See end to end tests which demonstrate how to use each feature of the SDK ### Transfer APT in 10 lines or less [Section titled “Transfer APT in 10 lines or less”](#transfer-apt-in-10-lines-or-less) simple\_transfer.ts ```ts 76 collapsed lines /* eslint-disable no-console */ /** * This example shows how to use the Aptos client to create accounts, fund them, and transfer between them. */ import { Account, AccountAddress, Aptos, AptosConfig, Network, NetworkToNetworkName } from "@aptos-labs/ts-sdk"; // TODO: There currently isn't a way to use the APTOS_COIN in the COIN_STORE due to a regex const APTOS_COIN = "0x1::aptos_coin::AptosCoin"; const COIN_STORE = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"; const ALICE_INITIAL_BALANCE = 100_000_000; const BOB_INITIAL_BALANCE = 100; const TRANSFER_AMOUNT = 100; // Default to devnet, but allow for overriding const APTOS_NETWORK: Network = NetworkToNetworkName[process.env.APTOS_NETWORK ?? Network.DEVNET]; /** * Prints the balance of an account * @param aptos * @param name * @param address * @returns {Promise<*>} * */ const balance = async (aptos: Aptos, name: string, address: AccountAddress) => { type Coin = { coin: { value: string } }; const resource = await aptos.getAccountResource({ accountAddress: address, resourceType: COIN_STORE, }); const amount = Number(resource.coin.value); console.log(`${name}'s balance is: ${amount}`); return amount; }; const example = async () => { console.log("This example will create two accounts (Alice and Bob), fund them, and transfer between them."); // Setup the client const config = new AptosConfig({ network: APTOS_NETWORK }); const aptos = new Aptos(config); // Create two accounts const alice = Account.generate(); const bob = Account.generate(); console.log("=== Addresses ===\n"); console.log(`Alice's address is: ${alice.accountAddress}`); console.log(`Bob's address is: ${bob.accountAddress}`); // Fund the accounts console.log("\n=== Funding accounts ===\n"); const aliceFundTxn = await aptos.faucet.fundAccount({ accountAddress: alice.accountAddress, amount: ALICE_INITIAL_BALANCE, }); console.log("Alice's fund transaction: ", aliceFundTxn); const bobFundTxn = await aptos.faucet.fundAccount({ accountAddress: bob.accountAddress, amount: BOB_INITIAL_BALANCE, }); console.log("Bob's fund transaction: ", bobFundTxn); // Show the balances console.log("\n=== Balances ===\n"); const aliceBalance = await balance(aptos, "Alice", alice.accountAddress); const bobBalance = await balance(aptos, "Bob", bob.accountAddress); if (aliceBalance !== ALICE_INITIAL_BALANCE) throw new Error("Alice's balance is incorrect"); if (bobBalance !== BOB_INITIAL_BALANCE) throw new Error("Bob's balance is incorrect"); // Transfer between users const txn = await aptos.transaction.build.simple({ sender: alice.accountAddress, data: { function: "0x1::coin::transfer", typeArguments: [APTOS_COIN], functionArguments: [bob.accountAddress, TRANSFER_AMOUNT], }, }); console.log("\n=== Transfer transaction ===\n"); const committedTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn }); await aptos.waitForTransaction({ transactionHash: committedTxn.hash }); console.log(`Committed transaction: ${committedTxn.hash}`); 15 collapsed lines console.log("\n=== Balances after transfer ===\n"); const newAliceBalance = await balance(aptos, "Alice", alice.accountAddress); const newBobBalance = await balance(aptos, "Bob", bob.accountAddress); // Bob should have the transfer amount if (newBobBalance !== TRANSFER_AMOUNT + BOB_INITIAL_BALANCE) throw new Error("Bob's balance after transfer is incorrect"); // Alice should have the remainder minus gas if (newAliceBalance >= ALICE_INITIAL_BALANCE - TRANSFER_AMOUNT) throw new Error("Alice's balance after transfer is incorrect"); }; example(); ``` # Creating and Managing Accounts There are several ways to generate account credentials using the TypeScript SDK. You can use: * `Account.generate()` * `Account.fromPrivateKey()` * `Account.fromDerivationPath()` `Account.generate()` is the most commonly used method to create keys for a new account. It defaults to `ED25519` key encodings, but you can also manually specify which signing scheme you would prefer like so: ```typescript const account = Account.generate(); // defaults to Legacy Ed25519 const account = Account.generate({ scheme: SigningSchemeInput.Secp256k1Ecdsa }); // Single Sender Secp256k1 const account = Account.generate({ scheme: SigningSchemeInput.Ed25519, legacy: false, }); // Single Sender Ed25519 ``` Note Following [AIP-55](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-55.md) the SDK supports `Legacy` and `Unified` authentications. `Legacy` includes `ED25519` and `MultiED25519` and `Unified` includes `SingleSender` and `MultiSender` authenticators. Once you have generated credentials, you **must** fund it for the network to know it exists. In localnet / devnet this can be done with a faucet by running the following command: ```typescript const transaction = await aptos.fundAccount({ accountAddress: account.accountAddress, amount: 100, }); ``` For testnet you can use the mint page [here](/network/faucet). ## Other Ways To Represent Accounts [Section titled “Other Ways To Represent Accounts”](#other-ways-to-represent-accounts) If you have a private key, or equivalent representation, you can use that to create an `Account` object to manage those credentials while using the TypeScript SDK. Here are several examples that show how to do so with specific encoding schemes. ### Derive an account from private key [Section titled “Derive an account from private key”](#derive-an-account-from-private-key) The SDK supports deriving an account from a private key with `fromPrivateKey()` static method. In addition, this method supports deriving an account from a private key and account address. This method uses a local calculation and therefore is used to derive an `Account` that has not had its authentication key rotated. ```typescript // to derive an account with a legacy Ed25519 key scheme const privateKey = new Ed25519PrivateKey(privateKeyBytes); const account = Account.fromPrivateKey({ privateKey }); // to derive an account with a Single Sender Ed25519 key scheme const privateKey = new Ed25519PrivateKey(privateKeyBytes); const account = Account.fromPrivateKey({ privateKey, legacy: false }); // to derive an account with a Single Sender Secp256k1 key scheme const privateKey = new Secp256k1PrivateKey(privateKeyBytes); const account = Account.fromPrivateKey({ privateKey }); // to derive an account with a private key and account address const privateKey = new Ed25519PrivateKey(privateKeyBytes); const address = AccountAddress.from(address); const account = Account.fromPrivateKey({ privateKey, address }); ``` ### Derive an account from derivation path [Section titled “Derive an account from derivation path”](#derive-an-account-from-derivation-path) The SDK supports deriving an account from derivation path with `fromDerivationPath()` static method. ```typescript // to derive an account with a legacy Ed25519 key scheme const { mnemonic, address, path } = wallet; const account = Account.fromDerivationPath({ path, mnemonic, scheme: SigningSchemeInput.Ed25519, }); // to derive an account with a Single Sender Ed25519 key scheme const { mnemonic, address, path } = wallet; const account = Account.fromDerivationPath({ path, mnemonic, scheme: SigningSchemeInput.Ed25519, legacy: false, }); // to derive an account with a Single Sender Secp256k1 key scheme const { mnemonic, address, path } = wallet; const account = Account.fromDerivationPath({ path, mnemonic, scheme: SigningSchemeInput.Secp256k1Ecdsa, }); ``` # Account Abstraction Account Abstraction (AA) on Aptos **enables custom transaction authentication logic through Move modules**, allowing accounts to define their own rules beyond native cryptographic schemes. **Note:** This is currently only live on testnet as of July 17, 2025. ## Core Concepts [Section titled “Core Concepts”](#core-concepts) ### `FunctionInfo` [Section titled “FunctionInfo”](#functioninfo) A struct defining the authentication function to be invoked. ```move struct FunctionInfo has copy, drop, store { module_address: address, module_name: String, function_name: String } ``` The authentication function is responsible for defining the authentication logic using Move. It should return a signer if authentication is successful, otherwise it aborts the transaction. The only accepted authentication function signature that can be added onto an account is the following: ```move // The function can return a signer if authentication is successful, otherwise it aborts the transaction. public fun authenticate(account: signer, auth_data: AbstractionAuthData): signer; ``` **Example (Move)** ```move module deployer::authenticator { use aptos_framework::auth_data::{AbstractionAuthData}; public fun authenticate(account: signer, auth_data: AbstractionAuthData): signer { // ... authentication logic ... account } } ``` **Example (Typescript)** ```typescript const authenticationFunction = `${deployer}::authenticator::authenticate`; ``` ### `AbstractionAuthData` [Section titled “AbstractionAuthData”](#abstractionauthdata) An enum variant defining the authentication data to be passed to the authentication function. It contains: * `digest`: The sha256 hash of the signing message. * `authenticator`: Abstract bytes that will be passed to the authentication function that will be used to verify the transaction. ```move enum AbstractionAuthData has copy, drop { V1 { digest: vector, // SHA3-256 hash of the signing message authenticator: vector // Custom auth data (e.g., signatures) }, } ``` **Why is the `digest` important?** The `digest` is checked by the MoveVM to ensure that the signing message of the transaction being submitted is the same as the one presented in the `AbstractionAuthData`. This is important because it allows the authentication function to verify signatures with respect to the correct transaction. For example, if you want to permit a public key to sign transactions on behalf of the user, you can permit the public key to sign a transaction with a specific payload. However, if a malicious user sends a signature for the correct public key but a different payload from the `digest`, the signature will not be valid. **Example (Move)** This example demonstrates a simple authentication logic that checks if the authenticator is equal to `"hello world"`. ```move module deployer::hello_world_authenticator { use aptos_framework::auth_data::{Self, AbstractionAuthData}; public fun authenticate( account: signer, auth_data: AbstractionAuthData ): signer { let authenticator = *auth_data::authenticator(&auth_data); assert!(authenticator == b"hello world", 1); account } } ``` **Example (Typescript)** ```typescript const abstractedAccount = new AbstractedAccount({ /** * The result of the signer function will be available as the `authenticator` field in the `AbstractionAuthData` enum variant. */ signer: () => new TextEncoder().encode("hello world"), /** * The authentication function to be invoked. */ authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`, }); ``` ## Minimal Step-by-Step Guide [Section titled “Minimal Step-by-Step Guide”](#minimal-step-by-step-guide) 1. 1. Deploy Authentication Module In this example, we will deploy the `hello_world_authenticator` module. The `authenticate` function takes an `AbstractionAuthData` and returns a `signer` if the authentication is successful, otherwise it aborts the transaction. The authentication logic will only allow transactions that have an authenticator equal to `"hello world"`. ```move module deployer::hello_world_authenticator { use aptos_framework::auth_data::{Self, AbstractionAuthData}; use std::bcs; public fun authenticate( account: signer, auth_data: AbstractionAuthData ): signer { let authenticator = *auth_data::authenticator(&auth_data); assert!(authenticator == b"hello world", 1); account } } ``` To deploy the module, you can use the following commands from the [Aptos CLI](/build/cli). We assume that you already have set up a workspace with `aptos init` and declared the named addresses in your `Move.toml` file. ```shellscript aptos move publish --named-addresses deployer=0x1234567890123456789012345678901234567890 ``` 2. 2. Setup your Environment Once deployed, you can setup your environment. In this example, we will use Devnet and create an account named `alice` which will act as our user. ```typescript const DEPLOYER = "0x" const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); const alice = Account.generate(); const authenticationFunctionInfo = `${deployer}::hello_world_authenticator::authenticate`; ``` 3. 3. (Optional) Check if Account Abstraction is Enabled Before you ask them to enable account abstraction, you can check if the account has account abstraction enabled by calling the `isAccountAbstractionEnabled` function. This will return a boolean value indicating if the account has account abstraction enabled. ```typescript const accountAbstractionStatus = await aptos.abstraction.isAccountAbstractionEnabled({ accountAddress: alice.accountAddress, authenticationFunction, }); console.log("Account Abstraction status: ", accountAbstractionStatus); ``` 4. 4. Enable the Authentication Function Assuming that the account does not have account abstraction enabled, you need to enable the authentication function for the account. This can be done by calling the `enableAccountAbstractionTransaction` function. This creates a raw transaction that needs to be signed and submitted to the network. In this example, `alice` will be the account that will be enabled. ```typescript const transaction = aptos.abstraction.enableAccountAbstractionTransaction({ accountAddress: alice.accountAddress, authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`, }); const pendingTransaction = await aptos.signAndSubmitTransaction({ transaction, signer: alice.signer, }); await aptos.waitForTransaction({ hash: pendingTransaction.hash }); console.log("Account Abstraction enabled for account: ", alice.accountAddress); ``` **Wallet Adapter Example** Note If you are using the wallet adapter, you can use the `signTransaction` function to sign the transaction before submitting it to the network. ```tsx export default function useEnableAbstraction() { const { account, signTransaction } = useWallet(); return { enableAbstraction: async () => { if (!account) return; // Note: The Aptos client must be defined somewhere in the application. const transaction = aptos.abstraction.enableAccountAbstractionTransaction({ accountAddress: account.address, authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`, }); const senderAuthenticator = await signTransaction(txn); const pendingTxn = await aptos.transaction.submit.simple({ transaction: txn, senderAuthenticator, }); return await aptos.waitForTransaction({ hash: pendingTxn.hash }); } } } ``` 5. 5. Create an Abstracted Account Once the authentication function is enabled, you can create an abstracted account object for signing transactions. You must provide the authentication function that will be used to verify the transaction and a `signer` function that will be used to sign the transaction. The `signer` function is responsible for generating the authenticator that will be passed to the authentication function. ```typescript const abstractedAccount = new AbstractedAccount({ accountAddress: alice.accountAddress, signer: () => new TextEncoder().encode("hello world"), authenticationFunction: `${deployer}::hello_world_authenticator::authenticate`, }); ``` 6. 6. Sign and Submit a Transaction using the Abstracted Account Once you have created the abstracted account, you can use it to sign transactions normally. It is important that the `sender` field in the transaction is the same as the abstracted account’s address. ```typescript const coinTransferTransaction = await aptos.transaction.build.simple({ sender: abstractedAccount.accountAddress, data: { function: "0x1::coin::transfer", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [alice.accountAddress, 100], }, }); const pendingCoinTransferTransaction = await aptos.transaction.signAndSubmitTransaction({ transaction: coinTransferTransaction, signer: abstractedAccount, }); await aptos.waitForTransaction({ transactionHash: pendingCoinTransferTransaction.hash }); console.log("Coin transfer transaction submitted! ", pendingCoinTransferTransaction.hash); ``` 7. 7. Conclusion To verify that you have successfully sign and submitted the transaction using the abstracted account, you can use the explorer to check the transaction. If the transaction signature contains a `function_info` and `auth_data` field, it means you successfully used account abstraction! The full E2E demo can be found [here](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/public_key_authenticator_account_abstraction.ts). ![Transaction Signature](https://i.imgur.com/HZylFnc.png) ## Complex Step-by-Step Guide [Section titled “Complex Step-by-Step Guide”](#complex-step-by-step-guide) Now that you have a basic understanding of how account abstraction works, let’s dive into a more complex example. In this example, we will create an authenticator that allows users to permit certain public keys to sign transactions on behalf of the abstracted account. 1. 1. Create an Authenticator module We will deploy the `public_key_authenticator` module that does two things: * Allow users to permit and/or revoke public keys from signing on behalf of the user. * Allow users to authenticate on behalf of somebody else using account abstraction. ```move module deployer::public_key_authenticator { use std::signer; use aptos_std::smart_table::{Self, SmartTable}; use aptos_std::ed25519::{ Self, new_signature_from_bytes, new_unvalidated_public_key_from_bytes, unvalidated_public_key_to_bytes }; use aptos_framework::bcs_stream::{Self, deserialize_u8}; use aptos_framework::auth_data::{Self, AbstractionAuthData}; // ====== Error Codes ====== // const EINVALID_PUBLIC_KEY: u64 = 0x20000; const EPUBLIC_KEY_NOT_PERMITTED: u64 = 0x20001; const EENTRY_ALREADY_EXISTS: u64 = 0x20002; const ENO_PERMISSIONS: u64 = 0x20003; const EINVALID_SIGNATURE: u64 = 0x20004; // ====== Data Structures ====== // struct PublicKeyPermissions has key { public_key_table: SmartTable, bool>, } // ====== Authenticator ====== // public fun authenticate( account: signer, auth_data: AbstractionAuthData ): signer acquires PublicKeyPermissions { let account_addr = signer::address_of(&account); assert!(exists(account_addr), ENO_PERMISSIONS); let permissions = borrow_global(account_addr); // Extract the public key and signature from the authenticator let authenticator = *auth_data::authenticator(&auth_data); let stream = bcs_stream::new(authenticator); let public_key = new_unvalidated_public_key_from_bytes( bcs_stream::deserialize_vector(&mut stream, |x| deserialize_u8(x)) ); let signature = new_signature_from_bytes( bcs_stream::deserialize_vector(&mut stream, |x| deserialize_u8(x)) ); // Check if the public key is permitted assert!(smart_table::contains(&permissions.public_key_table, unvalidated_public_key_to_bytes(&public_key)), EPUBLIC_KEY_NOT_PERMITTED); // Verify the signature let digest = *auth_data::digest(&auth_data); assert!(ed25519::signature_verify_strict(&signature, &public_key, digest), EINVALID_SIGNATURE); account } // ====== Core Functionality ====== // public entry fun permit_public_key( signer: &signer, public_key: vector ) acquires PublicKeyPermissions { let account_addr = signer::address_of(signer); assert!(std::vector::length(&public_key) == 32, EINVALID_PUBLIC_KEY); if (!exists(account_addr)) { move_to(signer, PublicKeyPermissions { public_key_table: smart_table::new(), }); }; let permissions = borrow_global_mut(account_addr); assert!( !smart_table::contains(&permissions.public_key_table, public_key), EENTRY_ALREADY_EXISTS ); smart_table::add(&mut permissions.public_key_table, public_key, true); } public entry fun revoke_public_key( signer: &signer, public_key: vector ) acquires PublicKeyPermissions { let account_addr = signer::address_of(signer); assert!(exists(account_addr), ENO_PERMISSIONS); let permissions = borrow_global_mut(account_addr); smart_table::remove(&mut permissions.public_key_table, public_key); } } ``` Let’s break down the module… **Storing Public Keys** The `PublicKeyPermissions` struct is a key that contains a `SmartTable` of public keys that determines whether a public key is permitted to sign transactions on behalf of the user. ```move module deployer::public_key_authenticator { // ... struct PublicKeyPermissions has key { public_key_table: SmartTable, bool>, } } ``` **Permitting and Revoking Public Keys** We define two entry functions to permit and revoke public keys. These functions are used to add and remove public keys from the `PublicKeyPermissions` struct. ```move module deployer::public_key_authenticator { // ... public entry fun permit_public_key( signer: &signer, public_key: vector ) acquires PublicKeyPermissions { let account_addr = signer::address_of(signer); assert!(std::vector::length(&public_key) == 32, EINVALID_PUBLIC_KEY); if (!exists(account_addr)) { move_to(signer, PublicKeyPermissions { public_key_table: smart_table::new(), }); }; let permissions = borrow_global_mut(account_addr); assert!( !smart_table::contains(&permissions.public_key_table, public_key), EENTRY_ALREADY_EXISTS ); smart_table::add(&mut permissions.public_key_table, public_key, true); } public entry fun revoke_public_key( signer: &signer, public_key: vector ) acquires PublicKeyPermissions { let account_addr = signer::address_of(signer); assert!(exists(account_addr), ENO_PERMISSIONS); let permissions = borrow_global_mut(account_addr); smart_table::remove(&mut permissions.public_key_table, public_key); } } ``` **Authenticating on behalf of somebody else** The `authenticate` function is the main function that allows users to authenticate on behalf of somebody else using account abstraction. The `authenticator` will contain the **public key** and a **signature** of the user. We will verify that the public key is permitted and that the signature is valid. The signature is the result of signing the `digest`. The `digest` is the sha256 hash of the **signing message** which contains information about the transaction. By signing the `digest`, we confirm that the user has approved the specific transaction that was submitted. ```move module deployer::public_key_authenticator { // ... public fun authenticate( account: signer, auth_data: AbstractionAuthData ): signer acquires PublicKeyPermissions { let account_addr = signer::address_of(&account); assert!(exists(account_addr), ENO_PERMISSIONS); let permissions = borrow_global(account_addr); // Extract the public key and signature from the authenticator let authenticator = *auth_data::authenticator(&auth_data); let stream = bcs_stream::new(authenticator); let public_key = new_unvalidated_public_key_from_bytes( bcs_stream::deserialize_vector(&mut stream, |x| deserialize_u8(x)) ); let signature = new_signature_from_bytes( bcs_stream::deserialize_vector(&mut stream, |x| deserialize_u8(x)) ); // Check if the public key is permitted assert!(smart_table::contains(&permissions.public_key_table, unvalidated_public_key_to_bytes(&public_key)), EPUBLIC_KEY_NOT_PERMITTED); // Verify the signature let digest = *auth_data::digest(&auth_data); assert!(ed25519::signature_verify_strict(&signature, &public_key, digest), EINVALID_SIGNATURE); account } } ``` To deploy the module, you can use the following commands from the [Aptos CLI](/build/cli). We assume that you already have set up a workspace with `aptos init` and declared the named addresses in your `Move.toml` file. ```shellscript aptos move publish --named-addresses deployer=0x1234567890123456789012345678901234567890 ``` 2. 2. Setup your Environment Once deployed, you can setup your environment. In this example, we will use Devnet and create an account named `alice` as the user that will be authenticated on behalf of and `bob` as the user that will be permitted to sign transactions on behalf of `alice`. ```typescript const DEPLOYER = "0x" const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); const alice = Account.generate(); const bob = Account.generate(); const authenticationFunctionInfo = `${deployer}::public_key_authenticator::authenticate`; ``` 3. 3. (Optional) Check if Account Abstraction is Enabled Before we enable the authentication function, we can check if the account has account abstraction enabled by calling the `isAccountAbstractionEnabled` function. This will return a boolean value indicating if the account has account abstraction enabled. ```typescript const accountAbstractionStatus = await aptos.abstraction.isAccountAbstractionEnabled({ accountAddress: alice.accountAddress, authenticationFunction, }); console.log("Account Abstraction status: ", accountAbstractionStatus); ``` 4. 4. Enable the Authentication Function Assuming that the account does not have account abstraction enabled, we need to enable the authentication function for the account. This can be done by calling the `enableAccountAbstractionTransaction` function. This creates a raw transaction that needs to be signed and submitted to the network. In this example, `alice` will be the account that will be enabled. ```typescript const transaction = await aptos.abstraction.enableAccountAbstractionTransaction({ accountAddress: alice.accountAddress, authenticationFunction, }); const pendingTransaction = await aptos.signAndSubmitTransaction({ transaction, signer: alice.signer, }); await aptos.waitForTransaction({ hash: pendingTransaction.hash }); console.log("Account Abstraction enabled for account: ", alice.accountAddress); ``` 5. 5. Permit Bob’s Public Key Now that we have enabled the authentication function, we can permit `bob`’s public key to sign transactions on behalf of `alice`. ```typescript const enableBobPublicKeyTransaction = await aptos.transaction.build.simple({ sender: alice.accountAddress, data: { function: `${alice.accountAddress}::public_key_authenticator::permit_public_key`, typeArguments: [], functionArguments: [bob.publicKey.toUint8Array()], }, }); const pendingEnableBobPublicKeyTransaction = await aptos.signAndSubmitTransaction({ signer: alice, transaction: enableBobPublicKeyTransaction, }); await aptos.waitForTransaction({ hash: pendingEnableBobPublicKeyTransaction.hash }); console.log(`Enable Bob's public key transaction hash: ${pendingEnableBobPublicKeyTransaction.hash}`); ``` 6. 6. Create an Abstracted Account Now that we have permitted `bob`’s public key, we can create an abstracted account that will be used to sign transactions on behalf of `alice`. **Notice that the `signer` function uses `bob`’s signer.** ```typescript const abstractedAccount = new AbstractedAccount({ accountAddress: alice.accountAddress, signer: (digest) => { const serializer = new Serializer(); bob.publicKey.serialize(serializer); bob.sign(digest).serialize(serializer); return serializer.toUint8Array(); }, authenticationFunction, }); ``` 7. 7. Sign and Submit a Transaction using the Abstracted Account Now that we have created the abstracted account, we can use it to sign transactions normally. It is important that the `sender` field in the transaction is the same as the abstracted account’s address. ```typescript const coinTransferTransaction = new aptos.transaction.build.simple({ sender: abstractedAccount.accountAddress, data: { function: "0x1::coin::transfer", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [alice.accountAddress, 100], }, }); const pendingCoinTransferTransaction = await aptos.transaction.signAndSubmitTransaction({ transaction: coinTransferTransaction, signer: abstractedAccount, }); await aptos.waitForTransaction({ hash: pendingCoinTransferTransaction.hash }); console.log("Coin transfer transaction submitted! ", pendingCoinTransferTransaction.hash); ``` 8. 8. Conclusion To verify that you have successfully sign and submitted the transaction using the abstracted account, you can use the explorer to check the transaction. If the transaction signature contains a `function_info` and `auth_data` field, it means you successfully used account abstraction! The full E2E demo can be found [here](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/public_key_authenticator_account_abstraction.ts) ![Transaction Signature](https://i.imgur.com/3U40YSb.png) ## Management Operations [Section titled “Management Operations”](#management-operations) If you want to disable account abstraction for an account, you can use the `disableAccountAbstractionTransaction`. If you do not specify an authentication function, the transaction will disable all authentication functions for the account. ```typescript const transaction = aptos.abstraction.disableAccountAbstractionTransaction({ accountAddress: alice.accountAddress, /** * The authentication function to be disabled. If left `undefined`, all authentication functions will be disabled. */ authenticationFunction, }); ``` ## Application User Experience [Section titled “Application User Experience”](#application-user-experience) Applications that want to leverage account abstraction will want to provide a user experience that allows users to check if the account has account abstraction enabled, and to enable it, if it is not enabled. Below is a diagram of the UX flow for enabling account abstraction. ![Account Abstraction UX](https://i.imgur.com/1xcrFjG.png) # Derivable Account Abstraction [Derivable Account Abstraction (DAA)](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-113.md) is a standard for account abstraction that enables custom authentication schemes by registering a `derivable_authentication_function`. DAA differs from vanilla [Account Abstraction (AA)](/build/sdks/ts-sdk/account/account-abstraction) in that, for a given `derivable_authentication_function`, it defines how to deterministically derive the account address from an `abstract_public_key`, which can be done off-chain. In contrast, vanilla AA is enabled for a specific pre-existing account by explicitly registering an on-chain `authentication_function` and submitting a transaction, which involves extra steps and costs gas for each account. This allows registering secondary authentication schemes with identical user experience to the native ones. More specifically, this provides a flexible and secure way to manage cross-chain signatures. (see [x-chain accounts](/build/sdks/wallet-adapter/x-chain-accounts)) ## Core Concepts [Section titled “Core Concepts”](#core-concepts) ### Authentication function [Section titled “Authentication function”](#authentication-function) DAA works by defining an custom authentication scheme and registering a valid authentication function to perform on-chain authentication. Each abstract account should have an associated `abstract_public_key` and should be able to produce `abstract_signature`s whose formats depend on the authentication scheme. Simply put, the `derivable_authentication_function` needs to check that: * the `abstract_signature` is valid for the given `abstract_public_key` * the `abstract_signature` depends on the transaction’s digest ```move // The function should return a signer if authentication is successful, otherwise it aborts the execution public fun authenticate(account: signer, auth_data: AbstractionAuthData): signer; ``` The DAA framework automatically checks whether the address derived from `abstract_public_key` matches with the signer’s address. ### Authentication data [Section titled “Authentication data”](#authentication-data) `AbstractionAuthData` is an enum that represent the authentication data to be passed to custom authentication functions. It’s used in all flavors of AA, but the `DerivableV1` variant defines the following fields: * `digest`: The SHA3-256 hash of the signing message. * `abstract_signature`: Abstract signature bytes that need to be verified against `abstract_public_key`. * `abstract_public_key`: Abstract public key bytes associated to the abstract account Here’s what the Move enum looks like: ```move enum AbstractionAuthData has copy, drop { V1 { ... }, // Only applicable to vanilla AA DerivableV1 { digest: vector, // SHA3-256 hash of the signing message abstract_signature: vector, abstract_public_key: vector, } } ``` **Why is the `digest` important?** The `digest` is checked by the MoveVM to ensure that the signing message of the transaction being submitted is the same as the one presented in the `AbstractionAuthData`. This is important because it allows the authentication function to verify signatures with respect to the correct transaction. For example, if you want to permit a public key to sign transactions on behalf of the user, you can permit the public key to sign a transaction with a specific payload. However, if a malicious user sends a signature for the correct public key but a different payload from the `digest`, the signature will not be valid. ### Account address derivation [Section titled “Account address derivation”](#account-address-derivation) With DAA, a given `derivable_authentication_function` defines a space of account addresses that can be deterministically derived from their associated `abstract_public_key`. The on-chain function looks like the following: ```move public fun derive_account_address(derivable_func_info: FunctionInfo, abstract_public_key: &vector): address { let bytes = bcs::to_bytes(&derivable_func_info); bytes.append(bcs::to_bytes(abstract_public_key)); bytes.push_back(DERIVABLE_ABSTRACTION_DERIVED_SCHEME); from_bcs::to_address(hash::sha3_256(bytes)) } ``` where `FunctionInfo` is a fully qualified identifier for a on-chain function: ```move struct FunctionInfo has copy, drop, store { module_address: address, module_name: String, function_name: String } ``` The address derivation depends on the authentication function’s identifier and on a DAA-specific domain separator. Because of this, each address space is isolated from the others and it’s not possible for the same account to have multiple authentication functions. **Example (Move)** This example demonstrates domain account abstraction using ed25519 hex for signing. ```move module aptos_experimental::test_derivable_account_abstraction_ed25519_hex { use std::error; use aptos_std::string_utils; use aptos_std::ed25519::{ Self, new_signature_from_bytes, new_unvalidated_public_key_from_bytes, }; use aptos_framework::auth_data::AbstractionAuthData; const EINVALID_SIGNATURE: u64 = 1; /// Authorization function for derivable account abstraction. public fun authenticate(account: signer, aa_auth_data: AbstractionAuthData): signer { let hex_digest = string_utils::to_string(aa_auth_data.digest()); let public_key = new_unvalidated_public_key_from_bytes(*aa_auth_data.derivable_abstract_public_key()); let signature = new_signature_from_bytes(*aa_auth_data.derivable_abstract_signature()); assert!( ed25519::signature_verify_strict( &signature, &public_key, *hex_digest.bytes(), ), error::permission_denied(EINVALID_SIGNATURE) ); account } } ``` **Example (Typescript)** ```typescript const derivableAbstractedAccount = new DerivableAbstractedAccount({ /** * The result of the signer function will be available as the `abstract_signature` field in the `AbstractionAuthData` enum variant. */ signer: (digest) => { const hexDigest = new TextEncoder().encode(Hex.fromHexInput(digest).toString()); return solanaAccount.sign(hexDigest).toUint8Array(); }, /** * The authentication function to be invoked. */ authenticationFunction: `0x7::test_derivable_account_abstraction_ed25519_hex::authenticate`, /** * The abstract public key (i.e the account identity) */ abstractPublicKey: account.publicKey.toUint8Array(), }); ``` ## Minimal Step-by-Step Guide [Section titled “Minimal Step-by-Step Guide”](#minimal-step-by-step-guide) 1. 1. Generate a ED25519 key pair ```typescript const ed25519Account = Account.generate(); ``` 2. 2. Create a DAA ```typescript const daa = new DerivableAbstractedAccount({ signer: (digest) => { const hexDigest = new TextEncoder().encode(Hex.fromHexInput(digest).toString()); return ed25519Account.sign(hexDigest).toUint8Array(); }, authenticationFunction: `0x7::test_derivable_account_abstraction_ed25519_hex::authenticate`, abstractPublicKey: ed25519Account.publicKey.toUint8Array(), }); ``` 3. 3. Fund the DAA to create it on chain ```typescript await aptos.fundAccount({ accountAddress: daa.accountAddress, amount: 1000000 }); ``` 4. 4. Create a recipient account and transfer APT to it ```typescript const recipient = Account.generate(); const pendingTxn = await aptos.transaction.signAndSubmitTransaction({ signer: daa, transaction: await aptos.transferCoinTransaction({ sender: daa.accountAddress, recipient: recipient.accountAddress, amount: 100, }), }); const response = await aptos.waitForTransaction({ transactionHash: pendingTxn.hash }); ``` # Building Transactions Transactions allow you to change on-chain data or trigger events. Generally, transactions follow 5 steps from building to executing on chain: building, simulating, signing, submitting, and waiting. Note For these examples, `aptos` is an instance of the [`Aptos`](https://aptos-labs.github.io/aptos-ts-sdk/@aptos-labs/ts-sdk-1.35.0/classes/Aptos.html) client object. 1. Build Building a transaction is how you specify: 1. **The `sender` account.**\ This account normally pays the gas fees for this transaction. See [Transaction Sponsoring](/build/sdks/ts-sdk/building-transactions/sponsoring-transactions) to learn how to have another account pay for fees. 2. **The `function` being called on-chain.**\ This is the identifier for the smart contract entry function on-chain that will trigger when you execute this transaction. 3. **The `functionArguments`.**\ This is any data the function needs to run. This can be packaged into a `SimpleTransaction` using `aptos.transaction.build.simple(...)` like so: ```typescript const transaction = await aptos.transaction.build.simple({ sender: sender.accountAddress, data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [destination.accountAddress, 100], }, }); ``` Note There is a more advanced format to pass in `functionArguments` called [Binary Canonical Serialization (BCS)](/build/sdks/ts-sdk/building-transactions/bcs-format) format which is how the Aptos chain parses function arguments. The SDK converts TypeScript primitives to BCS format behind the scenes via an API call. #### Building Options [Section titled “Building Options”](#building-options) You can customize the way your transaction executes by passing in `options: {...}` when building. Some of the most commonly used options are: 1. `maxGasAmount` - This caps the amount of gas you are willing to pay for to execute this transaction. 2. `gasUnitPrice` - You can specify a higher than minimum price per gas to be executed with higher priority by the Aptos network. 3. `expireTimestamp` - This gives a concrete time the transaction must execute by or it will be canceled. The SDK provides sensible defaults for these values if they are not specified explicitly. 2. Simulate (Optional) Every transaction on the Aptos chain has a gas fee associated with how much work the network machines have to do when executing the transaction. In order to estimate the cost associated with that, you can simulate transactions before committing them. Note This simulation only requires the `publicKey` of an account since it will not impact the actual state of the ledger. You can execute the simulation by using `aptos.transaction.simulate.simple(...)` like so: ```typescript const [userTransactionResponse] = await aptos.transaction.simulate.simple({ signerPublicKey: signer.publicKey, transaction, }); // If the fee looks ok, continue to signing! ``` 3. Sign Once the transaction is built and the fees seem reasonable, you can sign the transaction with `aptos.transaction.sign`. The signature must come from the `sender` account. ```typescript // 3. Sign const senderAuthenticator = aptos.transaction.sign({ signer: sender, transaction, }); ``` 4. Submit Now that the transaction is signed, you can submit it to the network using `aptos.transaction.submit.simple` like so: ```typescript // 4. Submit const committedTransaction = await aptos.transaction.submit.simple({ transaction, senderAuthenticator, }); ``` 5. Wait Finally, you can wait for the result of the transaction by using [`aptos.waitForTransaction`](https://aptos-labs.github.io/aptos-ts-sdk/@aptos-labs/ts-sdk-1.35.0/classes/Aptos.html#waitForTransaction) and specifying the hash of the transaction you just submitted like so: ```typescript // 5. Wait const executedTransaction = await aptos.waitForTransaction({ transactionHash: committedTransaction.hash }); ``` ## Full TypeScript Example [Section titled “Full TypeScript Example”](#full-typescript-example) ```typescript /** * This example shows how to use the Aptos SDK to send a transaction. * Don't forget to install @aptos-labs/ts-sdk before running this example! */ import { Account, Aptos, AptosConfig, Network, } from "@aptos-labs/ts-sdk"; async function example() { console.log("This example will create two accounts (Alice and Bob) and send a transaction transfering APT to Bob's account."); // 0. Setup the client and test accounts const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); let alice = Account.generate(); let bob = Account.generate(); console.log("=== Addresses ===\n"); console.log(`Alice's address is: ${alice.accountAddress}`); console.log(`Bob's address is: ${bob.accountAddress}`); console.log("\n=== Funding accounts ===\n"); await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 100_000_000, }); await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: 100, }); console.log("Funded Alice and Bob's accounts!") // 1. Build console.log("\n=== 1. Building the transaction ===\n"); const transaction = await aptos.transaction.build.simple({ sender: alice.accountAddress, data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [bob.accountAddress, 100], }, }); console.log("Built the transaction!") // 2. Simulate (Optional) console.log("\n === 2. Simulating Response (Optional) === \n") const [userTransactionResponse] = await aptos.transaction.simulate.simple({ signerPublicKey: alice.publicKey, transaction, }); console.log(userTransactionResponse) // 3. Sign console.log("\n=== 3. Signing transaction ===\n"); const senderAuthenticator = aptos.transaction.sign({ signer: alice, transaction, }); console.log("Signed the transaction!") // 4. Submit console.log("\n=== 4. Submitting transaction ===\n"); const submittedTransaction = await aptos.transaction.submit.simple({ transaction, senderAuthenticator, }); console.log(`Submitted transaction hash: ${submittedTransaction.hash}`); // 5. Wait for results console.log("\n=== 5. Waiting for result of transaction ===\n"); const executedTransaction = await aptos.waitForTransaction({ transactionHash: submittedTransaction.hash }); console.log(executedTransaction) }; example(); ``` ## Summary [Section titled “Summary”](#summary) Building and sending transactions on-chain involves the following 5 steps: 1. **Build** the transaction. 2. **Simulate** the cost. (Optional) 3. **Sign** the transaction (if the simulated cost seems ok). 4. **Submit** the transaction to the network. 5. **Wait** for the chain to validate and update. ## Explore Advanced Transaction Features [Section titled “Explore Advanced Transaction Features”](#explore-advanced-transaction-features) Transactions have a couple of additional features which let them adapt to your needs which you can learn about here: 1. [Multi-Agent Transactions](/build/sdks/ts-sdk/building-transactions/multi-agent-transactions) - Allowing multiple accounts to interact with a single transaction. 2. [Orderless Transactions](/build/sdks/ts-sdk/building-transactions/orderless-transactions) - Allowing for transactions to be executed out of order for easier management. 3. [Sponsoring Transactions](/build/sdks/ts-sdk/building-transactions/sponsoring-transactions) - Have another account pay gas fees for this transaction. 4. [Batch Submit Transactions](/build/sdks/ts-sdk/building-transactions/batching-transactions) - How to send multiple transactions quickly from a single account. 5. [Binary Canonical Serialization (BCS)](/build/sdks/ts-sdk/building-transactions/bcs-format) - The format used to serialize data for Aptos transactions. 6. [Composing multiple Move calls with ScriptComposer](/build/sdks/ts-sdk/building-transactions/script-composer) - (Experimental) Building more complex transaction payload that calls into multiple Move functions dynamically. # Batching Transactions The TypeScript SDK has a built-in way to send several independent transactions together in a batch. This can be a convenient tool when trying to execute multiple transactions quickly from the same account. This can be done with `aptos.transaction.batch.forSingleAccount` as can be seen in the below example. ## Full TypeScript Example [Section titled “Full TypeScript Example”](#full-typescript-example) ```typescript /** * This example shows how to use the Aptos SDK to send several transactions in a batch. */ import { Account, Aptos, AptosConfig, Network, InputGenerateTransactionPayloadData, } from "@aptos-labs/ts-sdk"; async function example() { console.log("This example will send several transactions in a batch."); // Setup the client and test accounts const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); let sender = Account.generate(); console.log("=== Addresses ===\n"); console.log(`Sender's address is: ${sender.accountAddress}`); console.log("\n=== Funding sender ===\n"); await aptos.fundAccount({ accountAddress: sender.accountAddress, amount: 100_000_000, }); console.log("Funded the sender account!") // Generate several recipients to send APT to const recipients = [Account.generate(), Account.generate(), Account.generate()]; // Create transactions to send APT to each account const transactions: InputGenerateTransactionPayloadData[] = []; for (let i = 0; i < recipients.length; i += 1) { const transaction: InputGenerateTransactionPayloadData = { function: "0x1::aptos_account::transfer", functionArguments: [recipients[i].accountAddress, 10], }; transactions.push(transaction); } // Sign and submit all transactions as fast as possible (throws if any error) await aptos.transaction.batch.forSingleAccount({ sender: sender, data: transactions }); }; example(); ``` ## Checking The Status of Batched Transactions [Section titled “Checking The Status of Batched Transactions”](#checking-the-status-of-batched-transactions) In order to tell when transaction submitted in a batch have executed on chain, you must listen to events while the process runs. ```typescript export enum TransactionWorkerEventsEnum { // Fired after a transaction gets sent to the chain TransactionSent = "transactionSent", // Fired if there is an error sending the transaction to the chain TransactionSendFailed = "transactionSendFailed", // Fired when a single transaction has executed successfully TransactionExecuted = "transactionExecuted", // Fired if a single transaction fails in execution TransactionExecutionFailed = "transactionExecutionFailed", // Fired when the worker has finished its job / when the queue has been emptied ExecutionFinish = "executionFinish", } ``` You can find an example of how to listen to these events [here](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/batch_funds.ts#L108). # BCS Format Behind the scenes, the Aptos SDK has two formats for transaction parameters: 1. **Simple** - This represents transaction parameters using primitive types like strings, integers, etc. 2. **Binary Canonical Serialization (BCS)** - This is the format the Aptos chain recognizes, with specific types (ex. Instead of an integer, it uses types like `U64` or `U128`) Normally, the TypeScript SDK will automatically convert simple types in function parameters into BCS in order to communicate with the network. For some contracts though, you will have to use BCS directly to specify complicated types. Using BCS directly can have a light performance advantage as the SDK can skip an API call to transform the TypeScript primitive parameter format into BCS format. You can directly use the BCS format to build transactions by specifying argument types explicitly like so: ```typescript const transaction = await aptos.transaction.build.simple({ sender: alice.accountAddress, data: { function: "0x1::aptos_account::transfer", functionArguments: [AccountAddress.fromString("0x123"), new U64(1_000_000)], }, }); ``` You can learn more about BCS by exploring the [BCS GitHub repo](https://github.com/aptos-labs/bcs). # Multi-Agent Transactions Multi-agent transactions allow multiple accounts to participate in the logic of a Move contract. This can be used to require multiple parties agree to a transaction before executing or to use resources from multiple accounts. ## Writing Multi-Agent Transactions [Section titled “Writing Multi-Agent Transactions”](#writing-multi-agent-transactions) Creating and executing a multi-agent transaction follows a similar flow to the [simple transaction flow](/build/sdks/ts-sdk/building-transactions), but with several notable differences. Note Instead of `.simple`, multi-agent transaction functions use `.multiAgent`. 1. Build the transaction by including secondarySignerAddresses with a list of each additional agent. Note Make sure to replace the `function` field below with your entry function that requires multiple agents to sign. ```typescript const transaction = await aptos.transaction.build.multiAgent({ sender: alice.accountAddress, secondarySignerAddresses: [bob.accountAddress], data: { // REPLACE WITH YOUR MULTI-AGENT FUNCTION HERE function: " (Syntax {address}::{module}::{function})", // Pass in arguments for the function you specify above functionArguments: [], }, }); ``` 2. (Optional) Simulate the transaction. You can simulate the multi-agent transaction to preview the result before submitting it as follows: ```typescript const [userTransactionResponse] = await aptos.transaction.simulate.multiAgent( { signerPublicKey: alice.publicKey, secondarySignersPublicKeys: [bob.publicKey], transaction, }, ); ``` The `signerPublicKey` and `secondarySignersPublicKeys` inputs are optional and can be omitted to skip authentication key checks for the signers during simulation. If you want to skip the authentication key check for only some of the secondary signers, you can provide `secondarySignersPublicKeys` with the public keys of the specific signers you want to check, using `undefined` as a placeholder for the others. For example, if `bob` and `carol` are secondary signers and you only want to check `carol`’s authentication key, you can set `secondarySignersPublicKeys: [undefined, carol.publicKey]`, leaving `undefined` as a placeholder for `bob`. 3. Sign once for each agent. You will combine these signatures in the next step. ```typescript const aliceSenderAuthenticator = aptos.transaction.sign({ signer: alice, transaction, }); // Bob is a secondary signer for this transaction const bobSenderAuthenticator = aptos.transaction.sign({ signer: bob, transaction, }); ``` 4. Submit the transaction by combining all agent signatures via the additionalSignerAuthenticators parameter. ```typescript const committedTransaction = await aptos.transaction.submit.multiAgent({ transaction, senderAuthenticator: aliceSenderAuthenticator, additionalSignersAuthenticators: [bobSenderAuthenticator], }); ``` 5. Lastly, wait for the transaction to resolve. ```typescript const executedTransaction = await aptos.waitForTransaction({ transactionHash: committedTransaction.hash, }); ``` ## Full TypeScript Multi-Agent Code Snippet [Section titled “Full TypeScript Multi-Agent Code Snippet”](#full-typescript-multi-agent-code-snippet) Caution The below snippet needs light editing to work properly! (See below steps) 1. Install `@aptos-labs/ts-sdk` by running `pnpm i @aptos-labs/ts-sdk` or using whichever package manager is most comfortable for you. 2. Update the below snippet to build a transaction that requires multi-agent signing. 1. Replace the function and parameters below this comment: `// REPLACE WITH YOUR MULTI-AGENT FUNCTION HERE` 2. This customization is needed as there are no pre-made Aptos contracts which need multi-agent signatures. If you want to deploy your own example multi-agent contract, you can deploy the [“transfer two by two” example Move contract](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/tests/move/transfer/sources/script_two_by_two.move#L5). ```typescript /** * This example shows how to use the Aptos SDK to send a transaction. */ import { Account, Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; async function example() { console.log( "This example will create two accounts (Alice and Bob) and send a transaction transfering APT to Bob's account.", ); // 0. Setup the client and test accounts const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); let alice = Account.generate(); let bob = Account.generate(); let carol = Account.generate(); console.log("=== Addresses ===\n"); console.log(`Alice's address is: ${alice.accountAddress}`); console.log(`Bob's address is: ${bob.accountAddress}`); console.log(`Carol's address is: ${carol.accountAddress}`); console.log("\n=== Funding accounts ===\n"); await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 100_000_000, }); await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: 100_000_000, }); await aptos.fundAccount({ accountAddress: carol.accountAddress, amount: 100_000_000, }); console.log("Done funding Alice, Bob, and Carol's accounts."); // 1. Build console.log("\n=== 1. Building the transaction ===\n"); const transaction = await aptos.transaction.build.multiAgent({ sender: alice.accountAddress, secondarySignerAddresses: [bob.accountAddress], data: { // REPLACE WITH YOUR MULTI-AGENT FUNCTION HERE function: " (Syntax {address}::{module}::{function})", functionArguments: [], }, }); console.log("Transaction:", transaction); // 2. Simulate (Optional) console.log("\n === 2. Simulating Response (Optional) === \n"); const [userTransactionResponse] = await aptos.transaction.simulate.multiAgent( { signerPublicKey: alice.publicKey, secondarySignersPublicKeys: [bob.publicKey], transaction, }, ); console.log(userTransactionResponse); // 3. Sign console.log("\n=== 3. Signing transaction ===\n"); const aliceSenderAuthenticator = aptos.transaction.sign({ signer: alice, transaction, }); const bobSenderAuthenticator = aptos.transaction.sign({ signer: bob, transaction, }); console.log(aliceSenderAuthenticator); console.log(bobSenderAuthenticator); // 4. Submit console.log("\n=== 4. Submitting transaction ===\n"); const committedTransaction = await aptos.transaction.submit.multiAgent({ transaction, senderAuthenticator: aliceSenderAuthenticator, additionalSignersAuthenticators: [bobSenderAuthenticator], }); console.log("Submitted transaction hash:", committedTransaction.hash); // 5. Wait for results console.log("\n=== 5. Waiting for result of transaction ===\n"); const executedTransaction = await aptos.waitForTransaction({ transactionHash: committedTransaction.hash, }); console.log(executedTransaction); } example(); ``` ## Common Errors [Section titled “Common Errors”](#common-errors) `NUMBER_OF_SIGNER_ARGUMENTS_MISMATCH` - This happens when you are attempting to do multi-agent signing for a function which does not require that number of accounts. For example, if you try using multiple signatures for a `0x1::aptos_account::transfer` function - it only expects one address, and so produces an error when more than one is provided. # Orderless Transactions Orderless transactions allow you to create transactions that do not specify a order of execution between them. This is particularly useful in scenarios where multiple machines need to sign a transaction, but the order in which they sign does not affect the outcome of the transaction or matter to the creator. ## Building Orderless Transactions [Section titled “Building Orderless Transactions”](#building-orderless-transactions) Creating and executing a multi-agent transaction follows a similar flow to the [simple transaction flow](/build/sdks/ts-sdk/building-transactions), and the [multi-agent transaction flow](/build/sdks/ts-sdk/building-transactions/multi-agent-transactions). Note Instead of providing a `sequenceNumber` (or no sequence number at all), a `Replay Protection Nonce` is used to ensure that the transaction is unique and cannot be replayed (i.e., executed multiple times with the same nonce). For example, to create a single signer transaction that uses orderless transactions, specify the `nonce` in the `build.simple` method like so: ```typescript const transaction = await aptos.transaction.build.simple({ sender: sender.accountAddress, data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [destination.accountAddress, 100], }, options: { replayProtectionNonce: 12345, // This is the nonce that will be used to ensure the transaction is unique. } }); ``` Similarly, if you are building a multi-agent transaction, you can specify the `replayProtectionNonce` in the `build.multiAgent` method: ```typescript const transaction = await aptos.transaction.build.multiAgent({ sender: sender.accountAddress, secondarySignerAddresses: [bob.accountAddress], // List of secondary signers data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [destination.accountAddress, 100], }, options: { replayProtectionNonce: 12345, // This is the nonce that will be used to ensure the transaction is unique. } }); ``` And the same if you are building a sponsored transaction, you can specify the `replayProtectionNonce` in the `build.multiAgent` method: ```typescript const transaction = await aptos.transaction.build.multiAgent({ sender: sender.accountAddress, withFeePayer: true, // This indicates that the transaction will be sponsored. data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [destination.accountAddress, 100], }, options: { replayProtectionNonce: 12345, // This is the nonce that will be used to ensure the transaction is unique. } }); ``` Note For orderless transactions, the `replayProtectionNonce` must be unique for each transaction. Additionally, the expiration time of the transaction is maximum 60 seconds from the time it is submitted. If the transaction is not executed within that time, it will be considered expired and will not be executed. After that, simply follow the same steps as you would for a simple transaction: 1. [**Simulate** the transaction (optional)](/build/sdks/ts-sdk/building-transactions/simulating-transactions). 2. **Sign** the transaction. 3. **Submit** the transaction to the network. 4. **Wait** for the transaction to be executed. ### Examples [Section titled “Examples”](#examples) * [TS SDK Example](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/simple_orderless_transfer.ts) # Invoke chains of Move calls with Dynamic Script Composer Caution Due to the current large bundle size of the ts-sdk, we are temporarily removing the Script Composer feature from the ts-sdk. In the future, we will provide this feature as a separate extension package. If you wish to continue using Script Composer, please install version 1.39.0: In the naive api, you only get to specify one entry function to invoke for one transaction. An advanced builder might want to be able to invoke multiple **public** Move functions inside one transaction. This is now enabled by the new `scriptComposer` api provided in the transaction builder. Here’s an example of how you can invoke the api: ```typescript const transaction = await aptos.transaction.build.scriptComposer({ sender: singleSignerED25519SenderAccount.accountAddress, // The builder expects a closure to build up the move call sequence. builder: async (builder) => { // invoke 0x1::coin::withdraw. This function would return a value of a `coin` type. const coin = await builder.addBatchedCalls({ function: "0x1::coin::withdraw", functionArguments: [CallArgument.newSigner(0), 1], typeArguments: ["0x1::aptos_coin::AptosCoin"], }); // Passing the coin value to the 0x1::coin::coin_to_fungible_asset to convert a coin // into fungible asset. const fungibleAsset = await builder.addBatchedCalls({ function: "0x1::coin::coin_to_fungible_asset", // coin[0] represents the first return value from the first call you added. functionArguments: [coin[0]], typeArguments: ["0x1::aptos_coin::AptosCoin"], }); // Deposit the fungibleAsset converted from second call. await builder.addBatchedCalls({ function: "0x1::primary_fungible_store::deposit", functionArguments: [singleSignerED25519SenderAccount.accountAddress, fungibleAsset[0]], typeArguments: [], }); return builder; }, }); ``` Behind the scene, the SDK will invoke a WASM binary to compile the series of Move calls into a `CompiledScript`. This will guarantee that the type and ability safety of Move is still being honored during the construction process. For the SDK users, this means: 1. ability safety: a. If returned value does not have Drop ability, the returned value need to be consumed by subsequent calls. b. If returned value does not have Copy ability, the returned value can only be passed to subsequent calls once. 2. The caller will need to make sure they pass the right values as arguments to subsequent calls. In the previous example, `0x1::coin::coin_to_fungible_asset` will expect an argument of `Coin`. This implements [AIP-102](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-102.md) # Simulating Transactions Simulating transactions allows you to preview the cost and effect of submitting a transaction without paying fees. You can use this to estimate fees, test a transaction, or to check what the output might be. To simulate a transaction, you must pass in a transaction and which account would be the signer: ```typescript import { Account, Aptos, AptosConfig, Network, } from "@aptos-labs/ts-sdk"; async function example() { let sender = Account.generate(); let receiver = Account.generate(); // 0. Setup the client and test accounts const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); await aptos.fundAccount({ accountAddress: sender.accountAddress, amount: 100_000_000, }); // 1. Build the transaction to preview the impact of it const transaction = await aptos.transaction.build.simple({ sender: sender.accountAddress, data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [receiver.accountAddress, 100], }, }); // 2. Simulate to see what would happen if we execute this transaction const [userTransactionResponse] = await aptos.transaction.simulate.simple({ signerPublicKey: sender.publicKey, transaction, }); console.log(userTransactionResponse) // If the fee looks ok, continue to signing! // ... } example(); ``` This will produce the same output as if the transaction was submitted. The `signerPublicKey` parameter in `aptos.transaction.simulate.simple` is used to verify the signer’s authentication key during transaction simulation. This parameter is optional, and simulation will bypass checking the authentication key if omitted. For example below: ```typescript // 2. Simulate to see what would happen if we execute this transaction, skipping the authentication key check const [userTransactionResponse] = await aptos.transaction.simulate.simple({ transaction, }); ``` Example Output ```shellscript { version: '9534925', hash: '0xea50b6fbea39ad1ba015d11cda0e7478334669c34830bc3df067a260d680893c', state_change_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', event_root_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', state_checkpoint_hash: null, gas_used: '9', success: true, vm_status: 'Executed successfully', accumulator_root_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', changes: [ { address: '0x811d5a94ccb597fa2a4f7872a3c678867cff94130d9378c39304c1354ef54abe', state_key_hash: '0x09adecee8779b64d05847488e2dbec6679c0c9e2fe618caf0793472ba3a7e4ab', data: [Object], type: 'write_resource' }, { address: '0x811d5a94ccb597fa2a4f7872a3c678867cff94130d9378c39304c1354ef54abe', state_key_hash: '0x0c70ede5412277b81d9f8d99369930ed5d56ad65862e3e878ad22dd5500833d0', data: [Object], type: 'write_resource' }, { address: '0xf40c314051890d16ba0a2ba427e003a83e730956fdeccf6c8eebc893a229ddc1', state_key_hash: '0x503f9cffb248036da24e18875f3dce72bb33d1d3ef5cfdbdb2fb3411cd718f4f', data: [Object], type: 'write_resource' }, { state_key_hash: '0x6e4b28d40f98a106a65163530924c0dcb40c1349d3aa915d108b4d6cfc1ddb19', handle: '0x1b854694ae746cdbd8d44186ca4929b2b337df21d1c74633be19b2710552fdca', key: '0x0619dc29a0aac8fa146714058e8dd6d2d0f3bdf5f6331907bf91f3acd81e6935', value: '0x708f579f62cb01000100000000000000', data: null, type: 'write_table_item' } ], sender: '0x811d5a94ccb597fa2a4f7872a3c678867cff94130d9378c39304c1354ef54abe', sequence_number: '0', max_gas_amount: '200000', gas_unit_price: '100', expiration_timestamp_secs: '1718983701', payload: { function: '0x1::aptos_account::transfer', type_arguments: [], arguments: [ '0xf40c314051890d16ba0a2ba427e003a83e730956fdeccf6c8eebc893a229ddc1', '100' ], type: 'entry_function_payload' }, signature: { public_key: '0x966b6b9aa8feb58ee1b911235dea1f185b9169de56303d18bb59937066881e44', signature: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', type: 'ed25519_signature' }, events: [ { guid: [Object], sequence_number: '0', type: '0x1::coin::CoinWithdraw', data: [Object] }, { guid: [Object], sequence_number: '0', type: '0x1::coin::WithdrawEvent', data: [Object] }, { guid: [Object], sequence_number: '0', type: '0x1::coin::CoinDeposit', data: [Object] }, { guid: [Object], sequence_number: '1', type: '0x1::coin::DepositEvent', data: [Object] }, { guid: [Object], sequence_number: '0', type: '0x1::transaction_fee::FeeStatement', data: [Object] } ], timestamp: '1718983681460047' } ``` Look [here](/build/sdks/ts-sdk/building-transactions) to see the full example of how to build, simulate, and submit a transaction. # Simulating more advanced Transactions [Section titled “Simulating more advanced Transactions”](#simulating-more-advanced-transactions) You can also learn how to simulate more advanced transactions by looking at the following guides: * [Sponsored Transactions](/build/sdks/ts-sdk/building-transactions/sponsoring-transactions) * [Multi-Agent Transactions](/build/sdks/ts-sdk/building-transactions/multi-agent-transactions) * Multisig V2 Transactions: See the next section for details. ## Simulating On-Chain Multisig (v2) Transactions [Section titled “Simulating On-Chain Multisig (v2) Transactions”](#simulating-on-chain-multisig-v2-transactions) For multisig transactions, there are two types of simulation: 1. Simulation of the target payload before it’s submitted on-chain, ignoring the voting status. 2. Simulation of the approved on-chain multisig transaction before execution to verify output and gas estimation. To perform the first type, you can simulate the target payload as a sponsored transaction with the multisig account as the sender, and set the fee payer to `0x0` to bypass gas fee payment during simulation. For example: ```typescript // Generate a raw transaction with the multisig address as the sender, // the provided entry function payload, and 0x0 as the fee payer address. const transactionToSimulate = await aptos.transaction.build.simple({ sender: multisigAddress, data: { function: "0x1::aptos_account::transfer", functionArguments: [recipient.accountAddress, 1_000_000], }, withFeePayer: true, }); // Simulate the transaction, skipping the public/auth key check for both the sender and the fee payer. const [simulateMultisigTx] = await aptos.transaction.simulate.simple({ transaction: transactionToSimulate, }); ``` This setup allows you to preview the target payload’s result before submitting it on-chain. Here, `signerPublicKey` is omitted to skip the authentication key check for the sender, as the multisig account does not have a public key. Additionally, `feePayerAddress` defaults to `0x0`, and `feePayerPublicKey` is omitted to bypass the gas fee payment during simulation. When this payload is later executed after submission and approval, the owner executing the transaction will cover the gas fee. For the second type of simulation, where the on-chain multisig payload transaction is simulated for final validation and gas estimation, use the following approach: ```typescript const transactionPayload: TransactionPayloadMultiSig = await generateTransactionPayload({ multisigAddress, function: "0x1::aptos_account::transfer", functionArguments: [recipient.accountAddress, 1_000_000], aptosConfig: config, }); const rawTransaction = await generateRawTransaction({ aptosConfig: config, sender: owner.accountAddress, payload: transactionPayload, }); const [simulateMultisigTx] = await aptos.transaction.simulate.simple({ signerPublicKey: owner.publicKey, transaction: new SimpleTransaction(rawTransaction), }); ``` Note that `signerPublicKey` is optional and can be omitted if you wish to skip the authentication key check for the sender during simulation. For the complete source code, see the [Multisig V2 Example](https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript-esm/multisig_v2.ts). # Sponsoring Transactions Normally, the account that is executing a transaction pays for the gas fees. You can allow another account to cover those charges by sponsoring a transaction. This can be used to help manage fees from a central account when working with complicated smart contracts. ## How To Sponsor a Transaction [Section titled “How To Sponsor a Transaction”](#how-to-sponsor-a-transaction) 1. Build the transaction with the parameter withFeePayer: true. ```typescript const transaction = await aptos.transaction.build.simple({ sender: sender.accountAddress, withFeePayer: true, data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [destination.accountAddress, 100], }, }); ``` 2. Sign the transaction with BOTH the sender and the feePayer. 1. Sign with the sender account using `.sign`. 2. Sign with the sponsor account using `.signAsFeePayer`. Caution The sponsor uses a different function (`.signAsFeePayer`) than the sender to sign! ```typescript const senderAuthenticator = aptos.transaction.sign({ signer: sender, transaction, }); const feePayerAuthenticator = aptos.transaction.signAsFeePayer({ signer: feePayer, transaction }) ``` 3. (Optional) Simulate the sponsoring transaction You can simulate the sponsoring transaction to preview the result before submitting it as follows: ```typescript const [userTransactionResponse] = await aptos.transaction.simulate.simple({ signerPublicKey: sender.publicKey, transaction, }); ``` By default, the `transaction`’s `feePayerAddress` is set to `0x0`, which directs the transaction simulation to skip the gas fee payment. This allows you to simulate the transaction without specifying a fee payer. Note that `signerPublicKey` is optional and can be omitted if you want to skip the authentication key check for the sender. You can also simulate the transaction with a specific fee payer by setting the `feePayerAddress` in the `transaction` object as follows: ```typescript transaction.feePayerAddress = feePayer.accountAddress; const [userTransactionResponse] = await aptos.transaction.simulate.simple({ signerPublicKey: sender.publicKey, feePayerPublicKey: feePayer.publicKey, transaction, }); ``` This setup will verify that `feePayer` has sufficient balance to cover the gas fee for the transaction. Similarly, `feePayerPublicKey` is optional and can be omitted if you wish to bypass the authentication key check for the fee payer. 4. Submit the transaction by combining both signatures. ```typescript // 4. Submit const committedTransaction = await aptos.transaction.submit.simple({ transaction, senderAuthenticator: senderAuthenticator, feePayerAuthenticator: feePayerAuthenticator, }); ``` 5. Wait for the transaction to execute. ```typescript // 5. Wait for results const executedTransaction = await aptos.waitForTransaction({ transactionHash: committedTransaction.hash }); ``` ## TypeScript Sponsor Transaction Code Sample [Section titled “TypeScript Sponsor Transaction Code Sample”](#typescript-sponsor-transaction-code-sample) ```typescript /** * This example shows how to use the Aptos SDK to send a transaction with a sponsor. */ import { Account, Aptos, AptosConfig, Network, } from "@aptos-labs/ts-sdk"; async function example() { console.log("This example will send a sponsored transaction from Alice to Carol."); // 0. Setup the client and test accounts const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); let alice = Account.generate(); let bob = Account.generate(); let carol = Account.generate(); console.log("=== Addresses ===\n"); console.log(`Alice's address is: ${alice.accountAddress}`); console.log(`Bob's address is: ${bob.accountAddress}`); console.log(`Carol's address is: ${carol.accountAddress}`); console.log("\n=== Funding accounts ===\n"); await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 500_000_000, }); await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: 500_000_000, }); await aptos.fundAccount({ accountAddress: carol.accountAddress, amount: 100, }); console.log("Funded the accounts!") // 1. Build console.log("\n=== 1. Building the transaction ===\n"); const transaction = await aptos.transaction.build.simple({ sender: alice.accountAddress, withFeePayer: true, data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [carol.accountAddress, 100], }, }); console.log("Built the transaction!") // 2. Sign console.log("\n=== 2. Signing transaction ===\n"); const aliceSenderAuthenticator = aptos.transaction.sign({ signer: alice, transaction, }); const bobSenderAuthenticator = aptos.transaction.signAsFeePayer({ signer: bob, transaction }) console.log("Signed the transaction!") // 3. Simulate (Optional) console.log("\n === 3. Simulating Response (Optional) === \n") const [userTransactionResponse] = await aptos.transaction.simulate.simple({ signerPublicKey: alice.publicKey, feePayerPublicKey: bob.publicKey, transaction, }); console.log(userTransactionResponse) // 4. Submit console.log("\n=== 4. Submitting transaction ===\n"); const committedTransaction = await aptos.transaction.submit.simple({ transaction, senderAuthenticator: aliceSenderAuthenticator, feePayerAuthenticator: bobSenderAuthenticator, }); console.log("Submitted transaction hash:", committedTransaction.hash); // 5. Wait for results console.log("\n=== 5. Waiting for result of transaction ===\n"); const executedTransaction = await aptos.waitForTransaction({ transactionHash: committedTransaction.hash }); console.log(executedTransaction) }; example(); ``` ## Common Errors [Section titled “Common Errors”](#common-errors) `INSUFFICIENT_BALANCE_FOR_TRANSACTION_FEE` : 1. This may be caused by accidentally using `.sign` instead of `.signAsFeePayer` when signing the transaction before submitting on-chain. 2. Sponsoring a transaction requires that the sponsoring account have enough funds to cover the max possible gas fee. This is often orders of magnitude larger than the expected or actual gas fees required for a transaction to execute. In this case, increase the funds in the account above the `max_gas_amount` **multiplied** by the `gas_unit_price` in the simulated transaction. These must be multiplied because gas is unitless, and so must be multiplied by the conversion rate from gas to [octas](/network/glossary#Octa). You can learn more about gas [here](/network/blockchain/gas-txn-fee). # Confidential Asset (CA) You can use `confidentialCoin` property of `Aptos` client to interact with `CA` ### Initialization [Section titled “Initialization”](#initialization) Operations in CA require generating zk-proofs (ZKPs), and depending on your environment, you need to define a `Range Proof` calculation. For the web, you could use `confidential-asset-wasm-bindings/confidential-asset-wasm-bindings`: Let’s prepare range-proof generation and configure SDK to use it: ```typescript import initWasm, { batch_range_proof as batchRangeProof, batch_verify_proof as batchVerifyProof, range_proof as rangeProof, verify_proof as verifyProof, } from '@aptos-labs/confidential-asset-wasm-bindings/range-proofs' import { BatchRangeProofInputs, BatchVerifyRangeProofInputs, RangeProofInputs, VerifyRangeProofInputs, } from '@lukachi/aptos-labs-ts-sdk' const RANGE_PROOF_WASM_URL = 'https://unpkg.com/@aptos-labs/confidential-asset-wasm-bindings@0.3.16/range-proofs/aptos_rp_wasm_bg.wasm' export async function genBatchRangeZKP( opts: BatchRangeProofInputs, ): Promise<{ proof: Uint8Array; commitments: Uint8Array[] }> { await initWasm({ module_or_path: RANGE_PROOF_WASM_URL }) const proof = batchRangeProof( new BigUint64Array(opts.v), opts.rs, opts.val_base, opts.rand_base, opts.num_bits, ) return { proof: proof.proof(), commitments: proof.comms(), } } export async function verifyBatchRangeZKP( opts: BatchVerifyRangeProofInputs, ): Promise { await initWasm({ module_or_path: RANGE_PROOF_WASM_URL }) return batchVerifyProof( opts.proof, opts.comm, opts.val_base, opts.rand_base, opts.num_bits, ) } ``` And then, just place this at the very top of your app: ```typescript import { RangeProofExecutor } from '@aptos-labs/ts-sdk' RangeProofExecutor.setGenBatchRangeZKP(genBatchRangeZKP); RangeProofExecutor.setVerifyBatchRangeZKP(verifyBatchRangeZKP); RangeProofExecutor.setGenerateRangeZKP(generateRangeZKP); RangeProofExecutor.setVerifyRangeZKP(verifyRangeZKP); ``` For the native apps: Generate `android` and `ios` bindings [here](https://github.com/aptos-labs/confidential-asset-wasm-bindings) and integrate in your app as you please. And the last, but not the least important part: To get a “numeric” value of the confidential balance, you also need to solve a Discrete Logarithm Problem (DLP). CA implements the Pollard’s Kangaroo method for solving DLPs on the Ristretto curve. [Source](https://cr.yp.to/dlog/cuberoot-20120919.pdf) So we also need to initialize a decryption function for that: ```typescript // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 import initWasm, { create_kangaroo, WASMKangaroo, } from '@aptos-labs/confidential-asset-wasm-bindings/pollard-kangaroo' import { ConfidentialAmount, TwistedEd25519PrivateKey, TwistedElGamal, TwistedElGamalCiphertext, } from '@lukachi/aptos-labs-ts-sdk' import { bytesToNumberLE } from '@noble/curves/abstract/utils' const POLLARD_KANGAROO_WASM_URL = 'https://unpkg.com/@aptos-labs/confidential-asset-wasm-bindings@0.3.15/pollard-kangaroo/aptos_pollard_kangaroo_wasm_bg.wasm' export async function createKangaroo(secret_size: number) { await initWasm({ module_or_path: POLLARD_KANGAROO_WASM_URL }) return create_kangaroo(secret_size) } export const preloadTables = async () => { const kangaroo16 = await createKangaroo(16) const kangaroo32 = await createKangaroo(32) const kangaroo48 = await createKangaroo(48) TwistedElGamal.setDecryptionFn(async pk => { if (bytesToNumberLE(pk) === 0n) return 0n let result = kangaroo16.solve_dlp(pk, 500n) if (!result) { result = kangaroo32.solve_dlp(pk, 1500n) } if (!result) { result = kangaroo48.solve_dlp(pk) } if (!result) throw new TypeError('Decryption failed') return result }) } ``` Now, place this at the top of your app: ```typescript const init = async () => { await preloadTables(); } ``` For the native apps, you could generate `android` and `ios` bindings [here](https://github.com/aptos-labs/confidential-asset-wasm-bindings) to use instead of WASM. *** Now we are ready to go. Let’s define Aptos client: ```typescript const APTOS_NETWORK: Network = NetworkToNetworkName[Network.TESTNET]; const config = new AptosConfig({ network: APTOS_NETWORK }); export const aptos = new Aptos(config); ``` ### Create Decryption Key (DK) [Section titled “Create Decryption Key (DK)”](#create-decryption-key-dk) To interact with the confidential asset, create a [unique key pair](/build/sdks/ts-sdk/confidential-asset#confidential-asset-store) first. Generate new: ```typescript const dk = TwistedEd25519PrivateKey.generate(); ``` Or import existed one: ```typescript const dk = new TwistedEd25519PrivateKey("0x..."); ``` Also, you could derive it using your `signature` (for testing purposes, don’t use at production): ```typescript const user = Account.generate() const signature = user.sign(TwistedEd25519PrivateKey.decryptionKeyDerivationMessage); const dk = TwistedEd25519PrivateKey.fromSignature(signature); ``` Or use [`pepper`](/build/guides/aptos-keyless/how-keyless-works) from [Keyless Account](/build/guides/aptos-keyless) ### Register [Section titled “Register”](#register) Next, you need to [register](/build/sdks/ts-sdk/confidential-asset#register) a previously generated encryption key (EK) in contracts: ```typescript export const registerConfidentialBalance = async ( account: Account, publicKeyHex: string, tokenAddress = "0x...", ) => { const txBody = await aptos.confidentialAsset.deposit({ sender: account.accountAddress, to: AccountAddress.from(to), tokenAddress: tokenAddress, amount: amount, }) const txResponse = await aptos.signAndSubmitTransaction({ signer: user, transaction: userRegisterCBTxBody }); const txReceipt = await aptos.waitForTransaction({ transactionHash: txResponse.hash }); return txReceipt; } ``` Check if a user has already registered a specific token: ```typescript export const getIsAccountRegisteredWithToken = async ( account: Account, tokenAddress = "0x...", ) => { const isRegistered = await aptos.confidentialAsset.hasUserRegistered({ accountAddress: account.accountAddress, tokenAddress: tokenAddress, }) return isRegistered } ``` ### Deposit [Section titled “Deposit”](#deposit) Let’s say you already have tokens. This will deposit them to your confidential balance ```typescript export const depositConfidentialBalance = async ( account: Account, amount: bigint, to: string, tokenAddress = "0x...", ) => { const txBody = await aptos.confidentialAsset.deposit({ sender: account.accountAddress, to: AccountAddress.from(to), tokenAddress: tokenAddress, amount: amount, }) // Sign and send transaction } ``` ### Get user’s balance [Section titled “Get user’s balance”](#get-users-balance) Let’s check the user’s balance after the deposit. ```typescript const userConfidentialBalance = await aptos.confidentialAsset.getBalance({ accountAddress: user.accountAddress, tokenAddress: TOKEN_ADDRESS }); ``` This method returns you the user’s [`pending` and `actual`](/build/sdks/ts-sdk/confidential-asset#confidential-asset-store) confidential balances, and to [decrypt](/build/sdks/ts-sdk/confidential-asset#encryption-and-decryption) them, you can use `ConfidentialAmount` class ```typescript export const getConfidentialBalances = async ( account: Account, decryptionKeyHex: string, tokenAddress = "0x...", ) => { const decryptionKey = new TwistedEd25519PrivateKey(decryptionKeyHex) const { pending, actual } = await aptos.confidentialAsset.getBalance({ accountAddress: account.accountAddress, tokenAddress, }) try { const [confidentialAmountPending, confidentialAmountActual] = await Promise.all([ ConfidentialAmount.fromEncrypted(pending, decryptionKey), ConfidentialAmount.fromEncrypted(actual, decryptionKey), ]) return { pending: confidentialAmountPending, actual: confidentialAmountActual, } } catch (error) { return { pending: ConfidentialAmount.fromAmount(0n), actual: ConfidentialAmount.fromAmount(0n), } } } ``` ### Rollover [Section titled “Rollover”](#rollover) After you deposited to user’s confidential balance, you can see, that he has, for instance `5n` at his `pending` balance, and `0n` at his `actual` balance. User can’t operate with `pending` balance, so you could [rollover](/build/sdks/ts-sdk/confidential-asset#rollover-pending-balance) it to `actual` one. And to do so - use `aptos.confidentialAsset.rolloverPendingBalance`. Caution Important note, that user’s actual balance need to be [normalized](/build/sdks/ts-sdk/confidential-asset#normalize) before `rollover` operation. To cover [normalization](#normalization) & `rollover` simultaneously, you could use `aptos.confidentialAsset.safeRolloverPendingCB`. ```typescript export const safelyRolloverConfidentialBalance = async ( account: Account, decryptionKeyHex: string, tokenAddress = "0x...", ) => { const rolloverTxPayloads = await aptos.confidentialAsset.safeRolloverPendingCB({ sender: account.accountAddress, tokenAddress, decryptionKey: new TwistedEd25519PrivateKey(decryptionKeyHex), }) // Sign and send batch txs } ``` *** ### Normalization [Section titled “Normalization”](#normalization) Usually you don’t need to explicitly call [normalization](/build/sdks/ts-sdk/confidential-asset#normalize) In case you want to: Caution Firstly, check a confidential balance is normalized, because trying to normalize an already normalized balance will return you an exception ```typescript export const getIsBalanceNormalized = async ( account: Account, tokenAddress = "0x...", ) => { const isNormalized = await aptos.confidentialAsset.isUserBalanceNormalized({ accountAddress: account.accountAddress, tokenAddress: tokenAddress, }) return isNormalized } ``` Get your balance and finally call the `aptos.confidentialAsset.normalizeUserBalance` method: ```typescript export const normalizeConfidentialBalance = async ( account: Account, decryptionKeyHex: string, encryptedPendingBalance: TwistedElGamalCiphertext[], amount: bigint, tokenAddress = "0x...", ) => { const normalizeTx = await aptos.confidentialAsset.normalizeUserBalance({ tokenAddress, decryptionKey: new TwistedEd25519PrivateKey(decryptionKeyHex), unnormalizedEncryptedBalance: encryptedPendingBalance, balanceAmount: amount, sender: account.accountAddress, }) // Sign and send transaction } ``` ### Withdraw [Section titled “Withdraw”](#withdraw) To [withdraw](/build/sdks/ts-sdk/confidential-asset#withdraw) your assets out from confidential balance: ```typescript export const withdrawConfidentialBalance = async ( account: Account, receiver: string, decryptionKeyHex: string, withdrawAmount: bigint, encryptedActualBalance: TwistedElGamalCiphertext[], tokenAddress = '0x...', ) => { const withdrawTx = await aptos.confidentialAsset.withdraw({ sender: account.accountAddress, to: receiver, tokenAddress, decryptionKey: decryptionKey, encryptedActualBalance, amountToWithdraw: withdrawAmount, }) // Sign and send transaction } ``` ### Transfer [Section titled “Transfer”](#transfer) For [transfer](/build/sdks/ts-sdk/confidential-asset#confidential-transfer) you need to know the recipient’s encryption key and `aptos` account address Let’s say you have a recipient’s account address, let’s get their encryption key. ```typescript export const getEkByAddr = async (addrHex: string, tokenAddress: string) => { return aptos.confidentialAsset.getEncryptionByAddr({ accountAddress: AccountAddress.from(addrHex), tokenAddress, }) } ``` Now, wrap it all together and transfer: ```typescript export const transferConfidentialCoin = async ( account: Account, decryptionKeyHex: string, encryptedActualBalance: TwistedElGamalCiphertext[], amountToTransfer: bigint, recipientAddressHex: string, auditorsEncryptionKeyHexList: string[], tokenAddress = "0x...", ) => { const decryptionKey = new TwistedEd25519PrivateKey(decryptionKeyHex) const recipientEncryptionKeyHex = await getEkByAddr( recipientAddressHex, tokenAddress, ) const transferTx = await aptos.confidentialAsset.transferCoin({ senderDecryptionKey: decryptionKey, recipientEncryptionKey: new TwistedEd25519PublicKey( recipientEncryptionKeyHex, ), encryptedActualBalance: encryptedActualBalance, amountToTransfer, sender: account.accountAddress, tokenAddress, recipientAddress: recipientAddressHex, auditorEncryptionKeys: auditorsEncryptionKeyHexList.map( hex => new TwistedEd25519PublicKey(hex), ), }) // Sign and send transaction } ``` ### Key Rotation [Section titled “Key Rotation”](#key-rotation) To do [key rotation](/build/sdks/ts-sdk/confidential-asset#rotate-encryption-key), you need to create a new decryption key and use `aptos.confidentialAsset.rotateCBKey` Caution But keep in mind, that `key-rotation` checks that pending balance equals 0. In that case, we could do a `rollover` with `freeze` option, to move assets from the pending balance to the actual one and lock our balance. ```typescript aptos.confidentialAsset.safeRolloverPendingCB({ ..., withFreezeBalance: false, }) ``` Now let’s create a new decryption key and rotate our encryption key: ```typescript const balances = await getBalances(user.accountAddress.toString(), myDecryptionKey, TOKEN_ADDRESS); const NEW_DECRYPTION_KEY = TwistedEd25519PrivateKey.generate(); const keyRotationAndUnfreezeTxResponse = await ConfidentialCoin.safeRotateCBKey(aptos, user, { sender: user.accountAddress, currDecryptionKey: currentDecryptionKey, newDecryptionKey: NEW_DECRYPTION_KEY, currEncryptedBalance: balances.actual.amountEncrypted, withUnfreezeBalance: true, // if you want to unfreeze balance after tokenAddress: TOKEN_ADDRESS, }); // save: new decryption key console.log(NEW_DECRYPTION_KEY.toString()); // check new balances const newBalance = await getBalances(user.accountAddress.toString(), NEW_DECRYPTION_KEY, TOKEN_ADDRESS); console.log(newBalance.pending.amount); console.log(newBalance.actual.amount); ``` # Fetch Data via SDK You can use the `Aptos` client to get on-chain data using a variety of helper functions. Specifically, many of the functions listed in the reference docs [here](https://aptos-labs.github.io/aptos-ts-sdk/@aptos-labs/ts-sdk-1.35.0/classes/Aptos.html) that start with `get...` will retrieve data from on-chain. Here’s an example showing how to fetch common data you may need in your application: ```typescript const aptosConfig = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(aptosConfig); const fund = await aptos.getAccountInfo({ accountAddress: "0x123" }); const modules = await aptos.getAccountModules({ accountAddress: "0x123" }); const tokens = await aptos.getAccountOwnedTokens({ accountAddress: "0x123" }); ``` Note Many queries have a parameter named `options` to customize the results, use it to get specifically what you are looking for. The `Aptos` client can out of the box query both network data from [fullnodes](https://api.mainnet.aptoslabs.com/v1/spec#/) and the [Indexer](https://cloud.hasura.io/public/graphiql?endpoint=https://api.mainnet.aptoslabs.com/v1/graphql) API which contains aggregated and enriched data. If you want to use a custom query for Indexer API data, you can use `aptos.queryIndexer` like so: ```typescript const ledgerInfo = await aptos.queryIndexer({ query: { query: ` query MyQuery { ledger_infos { chain_id } } ` } }) ``` ## Using Generic Queries [Section titled “Using Generic Queries”](#using-generic-queries) Some queries are intentionally broad, but this can make inferring the proper return type difficult. To accommodate that, these broad requests like `getAccountResources` allow you to specify what the expected response type should be. ```typescript type Coin = { coin: { value: string } }; const resource = await aptos.getAccountResource({ accountAddress: testAccount.accountAddress, resourceType: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", }); // Now you have access to the response type property const value = resource.coin.value; ``` ## Using Move View Functions [Section titled “Using Move View Functions”](#using-move-view-functions) You can call view functions which return custom data from on-chain by using `aptos.view`. For example, you can look up the network you are using with the `chain_id` view function: ```typescript const payload: InputViewFunctionData = { function: "0x1::chain_id::get", }; const chainId = (await aptos.view({ payload }))[0]; ``` ## Ensuring Fresh Indexer Data [Section titled “Ensuring Fresh Indexer Data”](#ensuring-fresh-indexer-data) Behind the scenes, some requests use the [Indexer API](/build/indexer) to access data which has been processed or aggregated. That extra parsing can take a bit of time, so the data may lag slightly behind the latest ledger. If you want to ensure that the data is fresh, you can specify the `minimumLedgerVersion` in any request which uses the Indexer API. ```typescript // Get the latest ledger version number const ledgerVersion = await aptos.getLedgerInfo().ledger_version; const tokens = await aptos.getAccountOwnedTokens({ accountAddress: alice.accountAddress, minimumLedgerVersion: BigInt(response.version), }); ``` You can also ensure that your request has the data from a transaction you submitted by getting the ledger version from the transaction validation itself. ```typescript // Wait for a transaction you just submitted const response = await aptos.waitForTransaction({ transactionHash: pendingTransaction.hash, }); // Then look up how that transaction affected alice's account const tokens = await aptos.getAccountOwnedTokens({ accountAddress: alice.accountAddress, minimumLedgerVersion: BigInt(response.version), }); ``` # Legacy TypeScript SDK Danger The Legacy TypeScript SDK package `aptos` is deprecated and will be replaced by the new TypeScript SDK. Please refer to the [new TypeScript SDK](/build/sdks/ts-sdk) for the latest features and updates. Take a look at the [migration guide](/build/sdks/ts-sdk/legacy-ts-sdk/migration-guide). # Migration Guide Danger The Legacy TypeScript SDK package `aptos` is deprecated and will be replaced by the new TypeScript SDK. Please refer to the [new TypeScript SDK](/build/sdks/ts-sdk) for the latest features and updates. If you are coming from an earlier version `1.x.x` of `aptos`, you will need to make the following updates. Note This guide only contains API differences and updates required for deprecated features. New features of the v2 SDK are not included. ## Install the SDK [Section titled “Install the SDK”](#install-the-sdk) The TypeScript SDK V2 is under a new [GitHub repo](https://github.com/aptos-labs/aptos-ts-sdk) and with a new package name - `@aptos-labs/ts-sdk` ```shellscript npm i @aptos-labs/ts-sdk ``` ## SDK usage and query the Aptos chain [Section titled “SDK usage and query the Aptos chain”](#sdk-usage-and-query-the-aptos-chain) Remove all `<*>Client` modules (i.e `AptosClient`, `FaucetClient`, `CoinClient`, etc.) and replace with an `Aptos` entry point class **V1** ```typescript const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL); const aptosClient = new AptosClient(NODE_URL); const indexerClient = new IndexerClient(INDEXER_URL); const tokenClient = new TokenClient(aptosClient); ``` **V2** Note Read more about it [here](/build/sdks/ts-sdk). ```typescript const aptos = new Aptos(); // make queries const fund = await aptos.fundAccount({ accountAddress: "0x123", amount: 100 }); const modules = await aptos.getAccountModules({ accountAddress: "0x123" }); const tokens = await aptos.getAccountOwnedTokens({ accountAddress: "0x123" }); ``` ## Configuration class [Section titled “Configuration class”](#configuration-class) To configure your `Aptos` client, you can use an `AptosConfig` object. ```typescript const aptosConfig = new AptosConfig({ network: Network.DEVNET }); // default to devnet const aptos = new Aptos(config); ``` ## Transaction Builder Flow [Section titled “Transaction Builder Flow”](#transaction-builder-flow) Removed all separate transaction functions in favor of a more simplified and friendlier transaction builder flow **V1** ```typescript const aptosClient = new AptosClient(NODE_URL); // bcs serialized arguments payload const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction( TxnBuilderTypes.EntryFunction.natural( "0x1::aptos_account", "transfer", [], [bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(receiver.address()))], ), ); // generate a raw transaction const transaction = await client.generateRawTransaction( sender.address(), entryFunctionPayload, ); // non-serialized arguments payload const payload: Gen.TransactionPayload = { type: "entry_function_payload", function: "0x1::aptos_account::transfer", type_arguments: [], arguments: [account2.address().hex(), 100000], }; // generate a raw transaction const transaction = await client.generateTransaction( account1.address(), payload, ); // sign transaction const signedTransaction = AptosClient.generateBCSTransaction( sender, transaction, ); // submit transaction const txn = await client.submitSignedBCSTransaction(signedTransaction); ``` **V2** Note Read more about it [here](/build/sdks/ts-sdk/building-transactions). ```typescript const aptos = new Aptos(); // non-serialized arguments transaction const transaction = await aptos.build.transaction({ sender: alice.accountAddress, data: { function: "0x1::coin::transfer", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [bobAddress, 100], }, }); // bcs serialized arguments transaction const transaction = await aptos.build.transaction({ sender: alice.accountAddress, data: { function: "0x1::coin::transfer", typeArguments: [parseTypeTag("0x1::aptos_coin::AptosCoin")], functionArguments: [bobAddress, new U64(100)], }, }); // sign transaction const senderAuthenticator = aptos.sign.transaction({ signer: alice, transaction, }); // submit transaction const committedTransaction = await aptos.submit.transaction({ transaction, senderAuthenticator, }); ``` ## Account [Section titled “Account”](#account) Rename `AptosAccount` to `Account` and use static methods to generate / derive an account **V1** ```typescript // generate a new account (or key pair) OR derive from private key OR derive from private key and address const account = new AptosAccount(); // supports only Legacy Ed25519 // derive account from derivation path const account = AptosAccount.fromDerivePath(..) ``` **V2** Note Read more about it [here](/build/sdks/ts-sdk/account). ```typescript // generate a new account (or key pair) const account = Account.generate(); // defaults to Legacy Ed25519 const account = Account.generate({ scheme: SigningSchemeInput.Secp256k1Ecdsa }); // Single Sender Secp256k1 const account = Account.generate({ scheme: SigningSchemeInput.Ed25519, legacy: false, }); // Single Sender Ed25519 // derive account from private key const account = Account.fromPrivateKey({ privateKey }); // derive account from private key and address const account = Account.fromPrivateKeyAndAddress({ privateKey, address: accountAddress, }); // derive account from derivation path const account = Account.fromDerivationPath({ path, mnemonic, scheme: SigningSchemeInput.Ed25519, }); ``` # TypeScript SDK Quickstart Note The complete example code can be found at [Full Quickstart Code](#full-quickstart-code) at the bottom of the page. 1. Initialize A Project This will initialize a typescript package with `quickstart.ts` * npm ```shellscript npm init && npm add -D typescript @types/node ts-node && npx tsc --init && mkdir src && echo 'async function example() { console.log("Running example!")}; example()' > src/quickstart.ts ``` * pnpm ```shellscript pnpm init && pnpm add -D typescript @types/node ts-node && pnpx tsc --init && mkdir src && echo 'async function example() { console.log("Running example!")}; example()' > src/quickstart.ts ``` * yarn ```shellscript yarn init -y && yarn add -D typescript @types/node ts-node && npx tsc --init && mkdir src && echo 'async function example() { console.log("Running example!")}; example()' > src/quickstart.ts ``` 2. Test Initialization To test if you have initialized the package correctly run: * npm ```shellscript npx ts-node src/quickstart.ts ``` * pnpm ```shellscript pnpx ts-node src/quickstart.ts ``` * yarn ```shellscript yarn ts-node src/quickstart.ts ``` 3. Install the TypeScript SDK using the package manager of your choice: ```shellscript npm i @aptos-labs/ts-sdk ``` 4. Set up the Aptos client You can use the `Aptos` object to handle everything that requires a connection to the Aptos network. A connection is established as soon as you create the object. ```typescript import { Account, Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; // Specify which network to connect to via AptosConfig async function example() { console.log( "This example will create two accounts (Alice and Bob), fund them, and transfer between them.", ); // Setup the client const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); } example() ``` Note (Advanced) If you need to connect to a specific node, you can set that in the `AptosConfig` by specifying the `fullnode` url. Ex. `fullnode: http://localhost:8080/v1`. 5. Fetch data from on-chain You can use the `Aptos` client to fetch all sorts of data from on-chain such as information about the network itself or account-specific information. ```typescript ... const ledgerInfo = await aptos.getLedgerInfo(); const modules = await aptos.getAccountModules({ accountAddress: "0x123" }); const tokens = await aptos.getAccountOwnedTokens({ accountAddress: "0x123" }); ... ``` 6. Send Transactions You can send transactions to change the state of the ledger. Transactions let you send tokens like APT, trigger Move modules, trade NFTs, and more. You can find an in-depth tutorial on transactions [here](/build/sdks/ts-sdk/building-transactions). To begin with though, here’s how you can send a basic transaction to transfer APT. #### 1. Create an Account [Section titled “1. Create an Account”](#1-create-an-account) To create a new account, you first generate new credentials then fund the account. On localnet / devnet you can fund an account programmatically by asking a “faucet” which has a lot of test APT to send some to your new account. ```typescript ... // Generate a new account key pair const alice: Account = Account.generate(); // Fund the account on chain. Funding an account creates it on-chain. await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: 100000000, }); // Also create a second account to transfer tokens to const bob: Account = Account.generate(); // Fund the account on chain await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: 100000000, }); ... ``` #### 2. Build the Transaction [Section titled “2. Build the Transaction”](#2-build-the-transaction) You can build transactions with `aptos.transaction.build.simple({...})` by specifying: 1. `sender` - The account that’s sending the transaction. This account will pay the gas fees. 2. `data` - The information needed for Aptos to identify what transaction to execute. 1. `function` - Which smart contract on chain to call. This has the format `::::`. 2. `functionArguments` - These are specific to the function being called. You can look up what parameters a function needs by searching for the account and module on chain with an explorer [like this](https://explorer.aptoslabs.com/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/code/aptos_account?network=mainnet). For example: ```typescript ... const transaction = await aptos.transaction.build.simple({ sender: alice.accountAddress, data: { // The Move entry-function function: "0x1::aptos_account::transfer", functionArguments: [bob.accountAddress, 100], }, }); ... ``` Note For some situations, you can also use simplified functions in the SDK such as [`transferCoinTransaction`](https://explorer.aptoslabs.com/account/0x0000000000000000000000000000000000000000000000000000000000000001/modules/code/aptos_account?network=mainnet). #### 3. Sign and Submit [Section titled “3. Sign and Submit”](#3-sign-and-submit) Signing proves that you own or manage the account that is executing the transaction. This is important since the sender must pay gas fees for the work the network does to execute the transaction. Once signed, you can submit to the network for on chain verification and execution. You can use `aptos.signAndSubmitTransaction` which combines those two steps into one: ```typescript ... // Both signs and submits (although these can be done separately too) const pendingTransaction = await aptos.signAndSubmitTransaction({ signer: alice, transaction, }); ... ``` #### 4. Wait for completion [Section titled “4. Wait for completion”](#4-wait-for-completion) You can run `aptos.waitForTransaction` to guarantee your code executes after the transaction has been processed and applied. This also helps you get any errors that may occur after submitting, such as the transaction being rejected. ```typescript ... const executedTransaction = await aptos.waitForTransaction({ transactionHash: pendingTransaction.hash }); ... ``` ## Full Quickstart Code [Section titled “Full Quickstart Code”](#full-quickstart-code) ### Run Quickstart [Section titled “Run Quickstart”](#run-quickstart) * npm ```shellscript npx ts-node src/quickstart.ts ``` * pnpm ```shellscript pnpx ts-node src/quickstart.ts ``` * yarn ```shellscript yarn ts-node src/quickstart.ts ``` ```typescript /** * This example shows how to use the Aptos client to create accounts, fund them, and transfer between them. */ import { Account, Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk"; const APTOS_COIN = "0x1::aptos_coin::AptosCoin"; const COIN_STORE = `0x1::coin::CoinStore<${APTOS_COIN}>`; const ALICE_INITIAL_BALANCE = 100_000_000; const BOB_INITIAL_BALANCE = 100; const TRANSFER_AMOUNT = 100; async function example() { console.log( "This example will create two accounts (Alice and Bob), fund them, and transfer between them.", ); // Setup the client const config = new AptosConfig({ network: Network.DEVNET }); const aptos = new Aptos(config); // Generate two account credentials // Each account has a private key, a public key, and an address const alice = Account.generate(); const bob = Account.generate(); console.log("=== Addresses ===\n"); console.log(`Alice's address is: ${alice.accountAddress}`); console.log(`Bob's address is: ${bob.accountAddress}`); // Fund the accounts using a faucet console.log("\n=== Funding accounts ===\n"); await aptos.fundAccount({ accountAddress: alice.accountAddress, amount: ALICE_INITIAL_BALANCE, }); await aptos.fundAccount({ accountAddress: bob.accountAddress, amount: BOB_INITIAL_BALANCE, }); console.log("Alice and Bob's accounts have been funded!"); // Look up the newly funded account's balances console.log("\n=== Balances ===\n"); const aliceAccountBalance = await aptos.getAccountResource({ accountAddress: alice.accountAddress, resourceType: COIN_STORE, }); const aliceBalance = Number(aliceAccountBalance.coin.value); console.log(`Alice's balance is: ${aliceBalance}`); const bobAccountBalance = await aptos.getAccountResource({ accountAddress: bob.accountAddress, resourceType: COIN_STORE, }); const bobBalance = Number(bobAccountBalance.coin.value); console.log(`Bob's balance is: ${bobBalance}`); // Send a transaction from Alice's account to Bob's account const txn = await aptos.transaction.build.simple({ sender: alice.accountAddress, data: { // All transactions on Aptos are implemented via smart contracts. function: "0x1::aptos_account::transfer", functionArguments: [bob.accountAddress, 100], }, }); console.log("\n=== Transfer transaction ===\n"); // Both signs and submits const committedTxn = await aptos.signAndSubmitTransaction({ signer: alice, transaction: txn, }); // Waits for Aptos to verify and execute the transaction const executedTransaction = await aptos.waitForTransaction({ transactionHash: committedTxn.hash, }); console.log("Transaction hash:", executedTransaction.hash); console.log("\n=== Balances after transfer ===\n"); const newAliceAccountBalance = await aptos.getAccountResource({ accountAddress: alice.accountAddress, resourceType: COIN_STORE, }); const newAliceBalance = Number(newAliceAccountBalance.coin.value); console.log(`Alice's balance is: ${newAliceBalance}`); const newBobAccountBalance = await aptos.getAccountResource({ accountAddress: bob.accountAddress, resourceType: COIN_STORE, }); const newBobBalance = Number(newBobAccountBalance.coin.value); console.log(`Bob's balance is: ${newBobBalance}`); // Bob should have the transfer amount if (newBobBalance !== TRANSFER_AMOUNT + BOB_INITIAL_BALANCE) throw new Error("Bob's balance after transfer is incorrect"); // Alice should have the remainder minus gas if (newAliceBalance >= ALICE_INITIAL_BALANCE - TRANSFER_AMOUNT) throw new Error("Alice's balance after transfer is incorrect"); } example(); ``` ## Summary [Section titled “Summary”](#summary) All told, you just learned how to transfer APT via a transaction by: 1. Connecting to the network using the `Aptos` client. 2. Creating an account. 3. Looking up data from on-chain using client helper functions like [`aptos.getAccountModules`](https://aptos-labs.github.io/aptos-ts-sdk/@aptos-labs/ts-sdk-1.35.0/classes/Aptos.html#getAccountModules). 4. Signing and submitting a transaction to the network. 5. Waiting for Aptos to execute the transaction. To see all this in action, you can copy and run the full working code snippet for this quickstart above. Note For future development, make sure to bookmark the [reference docs](https://aptos-labs.github.io/aptos-ts-sdk/) to look up specific function signatures. Note that most helper functions are listed on the [`Aptos` client object](https://aptos-labs.github.io/aptos-ts-sdk/@aptos-labs/ts-sdk-1.35.0/classes/Aptos.html). # TypeScript SDK Example Code For sample code which explains the core concepts of how to use the SDK, see: * [Fetching Data](/build/sdks/ts-sdk/fetch-data-via-sdk) * [Building, Simulating, and Submitting Transactions](/build/sdks/ts-sdk/building-transactions) Below are additional resources which may be more suited for your individual use case. ## Code Snippets [Section titled “Code Snippets”](#code-snippets) The [`examples` folder](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/examples) in the SDK repo has dozens of code snippets you can customize to your needs. ### How to run examples [Section titled “How to run examples”](#how-to-run-examples) To run one of the example scripts: 1. Clone the ```shellscript git clone https://github.com/aptos-labs/aptos-ts-sdk.git ``` 2. From the top-level of the package, install all dependencies. ```shellscript pnpm install ``` 3. Build the package. ```shellscript pnpm build ``` 4. Go to the folder of an example you would like to run. ```shellscript cd examples/typescript ``` 5. Install local dependencies. ```shellscript pnpm install ``` 6. Run the example. ```shellscript pnpm run simple_transfer ``` ## Helpful Reference Code [Section titled “Helpful Reference Code”](#helpful-reference-code) * [The SDK’s end-to-end tests](https://github.com/aptos-labs/aptos-ts-sdk/tree/main/tests/e2e) - This has the most comprehensive set of code that uses the SDK. * [SDK source code](https://github.com/aptos-labs/aptos-ts-sdk/tree/main) - This has in-line comments explaining what each function does. * [SDK reference docs](https://aptos-labs.github.io/aptos-ts-sdk/) - These are another way to view the in-line documentation with built-in search. # Surf: TypeScript Type Safety for Move Contracts ## What is Surf [Section titled “What is Surf”](#what-is-surf) Surf is a TypeScript library built on top of the Aptos TypeScript SDK and the wallet adapter that provides static type safety for your Move contracts by inferring type from contract ABI (Application Binary Interface). It allows you to catch type errors at compile time rather than at runtime. Most existing TypeScript IDEs will automatically provide warnings if you try to access fields that don’t exist, or provide wrong input types. ## Usage [Section titled “Usage”](#usage) 1. Step 1 First, download the ABI of the Move contract and save it to a TypeScript file. In this case, we’re naming the file `abi.ts` in the `src/utils` folder. * macOS & Linux ```shellscript #! /bin/bash # replace it with the network your contract lives on NETWORK=testnet # replace it with your contract address CONTRACT_ADDRESS=0x12345 # replace it with your module name, every .move file except move script has module_address::module_name {} MODULE_NAME=fungible_asset_launchpad # save the ABI to a TypeScript file echo "export const ABI = $(curl https://fullnode.$NETWORK.aptoslabs.com/v1/accounts/$CONTRACT_ADDRESS/module/$MODULE_NAME | sed -n 's/.*"abi":\({.*}\).*}$/\1/p') as const" > abi.ts ``` * Windows ```powershell # replace it with the network your contract lives on $NETWORK = "testnet" # replace it with your contract address $CONTRACT_ADDRESS = "0x1" # replace it with your module name, every .move file except move script has module_address::module_name {} $MODULE_NAME = "fungible_asset_launchpad" # save the ABI to a TypeScript file Invoke-RestMethod -Uri "https://fullnode.$NETWORK.aptoslabs.com/v1/accounts/$CONTRACT_ADDRESS/module/$MODULE_NAME" | Select-Object -ExpandProperty abi | ConvertTo-Json -Compress | Foreach-Object { "export const ABI = $_ as const" } | Out-File -FilePath "abi.ts" ``` 2. Step 2 With the ABI, you can use Surf as a layer on top of the Aptos TypeScript SDK client `Aptos`, when interacting with Move contracts. For non-contract related operations, the `Aptos` will still need to be used. ```typescript import { createSurfClient } from '@thalalabs/surf'; import { Aptos, AptosConfig, NETWORK } from "@aptos-labs/ts-sdk"; import { ABI } from "./abi"; // First, create an Aptos client, make sure the network is the one that contract lives on export const aptos = new Aptos(new AptosConfig({ network: Network.DEVNET })); // Second, create a SurfClient with the Aptos client and the ABI export const surfClient = createSurfClient(aptos).useABI(ABI); // Use Surf to executes an entry function const result = await surfClient.entry.transfer({ functionArguments: ['0x1', 1], typeArguments: ['0x1::aptos_coin::AptosCoin'], account: Account.fromPrivateKey(...), }); // Use Surf to query a view function const [balance] = await surfClient.view.balance({ functionArguments: ['0x1'], typeArguments: ['0x1::aptos_coin::AptosCoin'], }); ``` ## Resources [Section titled “Resources”](#resources) * [Surf GitHub](https://github.com/ThalaLabs/surf) * [A simple Next.js example demonstrating Surf](https://github.com/ThalaLabs/surf/tree/main/example) * [An example of a fungible asset launchpad using Surf](https://github.com/aptos-labs/move-by-examples/tree/main/fungible-asset-launchpad): This example is part of the Solana to Aptos guide on Aptos Learn, you can try it [here](https://fungible-asset-launchpad.vercel.app/) and read the complete tutorial [here](https://learn.aptoslabs.com/en/tutorials/aptogotchi-intermediate/fungible-assets?workshop=solana-to-aptos). ## Credits [Section titled “Credits”](#credits) Surf is built by [Thala Labs](https://thala.fi/), an Aptos ecosystem project, and maintained together by the Aptos community. ## Feedback [Section titled “Feedback”](#feedback) If you have any feedback or questions, please open an issue on [Surf’s GitHub](https://github.com/ThalaLabs/surf/issues). # Unity SDK Caution This SDK is currently in beta. Please report any issues you encounter by creating an issue in the [aptos-labs/unity-sdk](https://github.com/aptos-labs/unity-sdk) repository. Integrate Aptos Web3 capabilities within your Unity applications. The goal of this SDK is to provide a set of tools for developers to build Web3 games using the Unity game engine. **Supported Features** * Support for the [Aptos .NET SDK](/build/sdks/dotnet-sdk) > * Binary Canonical Serialization (BCS) encoding and decoding > * Ed25519, SingleKey, MultiKey, and Keyless signer support > * Utilities for transaction building, signing, and submission > * Abstractions over the Aptos Fullnode and Indexer APIs > * Aptos Names (ANS) support for resolution and lookup **Compatibility** | .NET Version | Supported | | ----------------- | --------- | | .NET Standard 2.1 | ✅ | ## Installation [Section titled “Installation”](#installation) ### Install via Unity Package Manager (UPM) [Section titled “Install via Unity Package Manager (UPM)”](#install-via-unity-package-manager-upm) 1. Open the Unity Package Manager (`Window` > `Package Manager`). 2. Click on the `+` button and select `Add package from git URL...`. 3. Enter the URL of the Aptos Unity SDK path in this repository: ```shellscript https://github.com/aptos-labs/unity-sdk.git?path=/Packages/com.aptoslabs.aptos-unity-sdk ``` ### Install via `unitypackage` [Section titled “Install via unitypackage”](#install-via-unitypackage) 1. Go to the [`aptos-labs/unity-sdk Releases`](https://github.com/aptos-labs/unity-sdk/releases) and download the latest release. 2. Drag and drop the `.unitypackage` file into your Unity project. ## Usage [Section titled “Usage”](#usage) Set up your Aptos client by adding the `Aptos` namespace and instantiating an `AptosUnityClient`. You can use a predefined configuration from `Networks` or configuring your own. ```csharp using UnityEngine; using Aptos; class Example : MonoBehaviour { public void Start() { PrintLedgerInfo(); } async void PrintLedgerInfo() { var client = new AptosUnityClient(Networks.Mainnet); var ledgerInfo = await client.Block.GetLedgerInfo(); Debug.Log(ledgerInfo.BlockHeight); } } ``` To interact with the blockchain, you will need to create a signer and build a transaction. ```csharp using UnityEngine; using Aptos; class Example : MonoBehaviour { public async void Start() { var client = new AptosUnityClient(Networks.Mainnet); // 1. Create a signer var signer = Account.Generate(); // 2. Build the transaction var transaction = await client.Transaction.Build( sender: account, data: new GenerateEntryFunctionPayloadData( function: "0x1::aptos_account::transfer_coins", typeArguments: ["0x1::aptos_coin::AptosCoin"], functionArguments: [account.Address, "100000"] ) ); // 3. Sign and submit the transaction var pendingTransaction = client.Transaction.SignAndSubmitTransaction(account, transaction); // 4. (Optional) Wait for the transaction to be committed var committedTransaction = await client.Transaction.WaitForTransaction(pendingTransaction); } } ``` ## Resources [Section titled “Resources”](#resources) [Aptos Wallet Starter ](https://github.com/aptos-labs/aptos-unity-starter)Example Unity project with an integration of the Aptos Unity SDK. # Wallet Adapter There are two wallet adapter standards in the Aptos ecosystem: 1. [Aptos Wallet Adapter](#aptos-wallet-adapter) by Aptos Labs 2. [OKX Connect](#okx-connect) by OKX ## Aptos Wallet Adapter [Section titled “Aptos Wallet Adapter”](#aptos-wallet-adapter) The Aptos Wallet Adapter by Aptos Labs provides a single interface for Aptos dapps and Aptos wallets to communicate with each other. For dapp developers, this means that you can connect to any Aptos wallet that is integrated with the Wallet Adapter without needing to write custom code for each wallet. This is described in [AIP-62](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-62.md), which defines the Modern Wallet Standard and autodetection of wallets. For Aptos wallet providers, integrating with AIP-62 means that your wallet will be compatible with any dapp that uses the Wallet Adapter. ### For Aptos Dapps [Section titled “For Aptos Dapps”](#for-aptos-dapps) Follow the [Wallet Adapter for Dapp Builders Guide](/build/sdks/wallet-adapter/dapp) on how to use the Wallet Adapter (via the [Wallet Adapter React package](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/packages/wallet-adapter-react)). ### For Aptos Wallet Providers [Section titled “For Aptos Wallet Providers”](#for-aptos-wallet-providers) Follow one of these guides for how to implement a Wallet Adapter plugin that dapps can connect to: 1. For [Browser Extension Wallets](/build/sdks/wallet-adapter/browser-extension-wallets) (ex. [Petra](https://chromewebstore.google.com/detail/petra-aptos-wallet/ejjladinnckdgjemekebdpeokbikhfci?hl=en)) 2. For [SDK Wallets](/build/sdks/wallet-adapter/wallets) (ex. [AptosConnect](https://aptosconnect.app)) ## OKX Connect [Section titled “OKX Connect”](#okx-connect) The OKX Connect adapter provides an interface for Aptos dapps to connect to OKX wallet and other wallets that support the OKX Connect standard. You can find more information about OKX Connect for Aptos dapps in the [OKX Connect documentation](https://web3.okx.com/build/dev-docs/sdks/app-connect-aptos) ## Other Resources [Section titled “Other Resources”](#other-resources) * [Dapp Builder Guide](/build/sdks/wallet-adapter/dapp) * [Wallet Browser Extension Guide](/build/sdks/wallet-adapter/browser-extension-wallets) * [SDK Wallet Builder Guide](/build/sdks/wallet-adapter/wallets) * [Modern Wallet Standard (AIP-62)](https://github.com/aptos-foundation/AIPs/blob/1bd0c41971701e54cf35da86c2877e58be61ee38/aips/aip-62.md) * [Wallet Adapter React Package](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/packages/wallet-adapter-react) * [Wallet Standard](https://github.com/aptos-labs/wallet-standard/tree/main) repo (with an example template for creating an AIP-62 Wallet plugin) * Core logic and react components for the [aptos-wallet-adapter](https://github.com/aptos-labs/aptos-wallet-adapter). # Wallet Adapter Plugin for Browser Extension Wallet Builders A wallet adapter plugin allows dapps to use your wallet. With the AIP-62 Wallet standard, dapps can simply update their version of `aptos-wallet-adapter` to connect to newly added Wallet plugins. Implementing a wallet plugin for a browser extension wallet has two main steps: 1. Implement a wallet adapter plugin for your browser extension. 2. Update the `aptos-wallet-adapter` package to let dapps know about your wallet. ## 1. Implement the Wallet Adapter Plugin. [Section titled “1. Implement the Wallet Adapter Plugin.”](#1-implement-the-wallet-adapter-plugin) You can use the [`wallet-standard`](https://github.com/aptos-labs/wallet-standard) repo’s example to implement an [AIP-62](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-62.md) compatible wallet adapter plugin that dapps can automatically recognize. Note For an example of how to implement the Wallet Adapter plugin (and how to register it), see the [Wallet Adapter Demo dapp](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/apps/nextjs-example). Specifically, [`standardWallet.ts`](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/apps/nextjs-example/src/utils/standardWallet.ts) contains the plugin implementation, and [`page.tsx`](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/apps/nextjs-example/src/app/page.tsx) has the `registerWallet` logic. 1. Copy the into your browser extension codebase. 2. Follow the instructions in that example to make it use your wallet to execute the AIP-62 functions. Note The full list of required functions for AIP-62 compatible wallets can be found [here](https://github.com/aptos-labs/wallet-standard/blob/38defe159b8641ff1763c4db61827c78ab448dab/src/detect.ts#L16). 3. Add a call to registerWallet with your plugin implementation so that it gets called on page load. This is what will notify dapps that your wallet is available. ```tsx // Put this function with your "MyWallet" implementation so it gets called on page load. (function () { if (typeof window === "undefined") return; const myWallet = new MyWallet(); registerWallet(myWallet); })(); ``` 4. Test your changes by going to the and trying to connect your wallet. 1. After your extension calls `registerWallet`, you should be able to click **“Connect a Wallet”** and see your wallet as an option. 1. You can then use the demo dapp features to verify your other wallet features work as expected. 2. **This simulates how a real dapp will interact with your browser extension.** 2. You can also test your implementations by updating [`standardWallet.ts`](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/apps/nextjs-example/src/utils/standardWallet.ts) from `MyWallet` to your wallet’s implementation, then running the [Wallet Adapter Demo dapp](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/apps/nextjs-example) locally. 1. See the Wallet Adapter Demo dapp [README.md](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/apps/nextjs-example) for instructions on how to run the demo locally. 2. In the demo, `registerWallet` is called from [`page.tsx`](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/apps/nextjs-example/src/app/page.tsx). *This is less realistic, as in practice your browser extension should be calling `registerWallet`.* 5. Publish the new version of your browser extension. ## 2. Update [`wallet-adapter-core`](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/packages/wallet-adapter-core) to know about your extension. [Section titled “2. Update wallet-adapter-core to know about your extension.”](#2-update-wallet-adapter-core-to-know-about-your-extension) In order for dapp users who are not already using your wallet to get the option to create an account with your wallet, you need to update [`wallet-adapter-core`](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/packages/wallet-adapter-core) with your browser extension’s download link. 1. Fork the monorepo. () 2. Open your fork in a local editor such as VSCode. 3. Create a new branch for your changes. ```shellscript git checkout -b your-wallet ``` 4. Navigate to . 5. Add your wallet’s details to by following the AptosStandardSupportedWallet interface. ```tsx export interface AptosStandardSupportedWallet { // The name of your wallet cast to WalletName (Ex. "Petra" as WalletName<"Petra">) name: WalletName; // The link to your chrome extension or main website where new users can create an account with your wallet. url: string; // An icon for your wallet. Can be one of 4 data types. Be sure to follow the below format exactly (including the "," after base64). icon: `data:image/${"svg+xml" | "webp" | "png" | "gif"};base64,${string}`; // Copy this exactly readyState: WalletReadyState.NotDetected; // Copy this exactly isAIP62Standard: true; } ``` For example: ```tsx { name: "Petra" as WalletName<"Petra">, url: "https://chromewebstore.google.com/detail/petra-aptos-wallet/ejjladinnckdgjemekebdpeokbikhfci?hl=en", icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAWbSURBVHgB7Z09c9NYFIaPlFSpUqQNK6rQhbSkWJghLZP9BesxfwAqytg1xe7+AY+3go5ACzObBkpwSqrVQkuRCiqkva8UZW1je22wpHPveZ8ZRU6wwwznueee+6FLJCuSdzrb7nZTNjaOJc9/ctdNiaJESPPkeeq+phLH5/L162k0HJ7JikTLvtEFPnFBf+D+0l/dt9tCNJK6xnjmZOg7GdJlPvC/AhQtPo5P3MsHQvwhiobLiLBQABf82y74z4Qt3ldSybKHToLTeW+I5/1B3u2euOD/JQy+zyRowEUs5zAzA1x+oCckJHrRYNCf/uE3AjD4QfONBBMC5PfvY2j3TEi4ZNmd8eHilQDFMK/s8xMhIXPhJLjuJLjAN/8VgRsbPWHwLbAtm5tXRWGRAS5b/99C7FBmgbTMAGXrJ5aIomJir8wA3S5afyLEEkUtEBezfQy+RYpFvdilgmMhNnGxRw2wL8QqScy1fMNE0T4yQCLEKkksxDQUwDj2BNjbK69pdndn/zxwNsUCCOyNGyJ374psbYkMBiLv30++59o1kW5X5NMnkdFI5OXL8nXghCsAAn10NL/Fz2NnpxQFFyR5/bq8BypDWAIg6AcHIoeH60nn4/K8e1deECIgwhAAQULQEXxIUAf43bju3ZvMDJ7jrwDT/XpToIvABeECqBf8EuB7+/W6CKBe0C/Auvv1uv C0XtArQBP9el14VC/oEqCtfr0uPKgX2hdAW79eF0rrhfYFQPCRKi1RyY4ZyZYF4GKQcSiAcSiAcSiAcSiAcSiAcSiAcSiAcSiAcSiAcSiAcSiAcSiAcShAm3z+LG1DAdqEAhjn40dpGwrQFtgIwgxgGAWtH1CAtsC2cQVQgLZQsk2cArSBoqeHKEAbKHpiiAI0DVq+kv4fUICmQetXMPyroABNgtb/5o1oggI0icJzBChAUyDwr16JNihAUzx+LBqhAE3w5InaU0MoQN08f64y9VdQgDrBkO/FC9EMBagLBB/P/yvHxlGxTYPh3tOn4gMUYN2g4FPc509DAdYFqvxZh1ArhwKsg6rSVzTHvywU4EeoqnyPTxKnAKuCVo4iD4s6ARwhTwGWoTrk8e3bIE4IH4cCVCDI1U6dL1/K73Eh4B727ctCASoQ6MBa9zJwJtA4FMA4FMA4FMA4FMA4FMA4FMA4FMA47Qtg4P/n1Uz7AgQ8zeoD7Qug5KQMq+joApgFWkNHEWhwEUYLFMA4OgRQdGCCNXQIUG28II2jZyKIWaAV9Aig7OgUK+gRAMH36ImaUNC1FoDt1swCjaJLAAQfT9mQxtC3GohugCOCxtC5HIyHLNkVNIJOATAv4Mnz9b6jd0MIhoWsB2pH944gPHmLkQGpDf1bwtAVUILa8GNPICRgd1AL/mwKRXfA0cHa8WtXMArDfp8bSdeIf9vCEfxHj8psQBF+GH/PB0A2wIzhrVsih4ciOztCVsfvAyKQAVAbYPr44EDk6Ehkd1fI8oRxQggKQ2QEXMgEe3ulELhvbQmZT3hHxFRn+1Tn/UAAZAWIUXUTHz4IKQn/jCBkB6Pn/ywDHw41DgUwDgRIhVgljSWKzoXYJM+dAFmWCrHKeewsOBViExd71AAjd10IsUYaDYdnsfty4Uz4U4g1zvClHAbm+e9CbJFlfdwKAVwWSJ0EfwixwrCIuYxPBOV5T1gLWCCtWj+4EqCoBbLsFyFhk2UPq9YPJqaCURW6W19IqPRdjCeG/dGsd+Xdbs/dToSERD8aDHrTP4zmvZsSBMXM4INo0afyTudY4vg39zIR4iNFXXfZtc9k4XJw0V9k2R1OFHkIhvVZdn1R8MHCDDDx+zqdxK0c9tz1szAjaKWc1XUTe+OV/iKWFmAcJ8NtJ8Kxe7kvkCGKEiHN45Zz3b/9yN3/uVzUGxXD+RX4F56985hsqA6SAAAAAElFTkSuQmCC", readyState: WalletReadyState.NotDetected, isAIP62Standard: true, } ``` 6. In type.ts, update the type AvailableWallets to include your wallet’s name. ```tsx export type AvailableWallets = "Nightly" | "Petra" | "T wallet" | "Your Wallet's Name"; ``` 7. Update the at the top-level of the aptos-wallet-adapter to include your wallet in the list of AIP-62 compatible wallets. 8. Commit and push your changes to your fork. Note If you’ve pushed your changes to your fork, a green button should appear at the top of the [`aptos-wallet-adapter`](https://github.com/aptos-labs/aptos-wallet-adapter) repo asking if you would like to create a pull request. 9. Follow to open a pull request for the repo. ## Resources [Section titled “Resources”](#resources) * Wallet Adapter Demo App * [Live site](https://aptos-labs.github.io/aptos-wallet-adapter) * [Source code](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/apps/nextjs-example) * See [`standardWallet.ts`](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/apps/nextjs-example/src/utils/standardWallet.ts) for an example implementation of an AIP-62 compatible wallet-adapter plugin. * [`wallet-standard`](https://github.com/aptos-labs/wallet-standard) source code. * [`wallet-adapter-core`](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/packages/wallet-adapter-core) source code. * [AIP-62 standard](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-62.md). # Wallet Adapter for Dapp Builders Aptos provides a React `Provider` and `Context` for connecting Aptos wallets to your dapp. This `Provider` allows you to specify which Wallets you want to allow connections to. Then you can use the `Provider` to look up account information and sign transactions / messages. This provides a standard interface for using all Aptos wallets, and allows new wallets to easily be supported just by updating your React Wallet Adapter dependency version. ## Using the React `Provider` and `Context` [Section titled “Using the React Provider and Context”](#using-the-react-provider-and-context) 1. Install @aptos-labs/wallet-adapter-react. ```shellscript npm install @aptos-labs/wallet-adapter-react ``` **For versions prior to v4.0.0** ### (Optional) Install the plugins for any “Legacy Standard Compatible” Wallets you want to support from [this list](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/README.md#supported-wallet-packages). [Section titled “(Optional) Install the plugins for any “Legacy Standard Compatible” Wallets you want to support from this list.”](#optional-install-the-plugins-for-any-legacy-standard-compatible-wallets-you-want-to-support-from-this-list) Note The more modern [AIP-62](https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-62.md) wallets do NOT require installing a package - they work by default! The legacy standard required installing plugins manually. For wallets that have not updated to using the AIP-62 standard, their plugins must be installed and passed in to the `Provider` manually. For example: ```shellscript npm i @okwallet/aptos-wallet-adapter ``` ### In `App.tsx` or it’s equivalent, import the Aptos Wallet Adapter and any legacy Wallet plugins. [Section titled “In App.tsx or it’s equivalent, import the Aptos Wallet Adapter and any legacy Wallet plugins.”](#in-apptsx-or-its-equivalent-import-the-aptos-wallet-adapter-and-any-legacy-wallet-plugins) ```tsx import { AptosWalletAdapterProvider } from "@aptos-labs/wallet-adapter-react"; // Import any additional wallet plugins. Ex. import { OKXWallet } from "@okwallet/aptos-wallet-adapter"; // ... ``` 2. Initialize the AptosWalletAdapterProvider. You can use any of the following optional fields. It is recommended to: 1. Set `autoConnect` to `true`. 2. Set the `dappConfig` with: * The `network` property set to the network your dapp works with * The `aptosApiKeys` property set to the generated Api Key for the specified network | Field | Description | Example | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | `autoConnect` | A prop indicates whether the dapp should auto connect with the most recently connected wallet on page reload. | `true` | | `dappConfig` | Specify an alternate network to work on. This prop only works for wallets which are NOT chrome extensions. If set, this object must include the name of the network the app is connected to. The object may include a aptosConnectDappId. | `{ network: 'mainnet', aptosApiKeys:{}, aptosConnectDappId: undefined }` | | `onError` | A callback function to fire when the adapter throws an error. | `(error) => { console.log("error", error); }` | #### Full Example [Section titled “Full Example”](#full-example) ```tsx import { AptosWalletAdapterProvider } from "@aptos-labs/wallet-adapter-react"; import { PropsWithChildren } from "react"; import { Network } from "@aptos-labs/ts-sdk"; export const WalletProvider = ({ children }: PropsWithChildren) => { return ( { console.log("error", error); }} > {children} ); }; ``` 3. Import useWallet in files where you want to access data from the Provider. ```tsx import { useWallet } from "@aptos-labs/wallet-adapter-react"; // Access fields / functions from the adapter const { account, connected, wallet, changeNetwork } = useWallet(); ``` # Choose a UI Package [Section titled “Choose a UI Package”](#choose-a-ui-package) The [Wallet Adapter repository](https://github.com/aptos-labs/aptos-wallet-adapter) provides several UI packages to simplify allowing users to connect and select a wallet. For UI components that work out of the box, but are less customizable, choose one of: * [Ant Design](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/packages/wallet-adapter-ant-design) * [MUI](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/packages/wallet-adapter-mui-design) (Material UI) Otherwise, you should use the [shadcn/ui wallet selector](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/apps/nextjs-example/README.md#use-shadcnui-wallet-selector-for-your-own-app), as it has the most customization options. For more details on how to customize this wallet selector or build your own wallet selector, see [this guide](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/packages/wallet-adapter-react/docs/BYO-wallet-selector.md). Note For an example that shows how these UI options work in practice, see the [live demo app](https://aptos-labs.github.io/aptos-wallet-adapter/) (you can find its reference code [here](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/apps/nextjs-example)). ## `useWallet` Fields and Functions [Section titled “useWallet Fields and Functions”](#usewallet-fields-and-functions) ### Fields [Section titled “Fields”](#fields) | Field | Type | Description | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `connected` | `boolean` | Indicates if the wallet is currently connected. | | `isLoading` | `boolean` | Indicates if a wallet operation is currently loading. | | `account` | `{ address: string; publicKey: string \| string[]; minKeysRequired?: number; ansName?: string \| null; } \| null` | Current account info or null if no account is connected. | | `network` | `{ name: Network; chainId?: string; url?: string; } \| null` | Current network info or null if no network is selected. | | `wallet` | `{ name: WalletName; icon: string; url: string; } \| null` | Current wallet info or null if no wallet is selected. Includes wallet name, icon, and URL. | | `wallets` | `ReadonlyArray<{ name: WalletName; url: string; icon: string; readyState: WalletReadyState.NotDetected; isAIP62Standard: true; }>` | List of available wallets, including standard supported ones, each with name, URL, icon, readiness state, and AIP62 standard compliance indication. | ### Functions [Section titled “Functions”](#functions) *See [`WalletCore.ts`](https://github.com/aptos-labs/aptos-wallet-adapter/blob/main/packages/wallet-adapter-core/src/WalletCore.ts) in [`wallet-adapter-core`](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/packages/wallet-adapter-core) for where these functions are implemented.* | Function | Signature | Description | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `connect` | `connect(walletName: WalletName): void` | Connects to the specified wallet by its name. | | `disconnect` | `disconnect(): void` | Disconnects the currently connected wallet. | | `signTransaction` | `signTransaction(transactionOrPayload: AnyRawTransaction \| Types.TransactionPayload, asFeePayer?: boolean, options?: InputGenerateTransactionOptions): Promise` | Signs a transaction with optional parameters for fee payment. | | `submitTransaction` | `submitTransaction(transaction: InputSubmitTransactionData): Promise` | Submits a transaction with the provided transaction data. | | `signAndSubmitTransaction` | `signAndSubmitTransaction(transaction: InputTransactionData): Promise` | Signs and submits a transaction with the given input data. | | `signMessage` | `signMessage(message: SignMessagePayload): Promise` | Signs a message and returns the signature and other response info. | | `signMessageAndVerify` | `signMessageAndVerify(message: SignMessagePayload): Promise` | Signs a message and verifies the signer. | | `changeNetwork` | `changeNetwork(network: Network): Promise` | Requests a change in the connected network. This is not supported by all wallets. | ## Code Examples [Section titled “Code Examples”](#code-examples) See the next.js example dapp for a demonstration of how these components are used in practice: * [Live site](https://aptos-labs.github.io/aptos-wallet-adapter/) * [Source code](https://github.com/aptos-labs/aptos-wallet-adapter/tree/main/apps/nextjs-example) ### `wallets` [Section titled “wallets”](#wallets) `wallets` is a list of available wallets, including standard supported ones, each with name, URL, icon, readiness state, and AIP62 standard compliance indication. ```tsx import { useWallet } from '@aptos-labs/wallet-adapter-react'; const displayInstalledWalletsDemo = () => { const { wallets } = useWallet(); return (
{wallets.map(wallet => { return

{wallet.name}

})}
) } ``` #### Support for Uninstalled Wallets [Section titled “Support for Uninstalled Wallets”](#support-for-uninstalled-wallets) Following AIP-62, the adapter uses an event-based communication model between a wallet and a dapp. This means only wallets installed in the user’s browser are detected automatically and available for use. To support the full Aptos wallet ecosystem, the adapter maintains a registry of supported wallets—allowing dapps to also display uninstalled wallets. It also exposes a utility function to easily manage all wallets. ```tsx import { useWallet, groupAndSortWallets } from '@aptos-labs/wallet-adapter-react'; const displayAllWalletsDemo = () => { const { wallets = [], notDetectedWallets = [] } = useWallet(); const { aptosConnectWallets, availableWallets, installableWallets } = groupAndSortWallets( [...wallets, ...notDetectedWallets] ); return (
/** Wallets that use social login to create an account on the blockchain */ {aptosConnectWallets.map((aptosConnectwallet) => ( return

{aptosConnectwallet.name}

))} /** Wallets that are currently installed or loadable. */ {availableWallets.map((availableWallet) => ( return

{availableWallet.name}

))} /** Wallets that are NOT currently installed or loadable. */ {installableWallets.map((installableWallet) => ( return

{installableWallet.name}

))}
) } ``` ### `connect()` and `disconnect()` [Section titled “connect() and disconnect()”](#connect-and-disconnect) `connect()` establishes a connection between the dapp and a Wallet. You can then use `disconnect()` to ```tsx import React from 'react'; import { WalletName, useWallet } from '@aptos-labs/wallet-adapter-react'; const WalletConnectDemo = () => { const { connect, disconnect, account, connected } = useWallet(); const handleConnect = async () => { try { // Change below to the desired wallet name instead of "Petra" await connect("Petra" as WalletName<"Petra">); console.log('Connected to wallet:', account); } catch (error) { console.error('Failed to connect to wallet:', error); } }; const handleDisconnect = async () => { try { await disconnect(); console.log('Disconnected from wallet'); } catch (error) { console.error('Failed to disconnect from wallet:', error); } }; return (

Aptos Wallet Connection

{connected ? (

Connected to: {account?.address}

) : ( )}
); }; export default WalletConnectDemo; ``` ### `signAndSubmitTransaction` [Section titled “signAndSubmitTransaction”](#signandsubmittransaction) If you would like to separate out these steps, you can use `signTransaction` and `submitTransaction` separately instead. ```tsx import React from 'react'; import { useWallet } from '@aptos-labs/wallet-adapter-react'; import { Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk'; const config = new AptosConfig({ network: Network.MAINNET }); const aptos = new Aptos(config); const SignAndSubmit = () => { const { account, signAndSubmitTransaction } = useWallet(); const onSignAndSubmitTransaction = async () => { if(account == null) { throw new Error("Unable to find account to sign transaction") } const response = await signAndSubmitTransaction({ sender: account.address, data: { function: "0x1::aptos_account::transfer", functionArguments: [account.address, 1], }, }); // if you want to wait for transaction try { await aptos.waitForTransaction({ transactionHash: response.hash }); } catch (error) { console.error(error); } }; return ( ); }; export default SignAndSubmit; ``` `signMessage` and `verifyMessage` You can also use the shorthand `signAndVerifyMessage` to create a message which can be verifiably from the connected wallet. ```tsx import React, { useState } from 'react'; import { useWallet } from '@aptos-labs/wallet-adapter-react'; const SignMessageDemo = () => { const { signMessage, signMessageAndVerify, connected, account } = useWallet(); const [message, setMessage] = useState(''); const [nonce, setNonce] = useState(''); const [signedMessage, setSignedMessage] = useState(null); const [verificationResult, setVerificationResult] = useState(null); const [error, setError] = useState(null); const handleSignMessage = async () => { setError(null); try { const response = await signMessage({ message, nonce }); setSignedMessage(response); } catch (err: any) { setError(`Failed to sign message: ${err.message}`); } }; const handleVerifyMessage = async () => { setError(null); try { const result = await signMessageAndVerify({ message, nonce }); setVerificationResult(result); } catch (err: any) { setError(`Failed to verify message: ${err.message}`); } }; return (

Aptos Sign and Verify Message

{connected ? (

Connected to: {account?.address}