# Movement Network Documentation — Full Text > Full text of every documentation page, concatenated for LLMs and AI tools. - Concise index: https://docs.movementnetwork.xyz/llms.txt - Movement main site index: https://movementnetwork.xyz/llms.txt --- # API Documentation URL: /api # Movement Node API The Movement Full Node API is a RESTful API for client applications to interact with the Movement blockchain. Use this section of the documentation to review all requests that can be made to the full node. ## Contact **Move Industries:** URL: [https://github.com/movement-network/movement](https://github.com/movement-network/movement) ## License Apache 2.0 # FAQ URL: /devs/faq # FAQ Frequently asked questions about working with Movement as a developer. ## What is Movement Network? Movement Network makes the Move programming language accessible to all via our Layer 1 Move Blockchain. ## Is Mainnet/Testnet live? Currently we are live on mainnet and the latest information on networks can be found [here](/devs/networkEndpoints). ## How do I get started building? Our [developer documentation](/devs) will help you through finding exactly what you need in order to start building on Movement. ## How can I get help? Make sure to join our [Discord](https://discord.com/invite/moveindustries) and get yourself a developer role. You can ask any questions you have in dev-chat where someone in the community or our Developer Relations team will be able to answer your questions. ## What are the hardware requirements for running a node? For running a Movement follower node, we recommend: * CPU: 16 cores * RAM: 64GB * Storage: 4TB NVMe SSD * Network: 2.5Gbps However, minimum requirements are 8 cores, 32GB RAM, and 2TB storage. ## What programming languages are supported on Movement? Movement supports the Move programming language. ## How does Movement ensure security? Movement implements multiple security measures: * Secure Move VM implementation * Regular security audits * Strong consensus mechanism * Robust validator network ## What tools are available for developers? Movement provides a comprehensive suite of developer tools: * Movement CLI * SDK support for multiple languages * Development frameworks * Testing environments * Block explorers * Documentation and tutorials # Your First Move Contract URL: /devs/firstMoveContract Welcome to your first journey in deploying a Move module (smart contract) on the Movement Testnet. In the Move programming language, smart contracts are referred to as **modules**. This guide will walk you through each step, ensuring a smooth experience from setup to deployment. ## Prerequisites Before we begin, ensure you have one of the following command-line interfaces (CLIs) installed: * **Movement CLI**: [Installation Guide](/devs/movementcli) * **Aptos CLI**: [Installation Guide](https://aptos.dev/en/build/cli#-install-the-aptos-cli) **First Time Users:** If this is your first time following this tutorial, we recommend using the **Movement CLI testnet build** for the best experience. The Movement CLI is specifically configured for Movement networks and will make the setup process smoother. This tutorial uses the **Movement CLI**, but the commands are identical for the Aptos CLI—just replace `movement` with `aptos` in the commands. If you're using the Aptos CLI, you'll need to configure it for a custom network during the initialization process. *** ## Step 1: Scaffold the Project First, let's set up the structure for your Move module. ### Create Your Project Directory Open your command prompt or terminal, create a new directory for your project, and navigate into it: ```bash mkdir hello_blockchain cd hello_blockchain ``` ### Initialize the Move Project Now that you are inside your project directory, run the following command to initialize the Move project structure: ```bash movement move init --name hello_blockchain ``` This command creates the necessary folders and files for your Move project: ``` Move.toml sources/ scripts/ tests/ ``` * **Move.toml**: Configuration file for your Move project. * **sources/**: Directory where you'll write your Move modules. * **scripts/**: Directory for transaction scripts (optional). * **tests/**: Directory for writing unit tests. *** ## Step 2: Initialize the CLI If you are using Aptos CLI - ensure that you are using version 3.5 or lower. You can replace all `movement` commands with `aptos` in the following steps. Now, initialize the CLI with the following command: ```bash movement init ``` **Alternative:** You can also use the following command to skip the interactive prompts: ```bash movement init --network custom --rest-url https://testnet.movementnetwork.xyz/v1 --faucet-url https://faucet.testnet.movementnetwork.xyz/ ``` ### CLI Prompts 1. **Choose Network** You'll be prompted to select a network: ```bash Choose network from [devnet, testnet, local, custom | defaults to devnet]: ``` Type `custom` and press **Enter**. You will be promted for the following details from our [Network EndPoints](/devs/networkEndpoints) * RPC/Rest Endpoint: [https://testnet.movementnetwork.xyz/v1](https://testnet.movementnetwork.xyz/v1) * Faucet Endpoint: [https://faucet.testnet.movementnetwork.xyz/](https://faucet.testnet.movementnetwork.xyz/) 1. **Enter Private Key** Next, you'll be asked to enter your private key: ```bash Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]: ``` * If you have an existing private key, enter it now. * If not, simply press **Enter** to generate a new one. ### Initialization Success After completing the prompts, you should see a message like: ```bash Movement CLI is now set up for account 0xYOUR_ACCOUNT_ADDRESS as profile default! See the account here: https://explorer.movementnetwork.xyz/account/0xYOUR_ACCOUNT_ADDRESS?network=bardock+testnet Run `movement --help` for more information about commands { "Result": "Success" } ``` Note that Bardock Explorer is available here: [https://explorer.movementnetwork.xyz/?network=bardock+testnet](https://explorer.movementnetwork.xyz/?network=bardock+testnet) *** ## Step 3: Fund Your Account (If Needed) ## Step 2: Fund Your Account Your account should already be funded with testnet tokens after initialization. However, if you need additional funds for your wallet, you can use the faucet. 1. **Copy Your Account Address** Make note of your account address from the initialization success message (e.g., `0xYOUR_ACCOUNT_ADDRESS`). 2. **Visit the Faucet (Optional)** If you need additional testnet tokens, go to the [Movement Web Faucet](https://faucet.movementnetwork.xyz/) to fund your account. 3. **Request Tokens** Paste your account address into the faucet and request testnet tokens. *** ## Step 4: Explore the Configuration Your project directory now contains a hidden `.movement` folder with a `config.yaml` file: ``` .movement/ └── config.yaml ``` This configuration file stores information about your default profile, including your private key and network settings. ```yaml --- profiles: default: network: Testnet private_key: "YOUR_PRIVATE_KEY" public_key: "YOUR_PUBLIC_KEY" account: "YOUR_ACCOUNT_ADDRESS" rest_url: "https://testnet.movementnetwork.xyz/v1" faucet_url: "https://faucet.testnet.movementnetwork.xyz/" ``` **Warning:** **Do not commit `config.yaml` to version control systems like GitHub, as it contains your private key!** *** ## Step 5: Write Your First Module ### Create the Module File Navigate to the `Sources/` directory and create a new file named `hello_blockchain.move`: ```bash cd Sources touch hello_blockchain.move ``` ### Add the Module Code Open `hello_blockchain.move` in your preferred text editor and paste the following code: ```move module hello_blockchain::message { use std::error; use std::signer; use std::string::{String}; use aptos_framework::account; use aptos_framework::event; struct MessageHolder has key { message: String, message_change_events: event::EventHandle, } struct MessageChangeEvent has drop, store { from_message: String, to_message: String, } /// Error code indicating no message is present. const ENO_MESSAGE: u64 = 0; #[view] public fun signature(): address { @hello_blockchain } #[view] public fun get_message(addr: address): String acquires MessageHolder { assert!(exists(addr), error::not_found(ENO_MESSAGE)); borrow_global(addr).message } public entry fun set_message(account: signer, message: String) acquires MessageHolder { let account_addr = signer::address_of(&account); if (!exists(account_addr)) { move_to(&account, MessageHolder { message, message_change_events: account::new_event_handle(&account), }); } else { let message_holder = borrow_global_mut(account_addr); let from_message = message_holder.message; event::emit_event(&mut message_holder.message_change_events, MessageChangeEvent { from_message, to_message: copy message, }); message_holder.message = message; } } #[test(account = @0x1)] public entry fun sender_can_set_message(account: signer) acquires MessageHolder { let addr = signer::address_of(&account); aptos_framework::account::create_account_for_test(addr); set_message(account, std::string::utf8(b"Hello, Blockchain")); assert!( get_message(addr) == std::string::utf8(b"Hello, Blockchain"), ENO_MESSAGE ); } #[test] public fun signature_okay() { assert!(signature() == @hello_blockchain, ENO_MESSAGE); } } ``` This is a "Hello, Blockchain" module written in Move. We won't dive into the syntax and functionality in this tutorial. *** ## Step 6: Compile the Module Let's compile your module to ensure everything is set up correctly. ### Option 1: Without Editing `Move.toml` If you haven't modified the `Move.toml` file, run: ```bash movement move compile --named-addresses hello_blockchain=default ``` ### Option 2: By Editing `Move.toml` To simplify future commands, you can add your account address to `Move.toml`. 1. **Open `Move.toml`** and add: ```toml [addresses] hello_blockchain = "0xYOUR_ACCOUNT_ADDRESS" ``` 2. **Compile without Extra Flags** Now, you can compile with: ```bash movement move compile ``` ### Expected Output If the compilation is successful, you'll see something like: ```json { "Result": [ "a345dbfb0c94416589721360f207dcc92ecfe4f06d8ddc1c286f569d59721e5a::message" ] } ``` *** ## Step 7: Test the Module Your module includes unit tests. Let's run them to ensure everything works as expected. ### Run Tests ```bash movement move test ``` > **Note:** If you didn't edit `Move.toml`, add the `--named-addresses` flag: ```bash movement move test --named-addresses hello_blockchain=default ``` ### Expected Output You should see output similar to: ``` Running Move unit tests [ PASS ] 0x4bb138fa05ea42faa44268b30872ed6e5a84f25f8718bcac981a6de36a090e3a::message::sender_can_set_message [ PASS ] 0x4bb138fa05ea42faa44268b30872ed6e5a84f25f8718bcac981a6de36a090e3a::message::signature_okay Test result: OK. Total tests: 2; passed: 2; failed: 0 { "Result": "Success" } ``` *** ## Step 8: Publish the Module Now it's time to deploy your module to the Movement Testnet. ### Publish Command If you edited `Move.toml`: ```bash movement move publish ``` If you didn't edit `Move.toml`: ```bash movement move publish --named-addresses hello_blockchain=default ``` ### Optimizing Package Size (Optional) To reduce gas costs and package size, you can publish without including artifacts: ```bash movement move publish --named-addresses hello_blockchain=default --included-artifacts none ``` The `--included-artifacts` option controls what gets included in your package: * **`none`**: Most compact, includes only bytecode (lowest gas cost) * **`sparse`**: Minimal artifacts needed to reconstruct source (default, \~2x size of `none`) * **`all`**: All available artifacts (\~3-4x size of `none`) Using `none` significantly reduces deployment costs but means you cannot reconstruct the source package from the blockchain. This option is particularly useful for deployment of smart contracts without deploying the entire codebase. ### Confirm Deployment Cost You'll be prompted to confirm the transaction and spend a small amount of testnet tokens: ``` Do you want to submit this transaction? [Y/n] ``` Type `Y` and press **Enter**. > **Ensure you have sufficient testnet tokens from the faucet before proceeding.** ### Expected Output Upon successful deployment, you'll receive a confirmation: ``` Transaction submitted: https://explorer.movementnetwork.xyz/txn/0xTRANSACTION_HASH?network=bardock+testnet { "Result": { "transaction_hash": "0xTRANSACTION_HASH", "gas_used": 1696, "gas_unit_price": 100, "sender": "0xYOUR_ACCOUNT_ADDRESS", "sequence_number": 0, "success": true, "timestamp_us": 1726483247259754, "version": 183468408, "vm_status": "Executed successfully" } } ``` *** ## Step 9: Interact with Your Contract Congratulations! You've successfully deployed your first Move module on the Movement Testnet. * **View Your Module**: Visit the provided transaction link to see details on the explorer. * **Interact**: Use the Movement CLI or write scripts to interact with your deployed module. *** ## Recap In this tutorial, you've: * Scaffolded a new Move project. * Initialized the Movement CLI. * Funded your testnet account. * Written and compiled a Move module. * Ran unit tests to ensure functionality. * Deployed your module to the testnet. *** We're excited to see you continue on journey into the Move language! **Happy coding!** # Developer Documentation URL: /devs Build your first Move Module Install and use the Movement CLI Start building apps on the Movement Network All the endpoints you need to get started Get Testnet tokens Our Block Explorer # Indexer URL: /devs/indexing The Movement Indexer is a GraphQL API you can use to retrive aggregate data, historical data, and data that might be hard to get from the simpler full node API. | Service | URL | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | API Explorer (testnet) | [https://cloud.hasura.io/public/graphiql?endpoint=https://indexer.testnet.movementnetwork.xyz/v1/graphql](https://cloud.hasura.io/public/graphiql?endpoint=https://indexer.testnet.movementnetwork.xyz/v1/graphql) | | GraphQl Endpoint (testnet) | [https://indexer.testnet.movementnetwork.xyz/v1/graphql](https://indexer.testnet.movementnetwork.xyz/v1/graphql) | | API Explorer (mainnet) | [https://cloud.hasura.io/public/graphiql?endpoint=https://indexer.mainnet.movementnetwork.xyz/v1/graphql](https://cloud.hasura.io/public/graphiql?endpoint=https://indexer.mainnet.movementnetwork.xyz/v1/graphql) | | GraphQl Endpoint (mainnet) | [https://indexer.mainnet.movementnetwork.xyz/v1/graphql](https://indexer.mainnet.movementnetwork.xyz/v1/graphql) | ## Third Party Indexers | Service | URL | | ------- | -------------------------------------------------------------------------------------------------------- | | Sentio | [https://rpc.sentio.xyz/movement-indexer/v1/graphql](https://rpc.sentio.xyz/movement-indexer/v1/graphql) | ## Architecture There are three main components to indexing with the Movement Network. We first have the Movement full node which provides a gRPC stream of transactions. The gRPC stream of transactions is consumed by the Transaction Streaming Service which includes 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. * [**Indexer API**](https://github.com/aptos-labs/aptos-indexer-processors): Consumes the data-service providing a GraphQL API to dApps and other clients wishing to query the network. The Indexer API also allows the development of customized processors. ## Running the Transaction Streaming Service The following guides from Aptos are provided: [Aptos Documentation](https://aptos.dev/en/build/indexer/txn-stream/local-development) ## Indexing Movement - Future Plans Move Industries plans to provide a hosted Transaction Stream Service in the near future. In the meantime, anyone wishing to index the Movement network would need to self-host their own Transaction Streaming Service. ## Providing a GraphQL API With the Data Service running, the Indexer API can be configured to consume it as per the following [repository](https://github.com/aptos-labs/aptos-indexer-processors/) to provide a GraphQL API to downstream clients: [**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. ## Example Queries ### NFT Queries #### 1. Get all NFTs owned by an address with their collection info ``` query GetUserNFTs { current_token_ownerships_v2( where: { owner_address: {_eq: "0x123..."}, amount: {_gt: 0} } ) { token_data_id amount current_token_data: current_token_data { token_name token_uri token_properties collection_id current_collection: current_collection { collection_name creator_address description uri } } } } ``` #### 2. Get Recent NFT Sales/Transfers with Price Info ``` query GetRecentNFTSales { token_activities_v2( where: { type: {_in: ["0x3::token::DepositEvent", "0x3::token::WithdrawEvent"]} }, order_by: {transaction_timestamp: desc}, limit: 50 ) { transaction_version transaction_timestamp from_address to_address token_amount current_token_data { token_name collection_id current_collection { collection_name } } } } ``` #### 3. Get Collection Statistics ``` query GetCollectionStats { current_collections_v2( where: { collection_id: {_eq: "0x123..."} } ) { collection_name creator_address current_supply max_supply description uri # Get ownership distribution current_token_ownerships_v2_aggregate( where: { amount: {_gt: 0} } ) { aggregate { count(distinct: true) } } } } ``` ### DeFi Queries #### 1. Get User's Token Balances ``` query GetUserTokenBalances { current_fungible_asset_balances( where: { owner_address: {_eq: "0x123..."}, amount: {_gt: 0} } ) { asset_type amount last_transaction_timestamp metadata } } ``` #### 2. Track Large Token Transfers ``` query GetLargeTokenTransfers { fungible_asset_activities( where: { amount: {_gt: "1000000000"}, # Adjust threshold as needed type: {_in: ["0x1::coin::WithdrawEvent", "0x1::coin::DepositEvent"]} }, order_by: {transaction_timestamp: desc}, limit: 100 ) { transaction_version transaction_timestamp amount asset_type type owner_address is_transaction_success } } ``` #### 3. Get Token Activity History for an Address ``` query GetAddressTokenHistory { account_transactions( where: { account_address: {_eq: "0x123..."} }, order_by: {transaction_version: desc} ) { transaction_version fungible_asset_activities { amount asset_type type transaction_timestamp } } } ``` # Indexer Onboarding ## Indexer Infrastructure Movement Network's RPC provides a stable API. However, for those seeking efficient querying of on-chain states for applications, Move Industries provides an indexing service for Movement Network. The Movement Network Indexer API is based on the [Aptos Indexer API](https://aptos.dev/en/build/indexer) and will support all its features including GraphQL queries. ## How to deploy a Movement Indexer on AWS with Docker Compose This is a high level guide. It will not dive into all AWS infrastructure details, only what is relevant for the Movement Indexer. ## Prerequisites * A running a Movement Full Node that serves Aptos gRPC. In this example [https://mainnet.movementnetwork.xyz/v1](https://mainnet.movementnetwork.xyz/v1) and listens to indexer queries on port 30734. ### 1. Create EC2 instance 1. Ec2 Instate details 2. Machine type `c5.4xlarge`: 16 vCPU, Memory 32 GB 3. Chose `ubuntu` as image type 4. Disk size: 100 GB should be more then enough 5. VPC For now create the EC2 instance in the same VPC with your Movement Full Node. #### Configure connectivity to EC2 instance to use [AWS SSM](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html) 1. Add IAM role `AwsEc2SsmRole` 2. Expand Advanced section 3. In User data add custom startup script. ```bash #!/bin/bash set -e sudo snap install amazon-ssm-agent --classic sudo systemctl start snap.amazon-ssm-agent.amazon-ssm-agent sudo systemctl enable snap.amazon-ssm-agent.amazon-ssm-agent ``` Note: To connect to the EC2 instance, configure `aws cli` first and then: ```bash INST_ID="" AWS_REGION="" aws ssm start-session --target "${INST_ID}" --region "${AWS_REGION}" --document-name AWS-StartInteractiveCommand --parameters command=bash -l ``` ### 2. Install required software #### [docker and docker compose](https://docs.docker.com/engine/install/ubuntu/) [https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) Set up Docker's apt repository ```bash # Add Docker's official GPG key: sudo -i apt-get update apt-get install ca-certificates curl install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ tee /etc/apt/sources.list.d/docker.list > /dev/null apt-get update ``` Install the Docker packages ```bash apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` Verify that the installation is successful by running the hello-world image: ```bash docker run hello-world ``` #### [grpcurl (used for connectivity testing)](https://github.com/fullstorydev/grpcurl/releases/tag/v1.9.2) #### `postgresql-client` (used for connectivity testing) ```bash apt-get install -y postgresql-client ``` ```bash wget https://github.com/fullstorydev/grpcurl/releases/download/v1.9.2/grpcurl_1.9.2_linux_amd64.deb dpkg -i grpcurl_1.9.2_linux_amd64.deb ``` #### Clone movement repo ```bash HOME=/home/ssm-user cd "${HOME}" git clone https://github.com/movement-network/movement/ ``` ### 3. Create required config directories and files ```bash HOME=/home/ssm-user DOT_MOVEMENT_PATH="${HOME}/.movement" mkdir -p "${DOT_MOVEMENT_PATH}" touch "${DOT_MOVEMENT_PATH}/config.json" ``` Example config below. reminder: aptos.mainnet.movementlabs.xyz is served by Movement Full Node EDITOR `"${DOT_MOVEMENT_PATH}/config"` ```json { "maptos_config": { "chain": { "maptos_chain_id": 126 }, "indexer": { "maptos_indexer_grpc_listen_hostname": "aptos.mainnet.movementlabs.xyz", "maptos_indexer_grpc_listen_port": 30734, "maptos_indexer_grpc_inactivity_timeout": 120, "maptos_indexer_grpc_inactivity_ping_interval": 10 }, "indexer_processor": { "postgres_connection_string": "postgres://postgres:PASSWORD@AWS_RDS_INSTANCE.cluster-CLUSTER_ID.REGION.rds.amazonaws.com:5432/postgres", "indexer_processor_auth_token": "auth_token" }, "client": { "maptos_rest_connection_hostname": "aptos.mainnet.movementlabs.xyz", "maptos_rest_connection_port": 30731, "maptos_faucet_rest_connection_hostname": "aptos.mainnet.movementlabs.xyz", "maptos_faucet_rest_connection_port": 30732, "maptos_indexer_grpc_connection_hostname": "aptos.mainnet.movementlabs.xyz", "maptos_indexer_grpc_connection_port": 30734 } } } ``` * `maptos_chain_id` - is different for each network * `testnet-bardock`: 250 * `mainnet`: 126 * `maptos_indexer_grpc_listen_hostname` - depending on the setup it can be a FQDN or IP ### 4. Connectivity test Aptos gRPC node In the example below it's an IP ```bash nc -vz XX.80.XX.51 30734 Connection to XX.80.XX.51 30734 port [tcp/*] succeeded! ``` test with `grpcurl` also ```bash grpcurl --plaintext 3.80.159.51:30734 list aptos.indexer.v1.RawData grpc.reflection.v1alpha.ServerReflection ``` #### Debugging Connectivity Case 1: connection timeout. Make sure that on the Movement Full Node instance you allow traffic from IP of the Indexer Ec2 instance. ### 5. Create AWS RDS Postgres compatible DB Depending on if the workload is constant or not one has to chose between an auto-scalable setup or fixed size provisioned. I chose the use AWS AURORA Serverless with auto scaling. #### 1. Standard create #### 2. Aurora (PostgreSQL Compatible) #### 3. Templates - Production #### 4. Settings - Self managed credentials Use a strong password and save it in a password a manager like 1pass. #### 5. Cluster storage configuration - Aurora I/O-Optimized #### 6. Instance configuration - Serverless v2 Minimum capacity (ACUs) - 4 ACUs (8 GB) Maximum capacity (ACUs) - 64 ACUs (128 GiB) #### 7. Availability & durability Create an Aurora Replica or Reader node in a different AZ (recommended for scaled availability) #### 8. Connectivity Connect to an EC2 compute resource. Chose the EC2 instance you created for the indexer. #### 9. DB subnet group - Automatic setup #### 10. Public access - No #### 11. VPC security group (firewall) - Create new #### 12. Monitoring Database Insights - Advanced Additional monitoring settings - Enable Enhanced monitoring #### 13. Deletion protection - Enable deletion protection ### 6. Create a RDS Proxy #### 1. Engine family - Postgres #### 2. Target group configuration - The DB just created #### 3. Authentication ##### Create a new secret in a new tab: Secrets Manager secrets Create new secret * Credentials for Amazon RDS database * username: postgres * password: password from previous step * database: db from previous step #### 4. Select new secret just created in the other tab #### 5. Connectivity - Additional connectivity configuration VPC security group -> Choose existing !!! Make sure to select the security groups from the Indexer Ec2 Instance and from the Aurora DB. #### 5. Connectivity test From Indexer Ec2 instance to proxy, test that the postgresq port is reachable ```bash nc -vz indexer-testnet-bardock.proxy-XXXXXXXX.us-XXX-X.rds.amazonaws.com 5432 Connection to exer-testnet-bardock.proxy-XXXXXXXX.us-XXX-X.rds.amazonaws.com (1XX.31.9.4X) 5432 port [tcp/postgresql] succeeded! ``` test also the connection to the db, using the proxy ```bash export PGHOST=indexer-testnet-bardock.proxy-XXXXXXXX.us-XXX-X.rds.amazonaws.com export PGPASSWORD= psql --username=postgres --dbname=postgres --host=${PGHOST} ``` ```plaintext SSL connection (protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256, compression: off) Type "help" for help. postgres=> ``` list databases ```bash postgres=> \l ``` show tables ```bash \dt ``` show `postgres db size` ```bash postgres=> select pg_size_pretty(pg_database_size('postgres')); pg_size_pretty ---------------- 7900 kB (1 row) ``` ### 7. Create environment variables required by the docker compose file Create `.env` file in the required location ```bash HOME=/home/ssm-user cd "${HOME}"/movement/docker/compose/movement-indexer cat << 'EOF' > .env DOT_MOVEMENT_PATH=/home/ssm-user/.movement CONTAINER_REV=840783ee09f4e7d981207fad80e80a187a644322-amd64 MAPTOS_INDEXER_GRPC_LISTEN_PORT=30734 MAPTOS_INDEXER_GRPC_LISTEN_HOSTNAME=XX.80.XX.51 INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING=postgres://postgres:SECRET-PASSWORD@indexer-testnet-bardock.proxy-XXXXXXXX.us-XXX-X.rds.amazonaws.com/postgres POSTGRES_DB_HOST=dexer-testnet-bardock.proxy-XXXXXXXX.us-XXX-X.rds.amazonaws.com MAPTOS_INDEXER_GRPC_INACTIVITY_TIMEOUT_SEC=120 MAPTOS_INDEXER_GRPC_PING_INTERVAL_SEC=10 HASURA_GRAPHQL_ADMIN_SECRET=hasure-secert-here HASURA_GRAPHQL_JWT_SECRET={ "type": "HS256", "key": "readonlyValueHere" } MAPTOS_INDEXER_HEALTHCHECK_HOSTNAME=0.0.0.0 MAPTOS_INDEXER_HEALTHCHECK_PORT=8084 EOF ``` ### 8. Create production docker compose file ```bash cd "${HOME}"/movement/docker/compose/movement-indexer cat << 'EOF' > .env services: movement-indexer: image: ghcr.io/movement-network/movement-indexer:${CONTAINER_REV} # entrypoint: '/bin/sh -c "tail -f /dev/null"' container_name: movement-indexer environment: - DOT_MOVEMENT_PATH=/.movement - MAPTOS_INDEXER_GRPC_LISTEN_HOSTNAME=${MAPTOS_INDEXER_GRPC_LISTEN_HOSTNAME} - INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING=${INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING} - MAPTOS_INDEXER_HEALTHCHECK_HOSTNAME=${MAPTOS_INDEXER_HEALTHCHECK_HOSTNAME} - MAPTOS_INDEXER_HEALTHCHECK_PORT=${MAPTOS_INDEXER_HEALTHCHECK_PORT} volumes: - ${DOT_MOVEMENT_PATH}:/.movement restart: always healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8084/health"] interval: 5s timeout: 10s retries: 5 start_period: 5s ports: - "8084:8084" graphql-engine: image: hasura/graphql-engine:v2.45.0 ports: - "8085:8085" restart: always environment: HASURA_GRAPHQL_SERVER_PORT: 8085 ## postgres database to store Hasura metadata HASURA_GRAPHQL_METADATA_DATABASE_URL: ${INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING} HASURA_GRAPHQL_DATABASE_URL: ${INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING} ## this env var can be used to add the above postgres database to Hasura as a data source. this can be removed/updated based on your needs PG_DATABASE_URL: ${INDEXER_PROCESSOR_POSTGRES_CONNECTION_STRING} ## enable the console served by server HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console ## enable debugging mode. It is recommended to disable this in production HASURA_GRAPHQL_DEV_MODE: "true" HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log ## uncomment next line to run console offline (i.e load console assets from server instead of CDN) # HASURA_GRAPHQL_CONSOLE_ASSETS_DIR: /srv/console-assets ## uncomment next line to set an admin secret HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_GRAPHQL_ADMIN_SECRET} HASURA_GRAPHQL_JWT_SECRET: ${HASURA_GRAPHQL_JWT_SECRET} HASURA_GRAPHQL_METADATA_DEFAULTS: '{"backend_configs":{"dataconnector":{"athena":{"uri":"http://data-connector-agent:8081/api/v1/athena"},"mariadb":{"uri":"http://data-connector-agent:8081/api/v1/mariadb"},"mysql8":{"uri":"http://data-connector-agent:8081/api/v1/mysql"},"oracle":{"uri":"http://data-connector-agent:8081/api/v1/oracle"},"snowflake":{"uri":"http://data-connector-agent:8081/api/v1/snowflake"}}}}' # https://hasura.io/docs/2.0/auth/authorization/permissions/common-roles-auth-examples/#unauthorized-users-example HASURA_GRAPHQL_UNAUTHORIZED_ROLE: readonly depends_on: data-connector-agent: condition: service_healthy data-connector-agent: image: hasura/graphql-data-connector:v2.45.0 restart: always ports: - 8081:8081 environment: QUARKUS_LOG_LEVEL: ERROR # FATAL, ERROR, WARN, INFO, DEBUG, TRACE ## https://quarkus.io/guides/opentelemetry#configuration-reference QUARKUS_OPENTELEMETRY_ENABLED: "false" ## QUARKUS_OPENTELEMETRY_TRACER_EXPORTER_OTLP_ENDPOINT: http://jaeger:4317 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8081/api/v1/athena/health"] interval: 5s timeout: 10s retries: 5 start_period: 5s depends_on: - movement-indexer volumes: postgres_data: driver: local EOF ``` ### 9. Create a system v service file ```bash cd /etc/systemd/system cat << 'EOF' > indexer-testnet-bardock.service [Unit] Description=Indexer Testnet Bardock After=network.target [Service] Type=simple User=root WorkingDirectory=/home/ssm-user/movement ExecStart=/usr/bin/docker compose --env-file /home/ssm-user/movement/docker/compose/movement-indexer/.env -f /home/ssm-user/movement/docker/compose/movement-indexer/docker-compose.indexer.prod.yml up --force-recreate --remove-orphans Restart=on-failure [Install] WantedBy=multi-user.target EOF ``` ### 10. Start the indexer service ```bash systemctl enable indexer-testnet-bardock.service systemctl start indexer-testnet-bardock.service ``` ### 11. Validate the indexer containers Look at the logs of the containers anc make sure that there are no errors ```bash docker ps docker logs CONTAINER ``` ### 11. Expose Hasura GraphQL using nginx #### 1. Install nginx ```bash apt install -y nginx ``` #### 2. Create nginx config ```bash cd /etc/nginx/sites-available rm default cat << 'EOF' > indexer-testnet-bardock server { listen 80; server_name indexer.testnet.XXXX.xyz indexer.testnet.yyyyyy.xyz; location / { proxy_pass http://localhost:8085; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /health { proxy_pass http://localhost:8084/health; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } EOF ln -s /etc/nginx/sites-available/indexer-testnet-bardock indexer-testnet-bardock systemctl reload nginx.service ``` #### 3. Test if you can reach the Hasura GraphQL UI ```bash curl 127.0.0.1:80/console ``` output should be some HTML content #### 4. Configure the firewall (aws security group) to allow traffic to EC2 instance on port 80 #### 5. Test again using the externap IP of your EC2 instance ### 12. Create AWS Target group Point it to the indexer EC2 instance. ### 13 Create AWS Application Load Balancer Create listener on port 80. ### 14. Create DNS record and a secure connection over HTTPS We use CloudFlare for DNS management. When a new DNS CNMAE recored is created it also issues a SSL certificate. ### 16. Test Open the broser and go to FQDN. E. g [https://indexer.testnet.movementnetwork.xyz/console](https://indexer.testnet.movementnetwork.xyz/console) ### 17 Load Movement Hasura Metadata #### 1. Inside movement repo, update the meta data file with the postgresdb url * edit `networks/movement/indexer/hasura_metadata.json` * Find `INDEXER_V2_POSTGRES_URL` key and replace it with the postgresql url and save #### 2. Import Hasua Metadata file using the UI * Insert Hasura Admin secret * Go to Hasqura console admin access * Click setting (top right) * Click import metadata * Select the saved `hasura_metadata.json` file that you just modified. # LST Developer Guide URL: /devs/lst-developer-guide # LST Developer Guide ## Overview This guide helps developers integrate gMOVE into DeFi protocols, wallets, dashboards, and other applications on Movement Network. **What is gMOVE?** * Liquid staking token (LST) for Movement Network * Fungible Asset (FA) standard on Movement * Yield-bearing: value accrues through increasing exchange rate * Composable: can be used across DeFi protocols **Key properties** * **Exchange rate model:** 1 gMOVE = `exchange_rate` MOVE (monotonically increasing) * **Rebasing:** No. Balance stays constant, value increases via exchange rate. * **Standard:** Movement Fungible Asset (FA) * **Precision:** 8 decimals (same as MOVE) * **Exchange rate precision:** 10^9 (returned as `u128`, divide by `1_000_000_000` for decimal value) *** ## Contract Addresses Most integrations only need two of these: the **Module Address** (the published `liquid_staking` module, used in every call as `::liquid_staking::...`) and the **gMOVE Metadata Object** (the Fungible Asset that *is* gMOVE — reference it for balances, transfers, and listing the token in wallets or DEXes). The **Resource Account** is the protocol-owned account that custodies delegated stake; it is mainly useful for block explorers and for verifying delegation on validators. The **Validator** is the validator that stake is delegated to. ### Mainnet * **Module Address:** `0xb52bac12e50458cd2b958b82b05e3a240834eefbfc4b1bc0729fd580c625f1ea` * **Resource Account:** `0xd77f9e4e2a5dc1c9e9c567a39ec49ac388997f137e75ba92e12d5de75981804c` * **gMOVE Metadata Object:** `0xba070099efd401e69ae924e31464541bb9c815b9a1866367f07499d9b3698b2c` * **Validator:** `0x830bfd0cd58b06dc938d409b6f3bc8ee97818ffcf9b32d714c068454afb644c7` ### Testnet * **Module Address:** `0x9762cac6c378ff6110885449e80cf4c2890c19a725251b22412b1ac02100044d` * **Resource Account:** `0x51a3b8280eb8caf4f8c5a64c55fbe36e7fc15e4ced6676da7fb78a70abe4f2dc` * **gMOVE Metadata Object:** `0x9e412e6fa4ac80ca446487d6c605b9f8d1d5aafb28200dff16dd47d02a09390d` * **Validator:** `0xa1ef53a76fe31c0844c7b87988a3e2b905287ec687a8e081d793597eb351ed5c` **How to find the resource account** ```bash # Call the view function (returns: resource_account_address, minimum_stake_amount, precision_multiplier, is_paused) movement move view \ --function-id ::liquid_staking::get_protocol_config ``` *** ## View Functions (Read-Only) All view functions are read-only and do not require authentication. They are gas-free when called via RPC. ### 1. `get_exchange_rate()` Returns the current exchange rate of gMOVE to MOVE. **Signature** ```move #[view] public fun get_exchange_rate(): u128 ``` **Returns** * `u128`: Exchange rate with 10^9 precision **Conversion** ```text actual_rate = returned_value / 1_000_000_000 gMOVE_value_in_MOVE = gMOVE_amount * actual_rate ``` **Example** ```bash # CLI movement move view \ --function-id ::liquid_staking::get_exchange_rate # Example return: 1050000000 # Actual rate: 1050000000 / 1000000000 = 1.05 # Meaning: 1 gMOVE = 1.05 MOVE ``` **Example in Move** ```move use liquid_staking; public fun example() { let rate = liquid_staking::get_exchange_rate(); // Returns: 1050000000 let gmove_amount = 100_00000000; // 100 gMOVE (8 decimals) // Calculate MOVE value let move_value = (gmove_amount as u128) * (rate as u128) / 1_000_000_000; // move_value = 105_00000000 (105 MOVE) } ``` **Use cases** * Display gMOVE value in wallets * Pricing for DEX pools * Collateral valuation in lending protocols * Oracle feeds **Important** * Exchange rate is **monotonically increasing** (should never decrease) * A decreasing rate indicates validator slashing or a protocol issue * Rate updates when `harvest_and_compound()` is called *** ### 2. `get_total_supply()` Returns the total supply of gMOVE in circulation. **Signature** ```move #[view] public fun get_total_supply(): u128 ``` **Returns** * `u128`: Total gMOVE supply (8 decimals) **Example** ```bash movement move view \ --function-id ::liquid_staking::get_total_supply # Example return: 1000000_00000000 (1,000,000 gMOVE) ``` **Use cases** * TVL calculation: `total_supply * exchange_rate` * Market cap tracking * Analytics dashboards *** ### 3. `get_total_value()` Returns the total value locked (TVL) in MOVE. **Signature** ```move #[view] public fun get_total_value(): u128 ``` **Returns** * `u128`: Total active stake in MOVE (8 decimals) **Example** ```bash movement move view \ --function-id ::liquid_staking::get_total_value # Example return: 1100000_00000000 (1,100,000 MOVE) ``` **Relationship** ```text exchange_rate = get_total_value() / get_total_supply() ``` *** ### 4. `get_protocol_statistics()` Returns comprehensive protocol statistics in a single call. **Signature** ```move #[view] public fun get_protocol_statistics(): (u128, u128, u128, u64) ``` **Returns (in order)** 1. `u128`: Total value (MOVE), 8 decimals 2. `u128`: Total supply (gMOVE), 8 decimals 3. `u128`: Exchange rate, 10^9 precision 4. `u64`: Active validator count **Example** ```bash movement move view \ --function-id ::liquid_staking::get_protocol_statistics # Example return: # [ # "5999913835", // 59.99913835 MOVE # "6000204652", // 60.00204652 gMOVE # "999951532", // 0.999951532 exchange rate # "1" // 1 active validator # ] ``` **Use cases** * Dashboard displays * Single RPC call for complete state * Monitoring and alerting *** ### 5. `get_user_unstake_requests(address)` Returns all pending unstake requests for a specific address. **Signature** ```move #[view] public fun get_user_unstake_requests(user: address): vector ``` **Parameters** * `user`: Address to query **Returns** * `vector`: List of pending unstake requests, each containing: * `user`: Address of the unstaker * `gmove_amount`: gMOVE burned * `coin_amount`: Expected MOVE to receive * `unlock_time`: Timestamp when claimable (seconds since epoch) * `validator_unstakes`: Per-validator breakdown **Example** ```bash movement move view \ --function-id ::liquid_staking::get_user_unstake_requests \ --args address:0x123... # Example return: # [{ "user": "0x123...", "gmove_amount": "3000000000", "coin_amount": "2999892291", # "unlock_time": "1770771292", "validator_unstakes": [...] }] ``` **Additional view functions** * `get_user_unstake_request(user, request_id)` - Get a specific request * `check_can_claim(user, request_id)` - Check if a request is ready to claim **Use cases** * Wallet displays showing pending withdrawals * Countdown timers for unlock * Claiming flow UI *** ### 6. `get_protocol_config()` Returns the protocol configuration including the resource account address. **Signature** ```move #[view] public fun get_protocol_config(): (address, u64, u128, bool) ``` **Returns (in order)** 1. `address`: Resource account address 2. `u64`: Minimum stake amount (octas) 3. `u128`: Precision multiplier (10^9) 4. `bool`: Whether the protocol is paused **Example** ```bash movement move view \ --function-id ::liquid_staking::get_protocol_config # Example return: # [ # "0xd77f9e4e2a5dc1c9e9c567a39ec49ac388997f137e75ba92e12d5de75981804c", # "1000100000", # "1000000000", # false # ] ``` **Use cases** * Block explorer integrations * Verifying delegation on validators * Checking protocol pause status * Advanced analytics *** ## Entry Functions (State-Changing) These functions modify blockchain state and require a transaction signature. ### 1. `stake_and_mint(amount: u64)` Stake MOVE and receive gMOVE. **Signature** ```move public entry fun stake_and_mint(account: &signer, amount: u64) ``` **Parameters** * `account`: Signer (user wallet) * `amount`: MOVE amount to stake (8 decimals) **Flow** 1. Withdraws `amount` MOVE from user's primary store 2. Delegates to validator via resource account 3. Calculates gMOVE to mint based on exchange rate 4. Mints gMOVE to user's primary store **Example** ```bash # Stake 100 MOVE movement move run \ --function-id ::liquid_staking::stake_and_mint \ --args u64:10000000000 ``` **Example in Move** ```move use liquid_staking; public entry fun stake_for_user(user: &signer) { let amount = 100_00000000; // 100 MOVE liquid_staking::stake_and_mint(user, amount); } ``` **Important** * User must have at least `amount` MOVE in primary store * User must have migrated to fungible store (`coin::migrate_to_fungible_store`) * First depositor has 10 MOVE permanently locked (MINIMUM\_LIQUIDITY protection) *** ### 2. `stake_and_mint_with_slippage(amount: u64, min_gmove_out: u64)` Stake MOVE with slippage protection. **Signature** ```move public entry fun stake_and_mint_with_slippage( account: &signer, amount: u64, min_gmove_out: u64 ) ``` **Parameters** * `account`: Signer * `amount`: MOVE amount to stake * `min_gmove_out`: Minimum gMOVE to receive (reverts if less) **Use cases** * Front-running protection * User specifies acceptable exchange rate range **Example** ```bash # Stake 100 MOVE, expect at least 95 gMOVE (5% slippage tolerance) movement move run \ --function-id ::liquid_staking::stake_and_mint_with_slippage \ --args u64:10000000000 u64:9500000000 ``` *** ### 3. `burn_and_unstake(amount: u64)` Burn gMOVE and initiate unstaking process. **Signature** ```move public entry fun burn_and_unstake(account: &signer, amount: u64) ``` **Parameters** * `account`: Signer * `amount`: gMOVE amount to unstake (8 decimals) **Flow** 1. Burns `amount` gMOVE from user 2. Calculates MOVE value based on exchange rate 3. Unlocks stake from validator (starts 14-day unbonding) 4. Records pending unstake for user with a `request_id` **After 14 days** * User calls `claim_unlocked(request_id)` to receive MOVE **Example** ```bash # Unstake 50 gMOVE movement move run \ --function-id ::liquid_staking::burn_and_unstake \ --args u64:5000000000 ``` **Important** * User waits 14 days before claiming MOVE * gMOVE is burned immediately (stops earning rewards) * User can have multiple pending unstake requests (each with a unique `request_id`) *** ### 4. `burn_and_unstake_with_slippage(amount: u64, min_move_out: u64)` Unstake with slippage protection. **Signature** ```move public entry fun burn_and_unstake_with_slippage( account: &signer, amount: u64, min_move_out: u64 ) ``` **Parameters** * `account`: Signer * `amount`: gMOVE amount to unstake * `min_move_out`: Minimum MOVE to receive after 14 days (reverts if less) *** ### 5. `claim_unlocked(request_id: u64)` Claim MOVE after the unbonding period. **Signature** ```move public entry fun claim_unlocked(account: &signer, request_id: u64) ``` **Parameters** * `account`: Signer * `request_id`: The ID of the unstake request to claim **Flow** 1. Checks if unbonding period has passed 2. Withdraws MOVE from validator delegation 3. Transfers MOVE to user's primary store 4. Removes the unstake request **Example** ```bash movement move run \ --function-id ::liquid_staking::claim_unlocked \ --args u64:0 ``` **Errors** * `EUNLOCK_NOT_READY`: Unbonding period hasn't passed yet * `EREQUEST_NOT_FOUND`: No unstake request with this ID * `EINVALID_REQUEST`: Request doesn't belong to the caller *** ### 6. `harvest_and_compound()` Anyone can call this to compound staking rewards and update the exchange rate. No signer required. **Signature** ```move public entry fun harvest_and_compound() ``` **Flow** 1. Withdraws accumulated rewards from validator 2. Restakes rewards back into validator 3. Exchange rate increases for all gMOVE holders **Example** ```bash movement move run \ --function-id ::liquid_staking::harvest_and_compound ``` **Use cases** * Protocol keepers call this periodically * Users can call to update exchange rate before large operations * Bots can call when it is economically beneficial *** ## Integration Patterns ### Pattern 1: Wallet Integration **Display gMOVE balance and value** ```tsx // Pseudocode async function getGMoveInfo(userAddress: string) { // Get user's gMOVE balance (from FA primary store) const gmoveBalance = await getBalance(userAddress, GMOVE_METADATA); // Get current exchange rate const rate = await view({ function: `${MODULE_ADDRESS}::liquid_staking::get_exchange_rate`, type_arguments: [], arguments: [] }); // Calculate MOVE value const exchangeRate = rate[0] / 1_000_000_000; const moveValue = gmoveBalance * exchangeRate; return { gmoveBalance, moveValue, exchangeRate }; } ``` **Show pending unstakes** ```tsx async function getPendingUnstake(userAddress: string) { const requests = await view({ function: `${MODULE_ADDRESS}::liquid_staking::get_user_unstake_requests`, type_arguments: [], arguments: [userAddress] }); return requests.map(req => { const now = Date.now() / 1000; const isReady = req.unlock_time <= now; const timeRemaining = isReady ? 0 : req.unlock_time - now; return { amount: req.coin_amount / 100_000_000, unlockTime: req.unlock_time, isReady, daysRemaining: timeRemaining / 86400 }; }); } ``` *** ### Pattern 2: DEX Integration **Price oracle for gMOVE/MOVE pool** ```move // In your DEX contract use liquid_staking; public fun get_gmove_fair_value(): u64 { // Get exchange rate from gMOVE protocol let rate = liquid_staking::get_exchange_rate(); // This is the fair value: 1 gMOVE = `rate` MOVE rate } public fun check_pool_health(pool_price: u64, max_deviation_bps: u64): bool { let fair_value = get_gmove_fair_value(); let deviation = if (pool_price > fair_value) { ((pool_price - fair_value) as u128) * 10000 / (fair_value as u128) } else { ((fair_value - pool_price) as u128) * 10000 / (fair_value as u128) }; (deviation as u64) <= max_deviation_bps } ``` **Arbitrage detection** ```tsx async function checkArbitrageOpportunity() { // Get fair value from protocol const rateRaw = await getExchangeRate(); const fairValue = rateRaw / 1_000_000_000; // Get DEX price const dexPrice = await getDexPrice('gMOVE', 'MOVE'); // Calculate deviation const deviation = (dexPrice - fairValue) / fairValue; if (Math.abs(deviation) > 0.01) { // 1% deviation return { hasOpportunity: true, fairValue, dexPrice, deviation, action: deviation > 0 ? 'SELL_GMOVE' : 'BUY_GMOVE' }; } return { hasOpportunity: false }; } ``` *** ### Pattern 3: Lending Protocol Integration **Use gMOVE as collateral** ```move use liquid_staking; use fungible_asset::{Self, FungibleAsset}; // Calculate borrowing power public fun get_borrow_power(gmove_collateral: u64, ltv_bps: u64): u64 { let exchange_rate = liquid_staking::get_exchange_rate(); // Convert gMOVE to MOVE value let move_value = (gmove_collateral as u128) * (exchange_rate as u128) / 1_000_000_000; // Apply LTV (e.g., 75% = 7500 bps) let borrow_power = move_value * (ltv_bps as u128) / 10000; (borrow_power as u64) } // Check if position is healthy public fun is_position_healthy( gmove_collateral: u64, debt: u64, liquidation_threshold_bps: u64 ): bool { let exchange_rate = liquid_staking::get_exchange_rate(); let collateral_value = (gmove_collateral as u128) * (exchange_rate as u128) / 1_000_000_000; let max_debt = collateral_value * (liquidation_threshold_bps as u128) / 10000; (debt as u128) <= max_debt } ``` *** ### Pattern 4: Dashboard / Analytics **Track protocol metrics** ```tsx async function getProtocolMetrics() { const [totalValue, totalSupply, exchangeRate, validatorCount] = await view({ function: `${MODULE_ADDRESS}::liquid_staking::get_protocol_statistics`, type_arguments: [], arguments: [] }); return { tvl: totalValue / 100_000_000, // MOVE supply: totalSupply / 100_000_000, // gMOVE exchangeRate: exchangeRate / 1_000_000_000, validatorCount, avgStakePerValidator: (totalValue / validatorCount) / 100_000_000 }; } ``` **Track exchange rate history** ```tsx // Poll exchange rate periodically and store async function trackExchangeRate() { const rate = await getExchangeRate(); const timestamp = Date.now(); // Store in database await db.insert({ timestamp, exchangeRate: rate / 1_000_000_000, block: await getCurrentBlock() }); // Calculate APY based on historical data const rateOneYearAgo = await db.getRateAt(timestamp - 365*24*60*60*1000); const apy = ((rate / rateOneYearAgo) - 1) * 100; return { currentRate: rate, estimatedAPY: apy }; } ``` *** ## Error Codes Understanding error codes for better error handling: | Error Code | Constant | Meaning | How to Fix | | ---------- | ------------------------- | -------------------------------------------- | --------------------------------------------- | | `2` | `EALREADY_INITIALIZED` | Protocol already initialized | N/A (admin only) | | `3` | `EZERO_AMOUNT` | Amount is zero | Require amount > 0 | | `4` | `EINSUFFICIENT_BALANCE` | User does not have enough MOVE/gMOVE | Check balance before transaction | | `5` | `ENO_EXCHANGE_RATE` | Exchange rate unavailable | Ensure protocol has stake | | `7` | `EINVALID_POOL` | Delegation pool does not exist | Contact admin | | `8` | `EPAUSED` | Protocol is paused | Wait for unpause | | `9` | `EMIN_STAKE_NOT_MET` | Below minimum stake (10.001 MOVE) | Increase stake amount | | `10` | `EINVALID_VALIDATOR_SET` | Invalid validator configuration | Contact admin | | `11` | `ESLIPPAGE_EXCEEDED` | Output less than min specified | Adjust slippage tolerance | | `12` | `EUNLOCK_NOT_READY` | Unbonding period not finished | Wait until unlock time | | `13` | `EINVALID_REQUEST` | Request does not belong to caller | Use correct account | | `14` | `EREQUEST_NOT_FOUND` | No pending unstake to claim | Check request ID | | `15` | `EUNAUTHORIZED` | Caller is not `@liquid_staking` admin | Only module deployer can call admin functions | | `16` | `ETOO_MANY_VALIDATORS` | Cannot add more validators | N/A (20 validator limit) | | `17` | `EVALIDATOR_HAS_STAKE` | Validator still has stake | Wait for stake to be fully withdrawn | | `18` | `EEXCHANGE_RATE_OVERFLOW` | Exchange rate calculation overflow | Contact admin | | `19` | `EVALIDATOR_NOT_INACTIVE` | Validator not in inactive list | Only applies to cleanup/admin unstake | | `20` | `EINVALID_MOVE_METADATA` | MOVE FA metadata not found | Ensure FA migration completed | | `21` | `EDUPLICATE_VALIDATOR` | Validator already in active or inactive list | Validator already tracked | | `22` | `EQUEUE_NOT_INITIALIZED` | UnstakeQueue not initialized | Wait for protocol initialization | **Example error handling** ```tsx try { await stake(amount); } catch (error) { if (error.includes('EINSUFFICIENT_BALANCE')) { showError('Insufficient MOVE balance'); } else if (error.includes('EPAUSED')) { showError('Protocol is temporarily paused'); } else if (error.includes('ESLIPPAGE_EXCEEDED')) { showError('Price moved unfavorably. Please try again.'); } } ``` *** ## Best Practices ### 1. Always use view functions for read operations * View functions are free (no gas) * Call via RPC, not via transactions * Cache results appropriately (exchange rate changes infrequently) ### 2. Use slippage protection for user-facing operations * Prefer `stake_and_mint_with_slippage()` over `stake_and_mint()` * Prefer `burn_and_unstake_with_slippage()` over `burn_and_unstake()` * Recommend 0.5% to 1% slippage tolerance for users ### 3. Monitor exchange rate * Should only increase (monotonically) * Set up alerts for unexpected behavior * Any decrease indicates slashing or critical issue ### 4. Handle precision correctly * gMOVE: 8 decimals (same as MOVE) * Exchange rate: 10^9 precision * Always use `u128` for intermediate calculations to avoid overflow ```move // WRONG - can overflow let value = gmove_amount * exchange_rate / 1_000_000_000; // CORRECT - use u128 let value = ((gmove_amount as u128) * (exchange_rate as u128) / 1_000_000_000) as u64; ``` ### 5. Educate users about the 14-day unbonding period * Make the 14-day wait **very clear** in UI * Offer DEX instant exit as an alternative * Show a countdown timer for pending unstakes ### 6. Batch view calls * Use `get_protocol_statistics()` instead of multiple individual calls * Reduces RPC load and improves performance ### 7. Respect epoch voting power limits * Large deposits may be rate-limited by validator epoch capacity * Break large deposits into smaller chunks if needed * Communicate this limitation to users staking large amounts *** ## Testing ### Testnet testing checklist * [ ] Successfully call all view functions * [ ] Stake small amount (e.g., 11 MOVE) * [ ] Verify gMOVE balance increased correctly * [ ] Check exchange rate matches expected value * [ ] Unstake small amount * [ ] Wait for testnet unbonding (6 hours) * [ ] Claim unstaked MOVE successfully * [ ] Test slippage protection (both directions) * [ ] Verify error handling for edge cases * [ ] Test with multiple accounts * [ ] Monitor exchange rate over time ### Example test script ```bash #!/bin/bash MODULE_ADDR="0xb52bac12e50458cd2b958b82b05e3a240834eefbfc4b1bc0729fd580c625f1ea" echo "1. Get initial exchange rate" movement move view --function-id $MODULE_ADDR::liquid_staking::get_exchange_rate echo "2. Stake 11 MOVE" movement move run --function-id $MODULE_ADDR::liquid_staking::stake_and_mint \ --args u64:1100000000 --profile test --assume-yes echo "3. Check protocol stats" movement move view --function-id $MODULE_ADDR::liquid_staking::get_protocol_statistics echo "4. Unstake 5 gMOVE" movement move run --function-id $MODULE_ADDR::liquid_staking::burn_and_unstake \ --args u64:500000000 --profile test --assume-yes echo "5. Check pending unstake requests" movement move view --function-id $MODULE_ADDR::liquid_staking::get_user_unstake_requests \ --args address:0x... # your address echo "6. Wait 6 hours (testnet unbonding)..." echo "7. Then claim:" # movement move run --function-id $MODULE_ADDR::liquid_staking::claim_unlocked \ # --args u64:0 --profile test --assume-yes ``` *** ## Appendix: Complete ABI ```move module liquid_staking { // ========== View Functions ========== #[view] public fun get_exchange_rate(): u128 #[view] public fun get_total_supply(): u128 #[view] public fun get_total_value(): u128 #[view] public fun get_protocol_statistics(): (u128, u128, u128, u64) #[view] public fun get_user_unstake_requests(user: address): vector #[view] public fun get_user_unstake_request(user: address, request_id: u64): UnstakeRequest #[view] public fun check_can_claim(user: address, request_id: u64): bool #[view] public fun get_protocol_config(): (address, u64, u128, bool) #[view] public fun get_active_validators(): vector
#[view] public fun get_validator_stakes(validator: address): (u64, u64, u64) #[view] public fun get_gmove_metadata(): Object #[view] public fun get_unbonding_duration(): u64 #[view] public fun get_minimum_stake_amount(): u64 #[view] public fun get_precision_multiplier(): u128 #[view] public fun is_protocol_initialized(): bool #[view] public fun preview_stake(move_amount: u64): u128 #[view] public fun preview_unstake(gmove_amount: u64): u128 // ========== Entry Functions ========== public entry fun stake_and_mint(account: &signer, amount: u64) public entry fun stake_and_mint_with_slippage( account: &signer, amount: u64, min_gmove_out: u64 ) public entry fun burn_and_unstake(account: &signer, amount: u64) public entry fun burn_and_unstake_with_slippage( account: &signer, amount: u64, min_move_out: u64 ) public entry fun claim_unlocked(account: &signer, request_id: u64) public entry fun harvest_and_compound() // ========== Admin Functions (@liquid_staking only) ========== // All admin functions require signer to be the module deployer address. // Calling from any other address will abort with EUNAUTHORIZED (15). public entry fun initialize(admin: &signer) public entry fun initialize_testnet(admin: &signer) public entry fun initialize_with_validators(admin: &signer, validators: vector
) public entry fun add_validator(admin: &signer, validator: address) public entry fun remove_validator(admin: &signer, validator: address) public entry fun cleanup_inactive_validator(admin: &signer, validator: address) public entry fun admin_unstake_from_inactive(admin: &signer, validator: address, amount: u64) public entry fun pause(admin: &signer) public entry fun unpause(admin: &signer) public entry fun update_metadata( admin: &signer, icon_uri: string::String, project_uri: string::String ) } ``` # Move 2 URL: /devs/move2 # Move 2 Migration Guide for Movement This guide covers the most impactful changes in Move 2 for developers already familiar with Move 1 syntax. Each section shows before/after examples to help you understand how to leverage these new features. ## 1. Enum Types (Move 2.0) Enums allow you to define different variants of data layout in a single storable type, similar to Rust enums. ### Before (Move 1) ```move module my_addr::events { struct TransferEvent has store, drop { from: address, to: address, amount: u64, } struct MintEvent has store, drop { to: address, amount: u64, } struct BurnEvent has store, drop { from: address, amount: u64, } // Need separate handling for each event type struct EventStore has key { transfers: vector, mints: vector, burns: vector, } } ``` ### After (Move 2) ```move module my_addr::events { // All variants in one type enum TokenEvent has store, drop { Transfer { from: address, to: address, amount: u64 }, Mint { to: address, amount: u64 }, Burn { from: address, amount: u64 }, } // Single vector for all event types struct EventStore has key { events: vector, } // Pattern matching on variants public fun process_event(event: &TokenEvent) { match (event) { TokenEvent::Transfer { from, to, amount } => { // Handle transfer }, TokenEvent::Mint { to, amount } => { // Handle mint }, TokenEvent::Burn { from, amount } => { // Handle burn }, } } } ``` **Benefits**: Cleaner code organization, type safety across variants, and simplified storage patterns. *** ## 2. Receiver Style Functions (Move 2.0) Call functions using the familiar `value.method(args)` notation instead of `module::function(value, args)`. ### Before (Move 1) ```move module my_addr::token { struct Token has store { value: u64, } public fun new(value: u64): Token { Token { value } } public fun value(token: &Token): u64 { token.value } public fun add(token: &mut Token, amount: u64) { token.value = token.value + amount; } // Usage elsewhere: // let mut token = token::new(100); // let val = token::value(&token); // token::add(&mut token, 50); } ``` ### After (Move 2) ```move module my_addr::token { struct Token has store { value: u64, } public fun new(value: u64): Token { Token { value } } // Receiver-style functions public fun value(self: &Token): u64 { self.value } public fun add(self: &mut Token, amount: u64) { self.value = self.value + amount; } // Usage elsewhere - much more intuitive! // let mut token = token::new(100); // let val = token.value(); // token.add(50); } ``` **Benefits**: More intuitive, object-oriented style syntax that's easier to read and chain operations. *** ## 3. Index Notation (Move 2.0) Access vector elements and resource storage with cleaner bracket notation. ### Before (Move 1) ```move module my_addr::registry { use std::vector; struct Registry has key { items: vector, } public fun get_item(registry: &Registry, index: u64): u64 { *vector::borrow(®istry.items, index) } public fun update_item(registry: &mut Registry, index: u64, value: u64) { let item = vector::borrow_mut(&mut registry.items, index); *item = value; } public fun get_registry(addr: address): &Registry acquires Registry { borrow_global(addr) } } ``` ### After (Move 2) ```move module my_addr::registry { struct Registry has key { items: vector, } public fun get_item(registry: &Registry, index: u64): u64 { registry.items[index] } public fun update_item(registry: &mut Registry, index: u64, value: u64) { registry.items[index] = value; } public fun get_registry(addr: address): &Registry acquires Registry { &Registry[addr] // Cleaner resource access } } ``` **Benefits**: Dramatically cleaner syntax that's familiar to developers from other languages. *** ## 4. Optional Acquires (Move 2.2) The compiler can now infer which resources a function accesses, making the `acquires` annotation optional. ### Before (Move 1) ```move module my_addr::account { struct Balance has key { value: u64, } struct Config has key { fee: u64, } // Must manually annotate all acquired resources public fun transfer(from: address, to: address, amount: u64) acquires Balance, Config { let config = borrow_global(@my_addr); let fee = config.fee; let from_balance = borrow_global_mut(from); from_balance.value = from_balance.value - amount - fee; let to_balance = borrow_global_mut(to); to_balance.value = to_balance.value + amount; } } ``` ### After (Move 2.2) ```move module my_addr::account { struct Balance has key { value: u64, } struct Config has key { fee: u64, } // Acquires annotation is now optional - compiler infers it! public fun transfer(from: address, to: address, amount: u64) { let config = borrow_global(@my_addr); let fee = config.fee; let from_balance = borrow_global_mut(from); from_balance.value = from_balance.value - amount - fee; let to_balance = borrow_global_mut(to); to_balance.value = to_balance.value + amount; } } ``` **Benefits**: Less boilerplate, fewer chances for annotation errors, and easier refactoring. *** ## 5. Compound Assignments (Move 2.1) Use familiar `+=`, `-=`, etc. operators instead of verbose assignment patterns. ### Before (Move 1) ```move module my_addr::counter { struct Counter has key { value: u64, } public fun increment(counter: &mut Counter, amount: u64) { counter.value = counter.value + amount; } public fun decrement(counter: &mut Counter, amount: u64) { counter.value = counter.value - amount; } public fun multiply(counter: &mut Counter, factor: u64) { counter.value = counter.value * factor; } } ``` ### After (Move 2.1) ```move module my_addr::counter { struct Counter has key { value: u64, } public fun increment(counter: &mut Counter, amount: u64) { counter.value += amount; } public fun decrement(counter: &mut Counter, amount: u64) { counter.value -= amount; } public fun multiply(counter: &mut Counter, factor: u64) { counter.value *= factor; } } ``` **Benefits**: More concise, familiar syntax from other programming languages. Supported operations: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`. *** ## 6. Package Visibility (Move 2.0) Declare functions visible anywhere inside a package but not outside, with cleaner syntax than friend functions. ### Before (Move 1) ```move module my_addr::internal { friend my_addr::public_api; friend my_addr::admin; public(friend) fun internal_helper(): u64 { 42 } } module my_addr::public_api { use my_addr::internal; public fun call_helper(): u64 { internal::internal_helper() } } ``` ### After (Move 2.0) ```move module my_addr::internal { // Cleaner syntax - visible to entire package package fun internal_helper(): u64 { 42 } // Or use the explicit form public(package) fun another_helper(): u64 { 100 } } module my_addr::public_api { use my_addr::internal; public fun call_helper(): u64 { // Can call package functions from anywhere in the package internal::internal_helper() } } ``` **Benefits**: Simpler visibility control, no need to maintain friend lists, better encapsulation at package level. *** ## 7. Positional Structs (Move 2.0) Define wrapper types and simple structs with positional fields instead of named fields. ### Before (Move 1) ```move module my_addr::wrapped { struct Wrapped has store, drop { value: u64, } public fun new(value: u64): Wrapped { Wrapped { value } } public fun unwrap(wrapped: Wrapped): u64 { let Wrapped { value } = wrapped; value } } ``` ### After (Move 2.0) ```move module my_addr::wrapped { // Positional struct - perfect for wrappers struct Wrapped(u64) has store, drop; public fun new(value: u64): Wrapped { Wrapped(value) } public fun unwrap(wrapped: Wrapped): u64 { let Wrapped(value) = wrapped; value } } ``` **Benefits**: Less boilerplate for simple wrapper types, cleaner syntax for single-field structs. *** ## Quick Reference: Other Notable Changes ### Simplified Assertions (Move 2.0) ```move // Before: always needed abort code assert!(condition, ERROR_CODE); // After: abort code is optional assert!(condition); // Uses default abort code ``` ### Cleaner Cast Syntax (Move 2.0) ```move // Before: required parentheses function((x as u256)) // After: no parentheses needed at top level function(x as u256) ``` ### Loop Labels (Move 2.1) ```move // Break or continue outer loops from nested loops 'outer: loop { loop { if (condition) break 'outer; } } ``` *** ## Summary The most impactful Move 2 features for day-to-day development are: 1. **Enums** - Better type modeling and pattern matching 2. **Receiver functions** - More intuitive method call syntax 3. **Index notation** - Cleaner vector and resource access 4. **Optional acquires** - Less boilerplate in function signatures 5. **Compound assignments** - Familiar `+=` style operators 6. **Package visibility** - Simpler module organization These features make Move code more expressive, safer, and easier to maintain while preserving Move's core safety guarantees. # Movement CLI URL: /devs/movementcli Movement CLI supports Aptos Move natively. Here are the instructions to install and use `movement` CLI. ## Install via Homebrew Works on macOS ARM64 (M-series), macOS x86\_64 (Intel), and Linux x86\_64. ```bash brew tap moveindustries/movement brew install movement ``` Or directly: ```bash brew install moveindustries/movement/movement ``` Verify installation: ```bash movement --version ``` It should return `movement 7.4.0`. ### Windows For Windows users, we recommend installing the Aptos CLI. Movement CLI commands are fully compatible with Aptos CLI. ```bash tab="Windows" $Version = "7.4.0" $ZipUrl = "https://github.com/aptos-labs/aptos-core/releases/download/aptos-cli-v$Version/aptos-cli-$Version-Windows-x86_64.zip" $InstallDir = "$env:USERPROFILE\.aptoscli\bin" $ZipPath = "$env:TEMP\aptos-cli.zip" Invoke-WebRequest -Uri $ZipUrl -OutFile $ZipPath New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null Expand-Archive -Path $ZipPath -DestinationPath $InstallDir -Force # Set PATH $CurrentPath = [System.Environment]::GetEnvironmentVariable("Path", "User") if ($CurrentPath -notlike "*$InstallDir*") { setx PATH "$CurrentPath;$InstallDir" Write-Host "`n✅ PATH updated. Please restart your terminal before running 'aptos'." } else { Write-Host "`n✅ PATH already configured." } ``` ## Install Movement CLI Manually When developing on Movement, it is recommended to use the `movement` CLI. ### Binary Install ``` git clone https://github.com/movement-network/aptos-core/ && cd aptos-core ``` ``` cargo build -p movement ``` Copy `movement` into your `bin` or add it to your `PATH` depending on your system config. For example, to copy `movement` to `bin` on Mac or Linux: ``` sudo cp target/debug/movement /usr/local/bin/ ``` ## Use Movement CLI Movement CLI commands are analogous to those of Aptos CLI. Simply replace `aptos` with `movement`. So `aptos move build` becomes `movement move build`. For help within the CLI tool: ``` movement --help ``` or ``` movement --help ``` Developers who would like to contribute or read the source code, please see [the Movement CLI crate](https://github.com/movement-network/aptos-core/tree/movement/crates/aptos). # Movement Name Service URL: /devs/nameService # Movement Name Service The Movement Name Service is currently live on **testnet only**. The URL and contract addresses will change when it launches on mainnet. The Movement Name Service (MNS) allows users to register `.move` domain names as NFTs that serve as a universal identity across the Movement ecosystem. Each `.move` name resolves to a blockchain address and can be set as a primary name for reverse lookups. ## SDK Integration MNS functionality is built into the [Movement TypeScript SDK](/devs/interactonchain/tsSdk). All name service methods are accessible through the `movement` client instance. ### Installation ```bash tab="npm" npm install @moveindustries/ts-sdk ``` ```bash tab="pnpm" pnpm add @moveindustries/ts-sdk ``` ### Setup ```typescript import { Movement, MovementConfig, Network } from "@moveindustries/ts-sdk"; const config = new MovementConfig({ network: Network.TESTNET, }); const movement = new Movement(config); ``` ## Domain Registration ### Check Price and Availability ```typescript const name = "mydomain"; const price = await movement.getDomainPrice({ name, years: 1 }); console.log(`Registration cost: ${Number(price) / 1e8} MOVE`); const available = await movement.canRegister({ name }); console.log(`Available: ${available}`); ``` ### Register a Domain ```typescript const txn = await movement.registerName({ name: "mydomain.move", sender: account, expiration: { policy: "domain" }, }); ``` ### Renew a Domain ```typescript await movement.renewDomain({ name: "mydomain.move", sender: account, years: 1, }); ``` ## Name Resolution ### Resolve Name to Address ```typescript const address = await movement.getTargetAddress({ name: "mydomain.move" }); console.log(`Address: ${address}`); ``` ### Reverse Lookup (Address to Primary Name) ```typescript const name = await movement.getPrimaryName({ address: "0x123...", }); console.log(`Primary name: ${name}`); ``` ### Get All Names for an Address ```typescript const names = await movement.getAccountNames({ accountAddress: "0x123...", }); console.log(names); ``` ## Name Management ### Set Target Address Configure which address a name resolves to: ```typescript await movement.setTargetAddress({ sender: account, name: "mydomain.move", address: targetAddress, }); ``` ### Clear Target Address ```typescript await movement.clearTargetAddress({ sender: account, name: "mydomain.move", }); ``` ### Set Primary Name Designate a name as the primary identity for your account: ```typescript await movement.setPrimaryName({ sender: account, name: "mydomain.move", }); ``` ### Check Ownership ```typescript const isOwner = await movement.isNameOwner({ name: "mydomain.move", address: account.accountAddress, }); ``` ## Additional Methods | Method | Description | | ------------------- | ----------------------------------------- | | `getName` | Fetch metadata for a specific name | | `getOwnerAddress` | Get the owner of a domain | | `getExpiration` | Get the expiration timestamp for a domain | | `getAccountDomains` | List all domains owned by an address | | `getTokenAddress` | Get the NFT token address for a domain | ## Indexer Queries You can query MNS data directly using the Movement indexer GraphQL API. The testnet endpoint is: ``` https://indexer.testnet.movementnetwork.xyz/v1/graphql ``` You can explore and test queries interactively using the [GraphiQL explorer](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.testnet.movementnetwork.xyz%2Fv1%2Fgraphql). ### Schema The `current_aptos_names` table contains all MNS name records with the following fields: | Field | Type | Description | | -------------------------- | --------- | ------------------------------------------------ | | `domain` | String | The domain name (without suffix) | | `domain_with_suffix` | String | Full domain name (e.g. `mydomain.move`) | | `owner_address` | String | Address that owns the name NFT | | `registered_address` | String | Address the name resolves to | | `is_primary` | Boolean | Whether this is the primary name for the address | | `is_active` | Boolean | Whether the name is currently active | | `expiration_timestamp` | timestamp | When the domain registration expires | | `subdomain` | String | Subdomain portion if applicable | | `token_standard` | String | Token standard (v2) | | `last_transaction_version` | bigint | Last transaction that modified this record | ### Lookup a Domain by Name ```graphql { current_aptos_names( where: { domain: { _eq: "mydomain" }, is_active: { _eq: true } } ) { domain domain_with_suffix owner_address registered_address is_primary expiration_timestamp } } ``` ### Get All Names Owned by an Address ```graphql { current_aptos_names( where: { owner_address: { _eq: "0x..." } is_active: { _eq: true } } ) { domain domain_with_suffix is_primary registered_address expiration_timestamp } } ``` ### Get Primary Name for an Address ```graphql { current_aptos_names( where: { registered_address: { _eq: "0x..." } is_primary: { _eq: true } is_active: { _eq: true } } ) { domain domain_with_suffix owner_address } } ``` ### Count All Active Names ```graphql { current_aptos_names_aggregate( where: { is_active: { _eq: true } } ) { aggregate { count } } } ``` ### Using the Indexer in TypeScript ```typescript const INDEXER_URL = "https://indexer.testnet.movementnetwork.xyz/v1/graphql"; async function getNamesByOwner(ownerAddress: string) { const query = ` query GetNamesByOwner($owner: String!) { current_aptos_names( where: { owner_address: { _eq: $owner } is_active: { _eq: true } } ) { domain domain_with_suffix is_primary registered_address expiration_timestamp } } `; const response = await fetch(INDEXER_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, variables: { owner: ownerAddress }, }), }); const { data } = await response.json(); return data.current_aptos_names; } ``` # Network Endpoints URL: /devs/networkEndpoints This page contains public endpoints that may at times be subject to rate limits. All partners listed below have paid private nodes as well for scaling your applications. ### Network Status **View the network status and uptime performance of Movement Network here: [https://status.movementnetwork.xyz/](https://status.movementnetwork.xyz/)** ## Movement Mainnet **Chain ID: 126** | Service | URL | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------- | | RPC (Primary) | [https://mainnet.movementnetwork.xyz/v1](https://mainnet.movementnetwork.xyz/v1) | | Bridge | [https://bridge.movementnetwork.xyz/](https://bridge.movementnetwork.xyz/) | | Explorer | [https://explorer.movementnetwork.xyz/?network=mainnet](https://explorer.movementnetwork.xyz/?network=mainnet) | | Indexer Explorer | [Explorer](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.mainnet.movementnetwork.xyz%2Fv1%2Fgraphql) | | Indexer Endpoint | [https://indexer.mainnet.movementnetwork.xyz/v1/graphql](https://indexer.mainnet.movementnetwork.xyz/v1/graphql) | ### Partner Endpoints The following public endpoints are provided by Movement's ecosystem infrastructure providers. All of the partners below also have paid private nodes. To learn more about setting up a private node for your project, see their website below. | Name | RPC Endpoint | | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | [Sentio](https://app.sentio.xyz/) | [https://rpc.sentio.xyz/movement/v1](https://rpc.sentio.xyz/movement/v1) | | [Hello Moon (API Key Required)](https://www.hellomoon.io/) | [https://movement.hellomoon.io/v1](https://movement.hellomoon.io/v1) | | [BlockPi](https://blockpi.io/) | [https://movement.blockpi.network/rpc/v1/public/v1](https://movement.blockpi.network/rpc/v1/public/v1) | | [Lava Network](https://www.lavanet.xyz/) | [https://movement.lava.build/](https://movement.lava.build/) | | [Ankr](https://www.ankr.com/) | [https://rpc.ankr.com/http/movement\_mainnet/v1](https://rpc.ankr.com/http/movement_mainnet/v1) | #### Partner Indexers | Name | Indexer Endpoint | | --------------------------------- | -------------------------------------------------------------------------------------------------------- | | [Sentio](https://app.sentio.xyz/) | [https://rpc.sentio.xyz/movement-indexer/v1/graphql](https://rpc.sentio.xyz/movement-indexer/v1/graphql) | ## Movement Testnet **Chain ID: 250** | Service | URL | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | | RPC | [https://testnet.movementnetwork.xyz/v1](https://testnet.movementnetwork.xyz/v1) | | Faucet endpoint | [https://faucet.testnet.movementnetwork.xyz/](https://faucet.testnet.movementnetwork.xyz/) | | Faucet UI | [https://faucet.movementnetwork.xyz/](https://faucet.movementnetwork.xyz/) | | Explorer | [https://explorer.movementnetwork.xyz/?network=bardock+testnet](https://explorer.movementnetwork.xyz/?network=bardock+testnet)  | | Indexer Explorer | [Explorer](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Findexer.testnet.movementnetwork.xyz%2Fv1%2Fgraphql) | | Indexer Endpoint | [https://hasura.testnet.movementnetwork.xyz/v1/graphql](https://hasura.testnet.movementnetwork.xyz/v1/graphql) | ## Network Properties | | Testnet | | -------------------- | ------------------------------------------------------------------------------- | | Validators | Move Industries operated validators | | Full Nodes | Permissionless + Move Industries operated nodes | | Data Durability | Data wipes will be announced ahead of time. | | Announcement Channel | [Discord](https://discord.com/channels/1101576619493167217/1259638014184001668) | | Network Uptime | Constant uptime. | # Oracles URL: /devs/oracles This document explains how to use real-time data from [Pyth Network](https://www.pyth.network/) in modules on the Movement Bardock testnet. ## Configuring the Move.toml file Add the Pyth Contract to your project dependencies in the Move.toml file like so: ``` [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 `deployer` must be defined at compile time. These addresses are used to interact with the Pyth contract on Movement. The Pyth smart contracts are deployed on the Movement Network on the following addresses: | Name | Address | | -------- | ------------------------------------------------------------------ | | pyth | 0x9357e76fe965c9956a76181ee49f66d51b7f9c3800182a944ed96be86301e49f | | wormhole | 0x9236893d6444b208b7e0b3e8d4be4ace90b6d17817ab7d1584e46a33ef5c50c9 | | deployer | 0xa3ad2d9c8114b9a4fe97d45b7a9d3c731148d936b0f5dd396fc20a53a11a70da | ## Example 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) } } ``` The code snippet above does the following: 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 you wish to read. ## API Reference The Pyth contract exposes a complete API for reading and updating price feeds; the Pyth Aptos integration is also compatible with Movement. ## 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), which uses Pyth price feeds to mint an NFT. # Templates and Other Docs URL: /devs/templates Below you can find a list of templates and documentations for Wallet SDKs from many of our partnered wallets. In some cases you will have full templates for you to start from that can auto detect any Aptos Wallets, in other cases you will have documentation for you to implement their specific SDK into your dApp. OKX Connect Documentation Starter Template for Nightly Connect # Getting Started URL: /general High level overview of the Move Language How the Movement Network functions Start building apps on the Movement Network Build your first Move Module Get Testnet tokens Our Block Explorer # Encode submission URL: /api/node/encode_submission {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} This endpoint accepts an EncodeSubmissionRequest, which internally is a UserTransactionRequestInner (and optionally secondary signers) encoded as JSON, validates the request format, and then returns that request encoded in BCS. The client can then use this to create a transaction signature to be used in a SubmitTransactionRequest, which it then passes to the /transactions POST endpoint. To be clear, this endpoint makes it possible to submit transaction requests to the API from languages that do not have library support for BCS. If you are using an SDK that has BCS support, such as the official Rust, TypeScript, or Python SDKs, you do not need to use this endpoint. To sign a message using the response from this endpoint: * Decode the hex encoded string in the response to bytes. * Sign the bytes to create the signature. * Use that as the signature field in something like Ed25519Signature, which you then use to build a TransactionSignature. # Estimate gas price URL: /api/node/estimate_gas_price {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Gives an estimate of the gas unit price required to get a transaction on chain in a reasonable amount of time. The gas unit price is the amount that each transaction commits to pay for each unit of gas consumed in executing the transaction. The estimate is based on recent history: it gives the minimum gas that would have been required to get into recent blocks, for blocks that were full. (When blocks are not full, the estimate will match the minimum gas unit price.) The estimation is given in three values: de-prioritized (low), regular, and prioritized (aggressive). Using a more aggressive value increases the likelihood that the transaction will make it into the next block; more aggressive values are computed with a larger history and higher percentile statistics. More details are in AIP-34. # Get account URL: /api/node/get_account {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Return the authentication key and the sequence number for an account address. Optionally, a ledger version can be specified. If the ledger version is not specified in the request, the latest ledger version is used. # Get account module URL: /api/node/get_account_module {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieves an individual module from a given account and at a specific ledger version. If the ledger version is not specified in the request, the latest ledger version is used. The Aptos nodes prune account state history, via a configurable time window. If the requested ledger version has been pruned, the server responds with a 410. # Get account modules URL: /api/node/get_account_modules {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieves all account modules' bytecode for a given account at a specific ledger version. If the ledger version is not specified in the request, the latest ledger version is used. The Aptos nodes prune account state history, via a configurable time window. If the requested ledger version has been pruned, the server responds with a 410. # Get account resource URL: /api/node/get_account_resource {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieves an individual resource from a given account and at a specific ledger version. If the ledger version is not specified in the request, the latest ledger version is used. The Aptos nodes prune account state history, via a configurable time window. If the requested ledger version has been pruned, the server responds with a 410. # Get account resources URL: /api/node/get_account_resources {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieves all account resources for a given account and a specific ledger version. If the ledger version is not specified in the request, the latest ledger version is used. The Aptos nodes prune account state history, via a configurable time window. If the requested ledger version has been pruned, the server responds with a 410. # Get account transactions URL: /api/node/get_account_transactions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieves on-chain committed transactions from an account. If the start version is too far in the past, a 410 will be returned. If no start version is given, it will start at version 0. To retrieve a pending transaction, use /transactions/by\_hash. # Get blocks by height URL: /api/node/get_block_by_height {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} This endpoint allows you to get the transactions in a block and the corresponding block information. Transactions are limited by max default transactions size. If not all transactions are present, the user will need to query for the rest of the transactions via the get transactions API. If the block is pruned, it will return a 410 # Get blocks by version URL: /api/node/get_block_by_version {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} This endpoint allows you to get the transactions in a block and the corresponding block information given a version in the block. Transactions are limited by max default transactions size. If not all transactions are present, the user will need to query for the rest of the transactions via the get transactions API. If the block has been pruned, it will return a 410 # Get events by creation number URL: /api/node/get_events_by_creation_number {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Event types are globally identifiable by an account `address` and monotonically increasing `creation_number`, one per event type emitted to the given account. This API returns events corresponding to that that event type. # Get events by event handle URL: /api/node/get_events_by_event_handle {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} This API uses the given account `address`, `eventHandle`, and `fieldName` to build a key that can globally identify an event types. It then uses this key to return events emitted to the given account matching that event type. # Get ledger info URL: /api/node/get_ledger_info {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Get the latest ledger information, including data such as chain ID, role type, ledger versions, epoch, etc. # Get raw table item URL: /api/node/get_raw_table_item {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Get a table item at a specific ledger version from the table identified by `{table_handle}` in the path and the "key" (RawTableItemRequest) provided in the request body. The `get_raw_table_item` requires only a serialized key comparing to the full move type information comparing to the `get_table_item` api, and can only return the query in the bcs format. The Aptos nodes prune account state history, via a configurable time window. If the requested ledger version has been pruned, the server responds with a 410. # Get table item URL: /api/node/get_table_item {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Get a table item at a specific ledger version from the table identified by `{table_handle}` in the path and the "key" (TableItemRequest) provided in the request body. This is a POST endpoint because the "key" for requesting a specific table item (TableItemRequest) could be quite complex, as each of its fields could themselves be composed of other structs. This makes it impractical to express using query params, meaning GET isn't an option. The Aptos nodes prune account state history, via a configurable time window. If the requested ledger version has been pruned, the server responds with a 410. # Get transaction by hash URL: /api/node/get_transaction_by_hash {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Look up a transaction by its hash. This is the same hash that is returned by the API when submitting a transaction (see PendingTransaction). When given a transaction hash, the server first looks for the transaction in storage (on-chain, committed). If no on-chain transaction is found, it looks the transaction up by hash in the mempool (pending, not yet committed). To create a transaction hash by yourself, do the following: 1. Hash message bytes: "RawTransaction" bytes + BCS bytes of [Transaction](https://aptos-labs.github.io/aptos-core/aptos_types/transaction/enum.Transaction.html). 2. Apply hash algorithm `SHA3-256` to the hash message bytes. 3. Hex-encode the hash bytes with `0x` prefix. # Get transaction by version URL: /api/node/get_transaction_by_version {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieves a transaction by a given version. If the version has been pruned, a 410 will be returned. # Get transactions URL: /api/node/get_transactions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Retrieve on-chain committed transactions. The page size and start ledger version can be provided to get a specific sequence of transactions. If the version has been pruned, then a 410 will be returned. To retrieve a pending transaction, use /transactions/by\_hash. # Check basic node health URL: /api/node/healthy {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} By default this endpoint just checks that it can get the latest ledger info and then returns 200. If the duration\_secs param is provided, this endpoint will return a 200 if the following condition is true: `server_latest_ledger_info_timestamp >= server_current_time_timestamp - duration_secs` # Simulate transaction URL: /api/node/simulate_transaction {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} The output of the transaction will have the exact transaction outputs and events that running an actual signed transaction would have. However, it will not have the associated state hashes, as they are not updated in storage. This can be used to estimate the maximum gas units for a submitted transaction. To use this, you must: * Create a SignedTransaction with a zero-padded signature. * Submit a SubmitTransactionRequest containing a UserTransactionRequest containing that signature. To use this endpoint with BCS, you must submit a SignedTransaction encoded as BCS. See SignedTransaction in types/src/transaction/mod.rs. # Show OpenAPI explorer URL: /api/node/spec {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Provides a UI that you can use to explore the API. You can also retrieve the API directly at `/spec.yaml` and `/spec.json`. # Submit batch transactions URL: /api/node/submit_batch_transactions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} This allows you to submit multiple transactions. The response has three outcomes: 1. All transactions succeed, and it will return a 202 2. Some transactions succeed, and it will return the failed transactions and a 206 3. No transactions succeed, and it will also return the failed transactions and a 206 To submit a transaction as JSON, you must submit a SubmitTransactionRequest. To build this request, do the following: 1. Encode the transaction as BCS. If you are using a language that has native BCS support, make sure to use that library. If not, you may take advantage of /transactions/encode\_submission. When using this endpoint, make sure you trust the node you're talking to, as it is possible they could manipulate your request. 2. Sign the encoded transaction and use it to create a TransactionSignature. 3. Submit the request. Make sure to use the "application/json" Content-Type. To submit a transaction as BCS, you must submit a SignedTransaction encoded as BCS. See SignedTransaction in types/src/transaction/mod.rs. Make sure to use the `application/x.aptos.signed_transaction+bcs` Content-Type. # Submit transaction URL: /api/node/submit_transaction {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} This endpoint accepts transaction submissions in two formats. To submit a transaction as JSON, you must submit a SubmitTransactionRequest. To build this request, do the following: 1. Encode the transaction as BCS. If you are using a language that has native BCS support, make sure of that library. If not, you may take advantage of /transactions/encode\_submission. When using this endpoint, make sure you trust the node you're talking to, as it is possible they could manipulate your request. 2. Sign the encoded transaction and use it to create a TransactionSignature. 3. Submit the request. Make sure to use the "application/json" Content-Type. To submit a transaction as BCS, you must submit a SignedTransaction encoded as BCS. See SignedTransaction in types/src/transaction/mod.rs. Make sure to use the `application/x.aptos.signed_transaction+bcs` Content-Type. # Execute view function of a module URL: /api/node/view {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Execute the Move function with the given parameters and return its execution result. The Aptos nodes prune account state history, via a configurable time window. If the requested ledger version has been pruned, the server responds with a 410. # Wait for transaction by hash URL: /api/node/wait_transaction_by_hash {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} Same as /transactions/by\_hash, but will wait for a pending transaction to be committed. To be used as a long poll optimization by clients, to reduce latency caused by polling. The "long" poll is generally a second or less but dictated by the server; the client must deal with the result as if the request was a normal /transactions/by\_hash request, e.g., by retrying if the transaction is pending. # NEAR Intents SDK URL: /devs/interactonchain/near-intents-sdk # NEAR Intents SDK The [`@moveindustries/near-intents-sdk`](https://github.com/MoveIndustries/near-intents-sdk) is a thin TypeScript SDK for moving **USDC or USDT** from a supported origin chain onto **Movement** — landing as **USDCx** or **MOVE** — over [NEAR Intents](https://docs.near-intents.org). It wraps the official [1Click API](https://docs.near-intents.org/near-intents/integration/distribution-channels/1click-api) and pins the destination to Movement, so a transfer can't accidentally land on the wrong chain. It orchestrates the transfer — get a quote, build the deposit, track status — and **never signs, broadcasts, or holds keys**. Your own wallet signs the one on-chain step. Just want to move funds yourself without writing code? Use the [NEAR Intents app](https://near.com) instead — see [NEAR Intents](/general/usingmovement/near-intents) for the no-code onboarding flow. **The flow in four steps:** 1. **Quote** a route (origin chain + asset → Movement asset) and receive a one-time deposit address. 2. **Build** the unsigned deposit transaction with `prepareDepositTx` — a plain token transfer to that address. 3. **Sign & broadcast** it with your own wallet. 4. **Track** execution status until it reaches a terminal state. The destination is always Movement. You choose the origin (Ethereum, Polygon, or Tron), the origin asset (USDC or USDT), and whether you receive USDCx or MOVE on Movement. ## Installation ```bash tab="npm" npm install @moveindustries/near-intents-sdk ``` ```bash tab="pnpm" pnpm add @moveindustries/near-intents-sdk ``` ```bash tab="yarn" yarn add @moveindustries/near-intents-sdk ``` ```bash tab="bun" bun add @moveindustries/near-intents-sdk ``` ## Quick start The full end-to-end transfer of **1 USDC on Ethereum → USDCx on Movement**. It runs without any credentials. ```ts import { configure, quoteDeposit, prepareDepositTx, submitDeposit, getStatus, isTerminal, } from "@moveindustries/near-intents-sdk"; // configure() is optional — omit it entirely to run token-free (see Authentication). configure({ jwt: process.env.ONE_CLICK_JWT }); // 1. Quote the route. Amounts are in the origin asset's smallest units. const res = await quoteDeposit({ originChain: "ethereum", // "ethereum" | "polygon" | "tron" originAsset: "usdc", // "usdc" | "usdt" (Tron is USDT-only) destinationAsset: "usdcx", // "usdcx" | "move" amount: "1000000", // 1.0 USDC (6 decimals) recipient: "0xYourMovementAddress", refundTo: "0xYourEthereumAddress", minAmountOut: "995000", // required floor, destination asset's smallest units; "0" opts out }); const { depositAddress, amountOut, deadline } = res.quote; console.log(`Send to ${depositAddress}, you'll receive ~${amountOut} before ${deadline}`); // 2. Build the unsigned deposit transfer. Your wallet signs and broadcasts it. const depositTx = prepareDepositTx("ethereum", "usdc", res); // ...sign & broadcast `depositTx` with your wallet (see per-chain examples below)... const txHash = "0xYourDepositTxHash"; // 3. (Optional) Hand 1Click the tx hash to speed up deposit detection. await submitDeposit(depositAddress!, txHash); // 4. Poll status until it settles. let status = (await getStatus(depositAddress!)).status; while (!isTerminal(status)) { await new Promise((r) => setTimeout(r, 5000)); status = (await getStatus(depositAddress!)).status; } console.log("Final:", status); // "SUCCESS" | "REFUNDED" | "FAILED" ``` Amounts are strings in the origin asset's **smallest units**, not decimals. USDC and USDT use 6 decimals, so `"1000000"` = 1.0 USDC and `"20000"` = 0.02 USDC. ## Authentication **A 1Click JWT is optional — the full transfer works without one.** Quoting, deposit-address binding, and status tracking all succeed unauthenticated. Pass a JWT for two reasons: * **Waive the protocol fee.** Without a token, NEAR auto-injects its `appFees` and takes a small cut of the swap. * **Attributed rate limits** instead of anonymous, IP-based ones. Obtain one at the [NEAR Intents Partners Portal](https://partners.near-intents.org) and pass it to `configure`: ```ts configure({ jwt: process.env.ONE_CLICK_JWT }); ``` The token is yours to manage. The SDK attaches the JWT you provide to its requests; it does not create or renew them. Obtain a token from the Partners Portal and replace it before it expires. ## Using it in the browser The SDK works in the browser without additional setup: with no JWT it calls 1Click directly, and every step except the fee waiver functions normally. A proxy is required only when combining JWT benefits (fee waiver, attributed rate limits) with client-side use, since a JWT is a secret and must **never** be exposed to the client. To use a JWT from the browser, place a server-side proxy in front of 1Click to inject the token, and point the SDK at it with `baseUrl` (omit `jwt`; the proxy holds it): ```ts // Client code — no secret here. Requests go to your proxy, which adds the JWT. configure({ baseUrl: "/api/1click" }); ``` The proxy forwards the four paths the SDK uses (`v0/tokens`, `v0/quote`, `v0/status`, `v0/deposit/submit`) and adds the `Authorization` header server-side. A minimal Next.js Route Handler:
Example proxy — Next.js Route Handler ```ts // app/api/1click/[...path]/route.ts import { NextRequest, NextResponse } from "next/server"; const BASE = process.env.ONECLICK_BASE_URL ?? "https://1click.chaindefuser.com"; const JWT = process.env.ONECLICK_JWT; // server-side only — never NEXT_PUBLIC_ // Only the paths the SDK uses may be proxied. const ALLOWED = new Set(["v0/tokens", "v0/quote", "v0/status", "v0/deposit/submit"]); async function forward(method: "GET" | "POST", req: NextRequest, path: string[]) { const joined = path.join("/"); if (!ALLOWED.has(joined)) { return NextResponse.json({ error: "Path not allowed" }, { status: 400 }); } const url = new URL(`${BASE}/${joined}`); req.nextUrl.searchParams.forEach((v, k) => url.searchParams.set(k, v)); const res = await fetch(url.toString(), { method, headers: { ...(JWT ? { Authorization: `Bearer ${JWT}` } : {}), ...(method === "POST" ? { "Content-Type": "application/json" } : {}), }, body: method === "POST" ? await req.text() : undefined, }); const data = await res.json().catch(() => ({})); return NextResponse.json(data, { status: res.status }); } export async function GET(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { return forward("GET", req, (await params).path); } export async function POST(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) { return forward("POST", req, (await params).path); } ```
Server-side scripts and backends require no proxy — with no `baseUrl`, the SDK calls 1Click directly and the JWT stays private. ## The deposit transaction `prepareDepositTx(originChain, originAsset, quote)` returns an **unsigned** transfer of the quote's `amountIn` to its `depositAddress`. The shape depends on the origin chain family: ```ts // EVM (Ethereum, Polygon) { family: "evm", to: string, value: "0x0", data: string } // Tron { family: "tron", contractAddress: string, function: "transfer(address,uint256)", parameter: [depositAddress, amountIn] } ``` The SDK already knows each chain's USDC/USDT contract, so you don't pass a token address — everything needed to sign is in the returned object. The per-chain examples below show how to hand it to a wallet. The origin wallet needs the chain's **native gas token** to send the deposit (ETH on Ethereum, POL on Polygon, TRX on Tron) — the SDK moves the stablecoin, not gas. ## Complete examples Each example runs the full flow for one route. They share the `waitForSettlement` helper: ```ts import { getStatus, isTerminal } from "@moveindustries/near-intents-sdk"; async function waitForSettlement(depositAddress: string) { for (;;) { const { status } = await getStatus(depositAddress); if (isTerminal(status)) return status; // SUCCESS | REFUNDED | FAILED await new Promise((r) => setTimeout(r, 5000)); } } ``` ### EVM (Ethereum or Polygon, viem) One flow covers both EVM origins — only `originChain` and the viem `chain` differ. Shown server-side with a private key (JWT stays server-side, no proxy needed); the inline notes mark what changes in the browser and when the destination is MOVE. The EVM deposit is a raw transaction built entirely from `prepareDepositTx`. ```ts import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { mainnet } from "viem/chains"; // Polygon: import { polygon } import { configure, quoteDeposit, prepareDepositTx, submitDeposit } from "@moveindustries/near-intents-sdk"; // Server-side, JWT stays private: configure({ jwt: process.env.ONE_CLICK_JWT }); // optional // In the browser instead: configure({ baseUrl: "/api/1click" }) — the proxy holds the JWT. const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const wallet = createWalletClient({ account, chain: mainnet, transport: http() }); const res = await quoteDeposit({ originChain: "ethereum", // or "polygon" originAsset: "usdc", destinationAsset: "usdcx", // for "move", also raise slippageTolerance below amount: "1000000", // 1.0 USDC recipient: "0xYourMovementAddress", refundTo: account.address, minAmountOut: "995000", // required floor, destination asset's smallest units; "0" opts out // slippageTolerance: 300, // MOVE only: 3% — MOVE is volatile; the 1% default often refunds }); const { depositAddress } = res.quote; // prepareDepositTx returns { family: "evm", to, value: "0x0", data } — send it as-is. const tx = prepareDepositTx("ethereum", "usdc", res); // match originChain above const txHash = await wallet.sendTransaction({ account, chain: mainnet, to: tx.to as `0x${string}`, value: 0n, data: tx.data as `0x${string}`, }); await submitDeposit(depositAddress!, txHash); // optional speed-up console.log("Settled:", await waitForSettlement(depositAddress!)); ``` ### Tron → USDCx (injected TronLink) Tron is USDT-only. The deposit is a TRC-20 transfer through the injected `window.tronWeb` provider (TronLink): ```ts import { configure, quoteDeposit, prepareDepositTx, submitDeposit } from "@moveindustries/near-intents-sdk"; configure({ baseUrl: "/api/1click" }); async function bridgeTronToUsdcx(recipient: string, refundTo: string) { const tronWeb = (window as any).tronWeb; if (!tronWeb?.defaultAddress?.base58) throw new Error("Connect TronLink"); const res = await quoteDeposit({ originChain: "tron", originAsset: "usdt", destinationAsset: "usdcx", amount: "1000000", // 1.0 USDT recipient, refundTo, // your Tron address, for refunds minAmountOut: "995000", // required floor, destination asset's smallest units; "0" opts out }); const { depositAddress } = res.quote; // prepareDepositTx returns { family: "tron", contractAddress, function, parameter: [to, amount] } const tx = prepareDepositTx("tron", "usdt", res); const contract = await tronWeb.contract().at(tx.contractAddress); const txId: string = await contract.transfer(tx.parameter[0], tx.parameter[1]).send(); await submitDeposit(depositAddress!, txId); return waitForSettlement(depositAddress!); } ``` ### Tron → USDCx (TronWallet Adapter) To avoid using `window.tronWeb` directly, use the [TronWallet Adapter](https://github.com/tronprotocol/tronwallet-adapter) React hooks. You build the transaction with a standalone `TronWeb` instance, sign it with the adapter's `signTransaction`, then broadcast. This maps cleanly onto `prepareDepositTx`'s Tron output. ```ts import { TronWeb } from "tronweb"; import { useWallet } from "@tronweb3/tronwallet-adapter-react-hooks"; import { configure, quoteDeposit, prepareDepositTx, submitDeposit } from "@moveindustries/near-intents-sdk"; configure({ baseUrl: "/api/1click" }); // A read-only client just for building + broadcasting; the adapter does the signing. const tronWeb = new TronWeb({ fullHost: "https://api.trongrid.io" }); function BridgeButton({ recipient }: { recipient: string }) { const { address, signTransaction, connected } = useWallet(); async function bridge() { if (!connected || !address) throw new Error("Connect a Tron wallet"); const res = await quoteDeposit({ originChain: "tron", originAsset: "usdt", destinationAsset: "usdcx", amount: "1000000", // 1.0 USDT recipient, refundTo: address, minAmountOut: "995000", // required floor, destination asset's smallest units; "0" opts out }); const { depositAddress } = res.quote; const tx = prepareDepositTx("tron", "usdt", res); // Build the TRC-20 transfer from prepareDepositTx's contract + params. const { transaction } = await tronWeb.transactionBuilder.triggerSmartContract( tx.contractAddress, tx.function, // "transfer(address,uint256)" {}, [ { type: "address", value: tx.parameter[0] }, // deposit address { type: "uint256", value: tx.parameter[1] }, // amountIn ], address, ); const signed = await signTransaction(transaction); const receipt = await tronWeb.trx.sendRawTransaction(signed); const txId: string = receipt.txid; await submitDeposit(depositAddress!, txId); return waitForSettlement(depositAddress!); } return ; } ``` The adapter needs its provider set up once near the root of your app — wrap it in `WalletProvider` from `@tronweb3/tronwallet-adapter-react-hooks` (and optionally `WalletModalProvider` from `@tronweb3/tronwallet-adapter-react-ui`). See the [adapter docs](https://developers.tron.network/docs/tronwallet-adapter) for setup. ## Listing supported tokens `listTokens` returns the live set of supported tokens, filtered to the SDK's routes and tagged with the route keys `quoteDeposit` expects — so you can feed a token straight into a quote without any lookup: ```ts import { listTokens } from "@moveindustries/near-intents-sdk"; const tokens = await listTokens(); // each token carries: assetId, symbol, decimals, chain, contractAddress, price, // and its route keys — originChain, originAsset (sources) or destinationAsset (Movement) ``` ## Dry-run quotes Pass `dry: true` to `quoteDeposit` to price a route without reserving a deposit address — useful for showing an estimate before the user commits. A dry quote has no `depositAddress`, so don't pass it to `prepareDepositTx`. ```ts const preview = await quoteDeposit({ originChain: "ethereum", originAsset: "usdc", destinationAsset: "usdcx", amount: "1000000", recipient: "0xYourMovementAddress", refundTo: "0xYourEthereumAddress", minAmountOut: "0", // required; "0" opts out — fine for a price-only preview dry: true, }); console.log("You'd receive ~", preview.quote.amountOutFormatted ?? preview.quote.amountOut); ``` ## Supported routes | Origin chain | Assets | Movement destination | | ------------ | ---------- | -------------------- | | Ethereum | USDC, USDT | USDCx or MOVE | | Polygon | USDC, USDT | USDCx or MOVE | | Tron | USDT | USDCx or MOVE | When your destination is **MOVE**, raise `slippageTolerance` — MOVE is volatile, and the default 1% is often missed, causing a refund instead of a completed swap. `300` (3%) is a reasonable starting point. For the \~1:1 USDCx route, the default is fine. ## API reference | Export | Purpose | | ---------------------------------------------- | -------------------------------------------------------------------------------------------- | | `configure({ jwt?, baseUrl? })` | Optional. Set a 1Click JWT and/or point the SDK at a server-side proxy. | | `quoteDeposit(params)` | Quote a route; returns the quote with `depositAddress`, `amountIn`, `amountOut`, `deadline`. | | `prepareDepositTx(origin, originAsset, quote)` | Build the **unsigned** deposit transfer (`EvmDepositTx` or `TronDepositTx`) for your wallet. | | `submitDeposit(depositAddress, txHash)` | Optional. Hand 1Click the deposit tx hash to speed up detection. | | `getStatus(depositAddress)` | Read the transfer's current execution status once. | | `isTerminal(status)` | `true` for `SUCCESS`, `REFUNDED`, or `FAILED`. | | `listTokens()` | Live, route-tagged list of supported tokens (`SupportedToken[]`). | | `MOVEMENT`, `ORIGINS` | Registry objects: destination and origin assets with their `assetId` / `decimals`. | **Exported types:** `QuoteDepositParams`, `DepositTx`, `EvmDepositTx`, `TronDepositTx`, `SupportedToken`, `OriginKey`, `StableKey`, `DestKey`. ### `quoteDeposit` parameters | Field | Type | Notes | | -------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------ | | `originChain` | `"ethereum" \| "polygon" \| "tron"` | Source chain. | | `originAsset` | `"usdc" \| "usdt"` | Source asset. Tron is USDT-only. | | `destinationAsset` | `"usdcx" \| "move"` | What you receive on Movement. | | `amount` | `string` | Smallest units of the origin asset (USDC/USDT: 6 dp). | | `recipient` | `string` | Your Movement address. | | `refundTo` | `string` | Origin-chain address to refund if the swap can't settle. | | `minAmountOut` | `string` | Floor in the destination asset's smallest units; the quote is rejected below it. `"0"` opts out. | | `slippageTolerance?` | `number` | Basis points. Defaults to `100` (1%). Raise it for MOVE. | | `deadline?` | `string` | ISO timestamp. Defaults to 10 minutes from now. | | `dry?` | `boolean` | Price-only preview; no deposit address is reserved. | ## Learn more * [SDK repository & README](https://github.com/MoveIndustries/near-intents-sdk) * [NEAR Intents documentation](https://docs.near-intents.org) * [1Click API reference](https://docs.near-intents.org/near-intents/integration/distribution-channels/1click-api) # Python SDK URL: /devs/interactonchain/pythonSDK The Movement network is compatible with the Aptos Python SDK, which provides a comprehensive interface for blockchain interactions. The SDK is available on [PyPi](https://pypi.org/project/aptos-sdk/) with source code in the [aptos-python-sdk GitHub repository](https://github.com/aptos-labs/aptos-python-sdk). This guide demonstrates how to use the Python SDK to interact with smart contracts deployed on the Movement network. ## Installation ### Install with pip ```bash pip3 install aptos-sdk ``` ### Install from source code ```bash git clone https://github.com/aptos-labs/aptos-python-sdk pip3 install . --user ``` ### Install by embedding ```bash cd /path/to/python/project cp -r /path/to/aptos-python-sdk aptos-sdk ``` ## Getting Started This tutorial demonstrates how to use the Python SDK to interact with a smart contract deployed on the Movement network. We'll build a complete example that shows account management, balance checking, and smart contract interactions. ### Create Project Directory ```bash mkdir python-sdk-example cd python-sdk-example ``` ### Create Environment Configuration Create a `.env` file to store your configuration: ```bash touch .env ``` Add the following configuration to your `.env` file: ``` PRIVATE_KEY=YOUR_PRIVATE_KEY RPC_URL = "https://testnet.movementnetwork.xyz/v1" FAUCET_URL = "https://faucet.testnet.movementnetwork.xyz/" MODULE_ADDRESS=YOUR_MODULE_ADDRESS ``` **Note**: Replace the placeholder values with your actual private key and module address. You can generate a private key using the Movement CLI or other wallet tools. ### Required Imports First, let's import all the necessary modules for our Movement SDK client: ```python import asyncio import os from typing import Optional, Any, Dict from aptos_sdk.ed25519 import PrivateKey from aptos_sdk.async_client import FaucetClient, RestClient from aptos_sdk.account import Account, AccountAddress from aptos_sdk.transactions import ( EntryFunction, TransactionPayload, TransactionArgument ) from aptos_sdk.bcs import Serializer from dotenv import load_dotenv ``` ## Create the Movement Client Class We'll create a comprehensive client class that handles all interactions with Movement smart contracts: ```python class MovementSmartContractClient: """A client for interacting with Movement smart contracts, specifically the message contract.""" ``` ### Initialize the Client The constructor initializes the client with network configuration and sets up the necessary components: * **RestClient**: Handles RPC calls to the Movement network * **FaucetClient**: Manages account funding for testing * **Account**: Manages the user's account and private key ```python def __init__(self, private_key: str, rpc_url: str, faucet_url: str, module_address: str = ""): """ Initialize the Movement client with network configuration. Args: private_key: The private key for the account (with or without 0x prefix) rpc_url: The RPC URL for the Movement network faucet_url: The faucet URL for funding accounts module_address: The address of the deployed smart contract module """ # Initialize clients self.rest_client = RestClient(rpc_url) self.faucet_client = FaucetClient(faucet_url, self.rest_client) # Setup account if private_key.startswith("0x"): private_key = private_key[2:] self.private_key = PrivateKey.from_hex(private_key) self.account_address = AccountAddress.from_key(self.private_key.public_key()) self.account = Account(account_address=self.account_address, private_key=self.private_key) # Contract configuration self.module_address = module_address self.set_message_function = f"message::set_message" self.get_message_function = f"message::get_message" print(f"Account address: {self.account.address()}") ``` ### Account Management Methods #### Fund Account The `fund_account` method uses the faucet client to add test tokens to your account: ```python async def fund_account(self, amount: int = 100000000) -> None: """ Fund the account using the faucet. Args: amount: Amount to fund in Octas (default: 1 APT = 100000000 Octas) """ try: await self.faucet_client.fund_account(self.account.address(), amount) print(f"Successfully funded account with {amount} Octas") except Exception as e: print(f"Error funding account: {e}") ``` #### Check Balance The `get_balance` method retrieves the current MOVE token balance in Octas (1 MOVE = 100,000,000 Octas): ```python async def get_balance(self) -> int: """ Get the current account balance. Returns: Account balance in Octas """ try: balance = await self.rest_client.account_balance(self.account.address()) return balance except Exception as e: print(f"Error getting balance: {e}") return 0 ``` ### Smart Contract Interaction Methods #### Read Contract State The `get_message` method reads data from the smart contract without modifying the blockchain state: ```python async def get_message(self, account_address: Optional[str] = None) -> Optional[Dict[str, Any]]: """ Retrieve the resource message::MessageHolder::message Args: account_address: The account address to read message for (defaults to current account) Returns: The message holder resource or None if not found """ if not self.module_address: raise ValueError("Module address not set. Please provide module_address in constructor.") target_address = AccountAddress.from_str(account_address) if account_address else self.account.address() try: # Get the get_message resource from the account resource = await self.rest_client.view( f"{self.module_address}::{self.get_message_function}", [], [str(target_address)] ) print(f"Message resource: {resource}") return resource except Exception as e: print(f"Error reading message: {e}") return None ``` #### Write to Contract The `set_message` method submits a transaction to modify the smart contract state: ```python async def set_message(self, message: str) -> Optional[str]: """ Potentially initialize and set the resource message::MessageHolder::message Args: message: The message to store Returns: Transaction hash if successful, None otherwise """ if not self.module_address: raise ValueError("Module address not set. Please provide module_address in constructor.") try: print("\n=== Submitting Transaction ===") # Create the transaction payload using the correct format payload = EntryFunction.natural( f"{self.module_address}::{self.set_message_function}", "set_message", [], [TransactionArgument(message, Serializer.str)] ) # Create and submit the BCS signed transaction signed_transaction = await self.rest_client.create_bcs_signed_transaction( self.account, TransactionPayload(payload) ) # Submit the transaction txn_hash = await self.rest_client.submit_bcs_transaction(signed_transaction) print(f"Submitted transaction: {txn_hash}") # Wait for transaction confirmation await self.rest_client.wait_for_transaction(txn_hash) print(f"Transaction confirmed: {txn_hash}") return txn_hash except Exception as e: print(f"Error setting message: {e}") return None ``` ### Complete Example Usage Here's a complete example that demonstrates all the functionality: ```python async def main(): """Main function to demonstrate the smart contract interaction.""" # Load environment variables load_dotenv() private_key = os.getenv("PRIVATE_KEY") rpc_url = os.getenv("RPC_URL") faucet_url = os.getenv("FAUCET_URL") module_address = os.getenv("MODULE_ADDRESS", "") # Add this to your .env file if not all([private_key, rpc_url, faucet_url]): print("Please set PRIVATE_KEY, RPC_URL, and FAUCET_URL in your .env file") return # Initialize the client client = MovementSmartContractClient( private_key=private_key, rpc_url=rpc_url, faucet_url=faucet_url, module_address=module_address ) # Fund the account await client.fund_account() # Get balance await client.get_balance() # Wait a bit for funding to complete await asyncio.sleep(5) # Check current balance balance = await client.get_balance() print(f"Current balance: {balance} Octas") # If module address is set, demonstrate smart contract interaction if module_address: # Try to read existing message await client.get_message() # Set a new message message = "Gmove to those that still Gmove" txn_hash = await client.set_message(message) if txn_hash: # Read the message after setting it await client.get_message() else: print("\nTo interact with smart contracts, please set MODULE_ADDRESS in your .env file") if __name__ == "__main__": asyncio.run(main()) ``` ### Expected Output When you run the example, you should see output similar to: ```bash Account address: 0xACCOUNT_ADDRESS Successfully funded account with 100000000 Octas Current balance: 100000000 Octas Message resource: b'["gmove"]' === Submitting Transaction === Submitted transaction: 0xTRANSACTION_HASH Transaction confirmed: 0xTRANSACTION_HASH Message resource: b'["Gmove to those that still Gmove"]' ``` ### Full Example Code ```python import asyncio import os from typing import Optional, Any, Dict from aptos_sdk.ed25519 import PrivateKey from aptos_sdk.async_client import FaucetClient, RestClient from aptos_sdk.account import Account, AccountAddress from aptos_sdk.transactions import ( EntryFunction, TransactionPayload, TransactionArgument ) from aptos_sdk.bcs import Serializer from dotenv import load_dotenv class MovementSmartContractClient: """A client for interacting with Movement smart contracts, specifically the message contract.""" def __init__(self, private_key: str, rpc_url: str, faucet_url: str, module_address: str = ""): """ Initialize the Movement client with network configuration. Args: private_key: The private key for the account (with or without 0x prefix) rpc_url: The RPC URL for the Movement network faucet_url: The faucet URL for funding accounts module_address: The address of the deployed smart contract module """ # Initialize clients self.rest_client = RestClient(rpc_url) self.faucet_client = FaucetClient(faucet_url, self.rest_client) # Setup account if private_key.startswith("0x"): private_key = private_key[2:] self.private_key = PrivateKey.from_hex(private_key) self.account_address = AccountAddress.from_key(self.private_key.public_key()) self.account = Account(account_address=self.account_address, private_key=self.private_key) # Contract configuration self.module_address = module_address self.set_message_function = f"message::set_message" self.get_message_function = f"message::get_message" print(f"Account address: {self.account.address()}") async def fund_account(self, amount: int = 100000000) -> None: """ Fund the account using the faucet. Args: amount: Amount to fund in Octas (default: 1 APT = 100000000 Octas) """ try: await self.faucet_client.fund_account(self.account.address(), amount) print(f"Successfully funded account with {amount} Octas") except Exception as e: print(f"Error funding account: {e}") async def get_balance(self) -> int: """ Get the current account balance. Returns: Account balance in Octas """ try: balance = await self.rest_client.account_balance(self.account.address()) return balance except Exception as e: print(f"Error getting balance: {e}") return 0 async def get_message(self, account_address: Optional[str] = None) -> Optional[Dict[str, Any]]: """ Retrieve the resource message::MessageHolder::message Args: account_address: The account address to read message for (defaults to current account) Returns: The message holder resource or None if not found """ if not self.module_address: raise ValueError("Module address not set. Please provide module_address in constructor.") target_address = AccountAddress.from_str(account_address) if account_address else self.account.address() try: # Get the get_message resource from the account resource = await self.rest_client.view( f"{self.module_address}::{self.get_message_function}", [], [str(target_address)] ) print(f"Message resource: {resource}") return resource except Exception as e: print(f"Error reading message: {e}") return None async def set_message(self, message: str) -> Optional[str]: """ Potentially initialize and set the resource message::MessageHolder::message Args: message: The message to store Returns: Transaction hash if successful, None otherwise """ if not self.module_address: raise ValueError("Module address not set. Please provide module_address in constructor.") try: print("\n=== Submitting Transaction ===") # Create the transaction payload using the correct format payload = EntryFunction.natural( f"{self.module_address}::{self.set_message_function}", "set_message", [], [TransactionArgument(message, Serializer.str)] ) # Create and submit the BCS signed transaction signed_transaction = await self.rest_client.create_bcs_signed_transaction( self.account, TransactionPayload(payload) ) # Submit the transaction txn_hash = await self.rest_client.submit_bcs_transaction(signed_transaction) print(f"Submitted transaction: {txn_hash}") # Wait for transaction confirmation await self.rest_client.wait_for_transaction(txn_hash) print(f"Transaction confirmed: {txn_hash}") return txn_hash except Exception as e: print(f"Error setting message: {e}") return None async def get_account_info(self) -> Any: """ Get detailed account information. Returns: Account information from the blockchain """ try: account_info = await self.rest_client.account(self.account.address()) print(f"Account info: {account_info}") return account_info except Exception as e: print(f"Error getting account info: {e}") return None async def main(): """Main function to demonstrate the smart contract interaction.""" # Load environment variables load_dotenv() private_key = os.getenv("PRIVATE_KEY") rpc_url = os.getenv("RPC_URL") faucet_url = os.getenv("FAUCET_URL") module_address = os.getenv("MODULE_ADDRESS", "") # Add this to your .env file if not all([private_key, rpc_url, faucet_url]): print("Please set PRIVATE_KEY, RPC_URL, and FAUCET_URL in your .env file") return # Initialize the client client = MovementSmartContractClient( private_key=private_key, rpc_url=rpc_url, faucet_url=faucet_url, module_address=module_address ) # Fund the account await client.fund_account() # Get balance await client.get_balance() # Wait a bit for funding to complete await asyncio.sleep(5) # Get account info await client.get_account_info() # If module address is set, demonstrate smart contract interaction if module_address: # Try to read existing message await client.get_message() # Set a new message message = "Gmove to those that still Gmove" txn_hash = await client.set_message(message) if txn_hash: # Read the message after setting it await client.get_message() else: print("\nTo interact with smart contracts, please set MODULE_ADDRESS in your .env file") if __name__ == "__main__": asyncio.run(main()) ``` ## Next Steps Now that you understand the basics of using the Python SDK with Movement: * **Explore More Functions**: The SDK supports many more operations like multi-signature transactions, token transfers, and complex smart contract interactions * **Error Handling**: Implement robust error handling for production applications * **Testing**: Write comprehensive tests for your smart contract interactions * **Security**: Never hardcode private keys in production code - use secure key management solutions ## Additional Resources * [Movement Network Documentation](/) * [Your First Move Contract Tutorial](/devs/firstMoveContract) This completes the Python SDK tutorial for interacting with the Movement network. The SDK provides a powerful and flexible way to build Python applications that interact with Movement smart contracts. # Rust SDK URL: /devs/interactonchain/rustSDK Aptos provides an official lightly supported Rust SDK in the Aptos-core GitHub 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"] ``` ## Usage Full example availables [here](https://github.com/aptos-labs/aptos-core/blob/main/sdk/examples/transfer-coin.rs). Network endpoints should be replaced with the appropriate [Movement endpoints](/devs/networkEndpoints). # TypeScript SDK URL: /devs/interactonchain/tsSdk # Movement TypeScript SDK This Movement TS SDK repo is forked from [github.com/aptos-labs](https://github.com/aptos-labs) prior to the date on which the Aptos Foundation implemented its Innovation-Enabling Source Code License in substitution for the prior Apache License, Version 2.0 governing this repository. Move Industries continues to maintain, develop, modify, and distribute this repository solely under the Apache License, Version 2.0, as existed at the time of the fork and without application of the license instituted by the Aptos Foundation. The [Movement TypeScript SDK](https://github.com/MoveIndustries/ts-sdk) allows you to interact with the Movement Network. The guide below will walk you through the process of setting up the SDK and interacting with the chain. ## Installation ```bash tab="npm" npm install @moveindustries/ts-sdk ``` ```bash tab="pnpm" pnpm add @moveindustries/ts-sdk ``` ```bash tab="yarn" yarn add @moveindustries/ts-sdk ``` ```bash tab="bun" bun add @moveindustries/ts-sdk ``` ## Imports To get started with the SDK, import the necessary components: ```javascript const { Account, Movement, MovementConfig, Network, Ed25519PrivateKey } = require("@moveindustries/ts-sdk"); ``` ## Configuration Configure the SDK to connect to the Movement Bardock Testnet (Please refer to [Network Endpoints](/devs/networkEndpoints) for more endpoints): ```javascript const config = new MovementConfig({ network: Network.CUSTOM, fullnode: 'https://testnet.movementnetwork.xyz/v1', faucet: 'https://faucet.testnet.movementnetwork.xyz/', }); // Initialize the Movement client const movement = new Movement(config); ``` ## Interacting with the Chain ### Account Setup ```javascript // Create an account from a private key const privateKey = new Ed25519PrivateKey("YOUR_PRIVATE_KEY") const account = Account.fromPrivateKey({ privateKey }) ``` ### Reading Data ```javascript // Example of reading data using view function const viewPayload = { function: "0x1::message::get_message", functionArguments: [accountAddress] }; const result = await movement.view({ payload: viewPayload }); ``` ### Sending Transactions ```javascript // Build the transaction const transaction = await movement.transaction.build.simple({ sender: accountAddress, data: { function: "0x1::message::set_message", functionArguments: ["Hello Movement!"] }, }); // Sign the transaction const signature = movement.transaction.sign({ signer: account, transaction }); // Submit the transaction const committedTxn = await movement.transaction.submit.simple({ transaction, senderAuthenticator: signature, }); // Wait for transaction completion const response = await movement.waitForTransaction({ transactionHash: committedTxn.hash }); ``` Remember to replace `YOUR_PRIVATE_KEY` with your actual private key and never share or commit private keys to version control. ## Example Code The Code below is based on the [Your First Move Contract tutorial](/devs/firstMoveContract). ```javascript const { Account, Movement, MovementConfig, Network, Ed25519PrivateKey } = require("@moveindustries/ts-sdk"); // Define the custom network configuration const config = new MovementConfig({ network: Network.CUSTOM, fullnode: 'https://testnet.movementnetwork.xyz/v1', faucet: 'https://faucet.testnet.movementnetwork.xyz/', }); // Define the module address and functions const MODULE_ADDRESS = ""; const SET_MESSAGE_FUNCTION = `${MODULE_ADDRESS}::message::set_message`; const GET_MESSAGE_FUNCTION = `${MODULE_ADDRESS}::message::get_message`; const PRIVATE_KEY = "YOUR_PRIVATE_KEY"; // Replace with your private key const MESSAGE = "gmove"; const setMessage = async () => { // Setup the client const movement = new Movement(config); // Create an account from the provided private key console.log("creating") const privateKey = new Ed25519PrivateKey(PRIVATE_KEY) const account = Account.fromPrivateKey({ privateKey }) const accountAddress = account.accountAddress console.log(`address: ${account.accountAddress}`) console.log(`Using account: ${accountAddress}`); // Build the transaction payload const payload = { function: SET_MESSAGE_FUNCTION, type_arguments: [], arguments: [MESSAGE], }; console.log("\n=== Reading Message ===\n"); const viewPayload = { function: GET_MESSAGE_FUNCTION, functionArguments: [accountAddress] } try { const message = await movement.view({ payload: viewPayload }); console.log("Message:", message); } catch (error) { console.error("Error reading message:", error); } // Submit the transaction console.log("\n=== Submitting Transaction ===\n"); const transaction = await movement.transaction.build.simple({ sender: accountAddress, data: { function: SET_MESSAGE_FUNCTION, functionArguments: [MESSAGE] }, }); // Sign the transaction const signature = movement.transaction.sign({ signer: account, transaction }); // Submit the transaction to chain const committedTxn = await movement.transaction.submit.simple({ transaction, senderAuthenticator: signature, }); console.log(`Submitted transaction: ${committedTxn.hash}`); const response = await movement.waitForTransaction({ transactionHash: committedTxn.hash }); console.log({ response }) // Read the message after it has been set console.log("\n=== Reading Message ===\n"); const newMessage = await movement.view({ payload: viewPayload }); console.log("Message:", newMessage); }; setMessage().catch((err) => { console.error("Error setting message:", err); }); ``` # Address URL: /devs/move-book/address # Address Type The `address` type represents unique identifiers for accounts and modules in Move. Think of addresses as unique locations where code and data can be stored. ## What is an Address? An address is a 256-bit identifier that serves as a unique location in the blockchain: * **Accounts**: Store resources and data * **Modules**: Store code and functions * **Packages**: Collections of modules at the same address ## Address Literals Addresses are written with the `@` symbol followed by a hexadecimal number: ```move let user_address = @0x1; let contract_address = @0x42; let long_address = @0xDEADBEEF; ``` ### Short and Long Form Move accepts both short and long address formats: ```move // Short form (Move pads with zeros) let addr1 = @0x1; // Same as @0x0000...0001 let addr2 = @0x42; // Same as @0x0000...0042 // Long form (explicit) let addr3 = @0x0000000000000000000000000000000000000000000000000000000000000001; ``` ## Named Addresses Instead of using hex numbers, you can use named addresses for better readability: ```move // In Move.toml or package configuration // std = "0x1" // my_package = "0x42" let std_addr = @std; // Refers to 0x1 let my_addr = @my_package; // Refers to 0x42 ``` ## Address Usage ### In Expressions When using addresses as values, always use the `@` prefix: ```move fun get_user_address(): address { @0x123 } let user = @0x456; ``` ### In Module Declarations When declaring modules, omit the `@` prefix: ```move module 0x42::my_module { // Module code here } // Or with named addresses module my_package::my_module { // Module code here } ``` ## Address Properties Addresses in Move are **opaque**, meaning: * You cannot create them from integers * You cannot perform arithmetic on them * You cannot modify them directly * They can only be compared for equality ```move let addr1 = @0x1; let addr2 = @0x2; // Valid operations let same = addr1 == addr1; // true let different = addr1 != addr2; // true // Invalid operations (won't compile) // let sum = addr1 + addr2; // ERROR! // let addr3 = addr1 * 2; // ERROR! ``` ## Practical Examples Here are common ways to use addresses: ```move // Check if an address owns a resource fun has_account(addr: address): bool { // Implementation would check global storage true // Simplified } // Compare addresses fun is_admin(user: address): bool { user == @0x1 // Check if user is admin address } // Store addresses in data structures struct UserInfo has key { owner: address, balance: u64, } ``` ## Global Storage Operations The primary purpose of `address` values is to interact with global storage. They are used with the following operations: * `exists` * `borrow_global` * `borrow_global_mut` * `move_from` Note that the `move_to` operation does not use an `address` but instead requires a `signer`. ## Ownership As with the other scalar values built-in to the language, address values are implicitly copyable, meaning they can be copied without an explicit instruction such as `copy`. ## Summary Addresses in Move: * **Identify locations** for accounts and modules * **Use `@` prefix** when used as values * **Support named aliases** for better readability * **Are opaque** - no arithmetic operations allowed * **Are copyable** - no explicit `copy` needed * **Enable access** to global storage and resources Addresses are fundamental to Move's security model, ensuring that resources and modules have clear ownership and access patterns. # Assertion and Abortion URL: /devs/move-book/assertionAndAbortion # Assertion and Abortion `return` and `abort` are two control flow constructs that end execution: one for the current function and one for the entire transaction. While `return` exits the current function, `abort` halts execution and reverts all changes made to global state by the current transaction. ## Abort `abort` is an expression that takes one argument: an abort code of type `u64`. For example: ```move abort 42 ``` The `abort` expression halts execution of the current function and reverts all changes made to global state by the current transaction. There is no mechanism for "catching" or otherwise handling an abort. ### Transaction Semantics In Move, transactions are all-or-nothing, meaning any changes to global storage are made all at once only if the transaction succeeds. Because of this transactional commitment of changes, after an abort there is no need to worry about backing out changes. While this approach is lacking in flexibility, it is incredibly simple and predictable. Similar to `return`, `abort` is useful for exiting control flow when some condition cannot be met. ### Basic Usage Example In this example, the function will withdraw funds from an account, but will abort early if there are insufficient funds: ```move fun withdraw_funds(balance: &mut u64, amount: u64): u64 { if (*balance < amount) abort 42; *balance = *balance - amount; amount } #[test] public fun test_withdraw_funds() { let balance = 100u64; let safe_withdrawn = withdraw_funds(&mut balance, 50u64); // This will not abort let withdrawn = withdraw_funds(&mut balance, 225u64); // This will abort } ``` ### Complex Control Flow Example This is even more useful deep inside a control-flow construct. For example, this function validates that all ages in a list are within legal limits and aborts otherwise: ```move use std::vector; fun validate_ages(ages: &vector, max_age: u8) { let i = 0; let n = vector::length(ages); while (i < n) { let age = *vector::borrow(ages, i); if (age > max_age) abort 42; i = i + 1; } } ``` ## Assert `assert` is a builtin, macro-like operation provided by the Move compiler. It takes two arguments: a condition of type `bool` and a code of type `u64`: ```move assert!(condition: bool, code: u64) ``` Since the operation is a macro, it must be invoked with the `!`. This is to convey that the arguments to `assert` are call-by-expression. In other words, `assert` is not a normal function and does not exist at the bytecode level. It is replaced inside the compiler with: ```move if (condition) () else abort code ``` ### Assert vs Abort `assert` is more commonly used than just `abort` by itself. The abort examples above can be rewritten using `assert`: ```move fun withdraw_funds(balance: &mut u64, amount: u64): u64 { assert!(*balance >= amount, 42); // Now uses 'assert' *balance = *balance - amount; amount } ``` And: ```move use std::vector; fun validate_ages(ages: &vector, max_age: u8) { let i = 0; let n = vector::length(ages); while (i < n) { let age = *vector::borrow(ages, i); assert!(age <= max_age, 42); // Now uses 'assert' i = i + 1; } } ``` ### Lazy Evaluation Note that because the operation is replaced with an if-else, the argument for the code is not always evaluated. For example: ```move assert!(true, 1 / 0) ``` Will not result in an arithmetic error, it is equivalent to: ```move if (true) () else (1 / 0) ``` So the arithmetic expression is never evaluated! ## Abort Codes in the Move VM When using `abort`, it is important to understand how the `u64` code will be used by the VM. Normally, after successful execution, the Move VM produces a change-set for the changes made to global storage (added/removed resources, updates to existing resources, etc). If an abort is reached, the VM will instead indicate an error. Included in that error will be two pieces of information: * The module that produced the abort (address and name) * The abort code ### Example Error Information ```move module 0x2::bank { public fun transfer_funds() { abort 42 } } ``` ```move script { fun failed_transfer() { 0x2::bank::transfer_funds() } } ``` If a transaction, such as the script `failed_transfer` above, calls `0x2::bank::transfer_funds`, the VM would produce an error that indicated the module `0x2::bank` and the code `42`. ## Using Constants for Error Codes This can be useful for having multiple aborts being grouped together inside a module. It's a best practice to use constants to define error codes: ```move module 0x42::account { const INSUFFICIENT_BALANCE: u64 = 0; const INVALID_AMOUNT: u64 = 1; const ACCOUNT_FROZEN: u64 = 2; struct Account has key { balance: u64, is_frozen: bool, } public fun transfer(from: &mut Account, to: &mut Account, amount: u64) { assert!(!from.is_frozen, ACCOUNT_FROZEN); assert!(!to.is_frozen, ACCOUNT_FROZEN); assert!(amount > 0, INVALID_AMOUNT); assert!(from.balance >= amount, INSUFFICIENT_BALANCE); from.balance = from.balance - amount; to.balance = to.balance + amount; } public fun withdraw(account: &mut Account, amount: u64): u64 { assert!(!account.is_frozen, ACCOUNT_FROZEN); assert!(amount > 0, INVALID_AMOUNT); assert!(account.balance >= amount, INSUFFICIENT_BALANCE); account.balance = account.balance - amount; amount } } ``` ### Benefits of Using Constants Using constants for error codes provides several advantages: * **Readability**: Code is more self-documenting * **Maintainability**: Easy to update error codes in one place * **Consistency**: Prevents duplicate or conflicting error codes * **Documentation**: Constants can be documented with comments ## Type Flexibility The `abort` expression can have any type since it breaks normal control flow and never needs to evaluate to an actual value: ```move let account: address = abort 0; // This will abort with error code 0 // Useful in branching scenarios let account_status = if (balance >= 1000) b"premium" else if (balance > 0) b"active" else abort 42; // Has type `vector` ``` ## Best Practices * Use **named constants** for error codes * Prefer `assert!` over manual `if-abort` patterns * Keep error codes **unique** within modules * **Document** error conditions clearly ## Summary Move's `abort` and `assert!` provide robust error handling for transaction safety: * **`abort`** immediately halts execution and reverts all transaction changes * **`assert!`** offers cleaner syntax for conditional aborts * **Error codes** help identify specific failure points in the VM * **Constants** make error codes maintainable and self-documenting * All aborts are **transaction-level** - there's no partial failure recovery Use these constructs to enforce invariants and handle exceptional conditions while maintaining Move's transactional guarantees. # Boolean URL: /devs/move-book/bool # Boolean Type The `bool` type represents boolean values in Move - either `true` or `false`. Booleans are essential for making decisions and controlling program flow. ## Boolean Literals There are only two boolean values in Move: ```move let is_active = true; let is_complete = false; ``` The compiler can always infer the `bool` type, so explicit type annotations are optional: ```move let flag: bool = true; // Explicit type let flag = true; // Type inferred ``` ## Logical Operations Move supports three logical operations for booleans: ### AND (`&&`) Returns `true` only if both operands are `true`: ```move let result = true && true; // true let result = true && false; // false let result = false && true; // false ``` ### OR (`||`) Returns `true` if at least one operand is `true`: ```move let result = true || false; // true let result = false || true; // true let result = false || false; // false ``` ### NOT (`!`) Inverts the boolean value: ```move let result = !true; // false let result = !false; // true ``` ## Short-Circuit Evaluation Logical operators use short-circuit evaluation: * `&&` stops evaluating if the first operand is `false` * `||` stops evaluating if the first operand is `true` ```move let x = 5; let result = (x > 10) && (x < 20); // Second condition not checked ``` ## Comparison Operations Booleans are often created from comparison operations: ```move let age = 25; let is_adult = age >= 18; // true let is_senior = age >= 65; // false let is_young_adult = age >= 18 && age <= 30; // true ``` ## Control Flow Usage Booleans are primarily used in control flow statements: ```move let score = 85; let passed = score >= 60; if (passed) { // Execute if true } else { // Execute if false } ``` ## Practical Examples Here are some common boolean usage patterns: ```move fun check_eligibility(age: u8, has_license: bool): bool { age >= 18 && has_license } fun is_even(number: u64): bool { number % 2 == 0 } fun is_in_range(value: u64, min: u64, max: u64): bool { value >= min && value <= max } ``` ## Ownership As with the other scalar values built-in to the language, boolean values are implicitly copyable, meaning they can be copied without an explicit instruction such as `copy`. ## Summary Booleans are simple but powerful: * Only two values: `true` and `false` * Support logical operations: `&&`, `||`, `!` * Use short-circuit evaluation for efficiency * Essential for control flow and decision making * Automatically copyable (no explicit `copy` needed) # Coding Conventions URL: /devs/move-book/codingCovention # Coding Conventions Move coding conventions promote consistency, readability, and maintainability across projects. These guidelines help teams collaborate effectively and make codebases easier to understand and maintain. **Key principles:** * **Consistency**: Uniform naming and formatting across all code * **Clarity**: Names should clearly express intent and purpose * **Maintainability**: Code should be easy to read and modify * **Community alignment**: Follow established Move ecosystem patterns While these are recommendations rather than strict requirements, following them helps create more professional and maintainable Move code that integrates well with the broader ecosystem. ## Naming Conventions ### Modules Use **lower\_snake\_case** for module names: ```move module 0x42::fixed_point32 { // Module implementation } module 0x42::token_registry { // Module implementation } ``` **Guidelines:** * Use descriptive names that indicate the module's purpose * Prefer full words over abbreviations * Keep names concise but clear ### Types and Structs Use **PascalCase** for custom types and structs: ```move module 0x42::defi { struct LiquidityPool has key { token_a: u64, token_b: u64, } struct UserAccount has key { balance: u64, role_id: RoleId, } struct RoleId has copy, drop { value: u8 } } ``` **Guidelines:** * Start with uppercase letter * Use descriptive names that indicate the data's purpose * Native types (u64, bool, address) remain lowercase ### Functions Use **lower\_snake\_case** for function names: ```move module 0x42::utils { public fun create_account(owner: &signer): Account { // Function implementation } public fun destroy_empty_container(container: Container) { // Function implementation } fun calculate_interest_rate(principal: u64, time: u64): u64 { // Function implementation } } ``` **Guidelines:** * Use verbs that describe what the function does * Be specific about the action performed * Avoid abbreviations unless they're widely understood ### Constants Use different conventions based on the constant's purpose: ```move module 0x42::errors { // Error codes: E + PascalCase const EInsufficientBalance: u64 = 1; const EInvalidPermission: u64 = 2; const EResourceNotFound: u64 = 3; // Non-error values: UPPER_SNAKE_CASE const MAX_SUPPLY: u64 = 1_000_000; const MIN_STAKE_AMOUNT: u64 = 100; const DEFAULT_FEE_RATE: u64 = 25; // 0.25% } ``` **Guidelines:** * **Error codes**: Start with `E` followed by PascalCase description * **Configuration values**: Use UPPER\_SNAKE\_CASE * Include comments for non-obvious values ### Generic Type Parameters Use descriptive names that indicate the parameter's role: ```move module 0x42::containers { struct Vault has key { contents: Asset } struct Pair has store { first: TokenA, second: TokenB, } // Single generic can use T when context is clear struct Box has copy, drop { value: T } } ``` **Guidelines:** * Use descriptive names for domain-specific generics (`Asset`, `Currency`) * Use `T` only when the type is truly generic * For multiple generics, use meaningful names (`TokenA`, `TokenB`) ## File Naming ### Module Files File names should match the module name exactly: ``` // File: fixed_point32.move module 0x42::fixed_point32 { ... } // File: token_registry.move module 0x42::token_registry { ... } ``` ### Script Files Use **lower\_snake\_case** matching the main function: ``` // File: create_account.move script { fun create_account(account: signer) { ... } } // File: transfer_tokens.move script { fun transfer_tokens(from: signer, to: address, amount: u64) { ... } } ``` ### Mixed Files For files containing multiple modules or scripts, use descriptive **lower\_snake\_case**: ``` // File: defi_utils.move - contains multiple related modules module 0x42::liquidity_pool { ... } module 0x42::swap_router { ... } module 0x42::price_oracle { ... } ``` ## Code Organization ### Module Structure Organize module contents in a consistent order: ```move module 0x42::token { // 1. Imports use std::signer; use std::vector; // 2. Friend declarations (if any) friend 0x42::token_registry; // 3. Constants const MAX_SUPPLY: u64 = 1_000_000; const EInsufficientBalance: u64 = 1; // 4. Structs struct Token has key { supply: u64, decimals: u8, } // 5. Functions (public first, then private) public fun create_token(creator: &signer): Token { ... } fun validate_amount(amount: u64): bool { ... } } ``` ## Best Practices * Choose names that express intent clearly * Use consistent terminology throughout the project * Group related functionality together * Keep modules focused on a single responsibility ## Summary Move coding conventions promote consistency and readability through: * **Naming patterns**: lower\_snake\_case for modules and functions, PascalCase for types, UPPER\_SNAKE\_CASE for constants * **Generic naming**: Use descriptive names for generics * **File organization**: File names should match module names * **Code structure**: Organize code with clear structure * **Benefits**: Creates professional, maintainable code that integrates well with the Move ecosystem # Conditional URL: /devs/move-book/conditional # Conditional Conditionals are fundamental control flow constructs that allow programs to make decisions based on runtime values. An `if` expression specifies that some code should only be evaluated if a certain condition is true. ## Basic If Expression The simplest form of a conditional expression: ```move if (temperature > 30) is_hot = true ``` The condition must be an expression of type `bool`. If the condition evaluates to `true`, the expression following the condition is executed. ## If-Else Expression An `if` expression can optionally include an `else` clause to specify another expression to evaluate when the condition is false: ```move if (speed <= 60) speed = speed + 5 else speed = 60 ``` Either the "true" branch or the "false" branch will be evaluated, but not both. Either branch can be a single expression or an expression block. ## Conditional Expressions with Values The conditional expressions may produce values so that the `if` expression has a result: ```move let price = if (quantity < 10) quantity * 5 else quantity * 4; ``` This allows conditionals to be used in assignments and other expressions where a value is expected. ## Type Compatibility The expressions in the true and false branches must have compatible types: ```move // width and height must be u64 integers let larger_dimension: u64 = if (width > height) width else height; ``` ### Type Errors Branches with incompatible types will result in compilation errors: ```move // ERROR! branches different types let result = if (is_valid < 10) 10u8 else 100u64; // ERROR! branches different types, as default false-branch is () not u64 if (score >= 10) score; ``` ## Default Else Clause If the `else` clause is not specified, the false branch defaults to the unit value `()`. The following are equivalent: ```move if (is_ready) start_process // implied default: else () if (is_ready) start_process else () ``` This means that when no `else` clause is provided, the `if` expression returns the unit type `()` when the condition is false. ## Expression Blocks Commonly, `if` expressions are used in conjunction with expression blocks for more complex logic: ```move let total_cost = if (items > 5) items * 8 else items * 10; if (total_cost < 50) { shipping_fee = 5; tax_rate = 8; } else if (total_cost >= 50 && total_cost < 100) { shipping_fee = 0; tax_rate = 10; } ``` ## Nested Conditionals You can chain multiple conditions using `else if`: ```move let shipping_method = if (weight <= 1) { b"standard" } else if (weight <= 5) { b"express" } else if (weight <= 20) { b"freight" } else { b"special_handling" }; ``` ## Practical Examples ### Temperature Control ```move fun adjust_temperature(current: u64, target: u64): u64 { if (current < target) { current + 2 } else if (current > target) { current - 2 } else { current } } ``` ## Complex Conditions Conditionals can use complex boolean expressions with logical operators: ```move fun can_access_system(role: u8, is_active: bool, has_permission: bool): bool { if (role >= 2 && is_active && has_permission) { true } else { false } } ``` ## Performance Considerations Due to short-circuit evaluation in logical operators, structure conditions efficiently: ```move // Efficient: check simple condition first if (is_online && complex_validation()) { // process request } // Less efficient: complex check might run unnecessarily if (complex_validation() && is_online) { // process request } ``` ## Grammar The formal grammar for conditional expressions: ``` if-expression → if ( expression ) expression else-clause? else-clause → else expression ``` ## Best Practices * **Use meaningful conditions** - Make boolean expressions clear and readable * **Consistent return types** - Ensure both branches return compatible types * **Avoid deep nesting** - Use `else if` chains instead of deeply nested conditionals * **Structure conditions efficiently** - Put simple checks before complex ones ## Summary Move's conditional expressions provide: * **Decision making** based on boolean conditions * **Value production** through if-else expressions * **Type safety** with compile-time branch compatibility checks * **Flexible syntax** supporting both simple and complex logic Conditionals are essential for implementing business logic, validation, and control flow in Move programs. # Constants URL: /devs/move-book/constants # Constants Constants in Move are immutable named values that are defined at the module or script level. They provide a way to give meaningful names to static values that are used throughout your code, improving readability and maintainability. Constants are evaluated at compile time and stored in the module's bytecode, with their values being copied each time they are used. ## Declaration Syntax Constants are declared using the `const` keyword followed by a name, type annotation, and value: ```move const : = ; ``` ### Naming Convention Constants must follow specific naming rules: * Must be in **SCREAMING\_SNAKE\_CASE** (all uppercase with underscores) * Must start with a letter `A-Z` * Can contain letters, digits, and underscores after the first character ```move // Valid constant names const MAX_SUPPLY: u64 = 1000000; const DEFAULT_FEE_RATE: u8 = 5; const ADMIN_ADDRESS: address = @0x1; const ERROR_INSUFFICIENT_BALANCE: u64 = 1001; // Invalid constant names // const maxSupply: u64 = 1000000; // Error: not SCREAMING_SNAKE_CASE // const 1ST_CONSTANT: u64 = 100; // Error: starts with digit ``` ## Supported Types Constants are limited to primitive types and `vector` (byte strings): ```move // Primitive types const IS_ENABLED: bool = true; const SAMPLE_U8: u8 = 200; const SAMPLE_U16: u16 = 1000; const SAMPLE_U32: u32 = 1000000; const SAMPLE_U64: u64 = 1000000; const SAMPLE_U128: u128 = 1000000; const SAMPLE_U256: u256 = 1000000; // Address type const TREASURY_ADDRESS: address = @0xCAFE; const ZERO_ADDRESS: address = @0x0; // Vector (byte strings) const APP_NAME: vector = b"MyDApp"; const VERSION: vector = b"1.0.0"; const EMPTY_BYTES: vector = b""; ``` ## Visibility and Scope Constants have **module-level visibility** only - they cannot be made public and are only accessible within the module where they are declared: ```move module 0x1::my_module { const PRIVATE_CONSTANT: u64 = 100; public fun get_constant(): u64 { PRIVATE_CONSTANT // Can access within same module } } module 0x1::other_module { use 0x1::my_module; public fun test() { // let x = PRIVATE_CONSTANT; // Error: not accessible let x = my_module::get_constant(); // Must use public function } } ``` ## Constant Expressions Constants can be initialized with simple expressions involving literals and other constants: ```move const BASE_RATE: u64 = 100; const MULTIPLIER: u64 = 2; const CALCULATED_RATE: u64 = BASE_RATE * MULTIPLIER; // 200 const IS_TESTNET: bool = true; const IS_MAINNET: bool = !IS_TESTNET; // false // Mathematical expressions const SECONDS_PER_MINUTE: u64 = 60; const MINUTES_PER_HOUR: u64 = 60; const SECONDS_PER_HOUR: u64 = SECONDS_PER_MINUTE * MINUTES_PER_HOUR; // 3600 // Address expressions const BASE_ADDRESS: address = @0x1; ``` ## Practical Usage Patterns ### Error Codes Constants are commonly used for error codes to make error handling more readable: ```move module 0x1::token { const ERROR_INSUFFICIENT_BALANCE: u64 = 1001; const ERROR_INVALID_RECIPIENT: u64 = 1002; const ERROR_TRANSFER_TO_SELF: u64 = 1003; const ERROR_AMOUNT_TOO_LARGE: u64 = 1004; public fun transfer(from: address, to: address, amount: u64) { assert!(from != to, ERROR_TRANSFER_TO_SELF); assert!(amount > 0, ERROR_INVALID_AMOUNT); // Transfer logic... } } ``` ### Configuration Values Use constants for configuration that shouldn't change during execution: ```move module 0x1::defi_protocol { const MAX_LOAN_TO_VALUE_RATIO: u8 = 80; // 80% const LIQUIDATION_THRESHOLD: u8 = 85; // 85% const MINIMUM_COLLATERAL: u64 = 1000; // Minimum collateral amount const FEE_DENOMINATOR: u64 = 10000; // For percentage calculations public fun calculate_fee(amount: u64, fee_rate: u64): u64 { (amount * fee_rate) / FEE_DENOMINATOR } } ``` ### Resource Limits Define limits and constraints as constants: ```move module 0x1::nft_collection { const MAX_SUPPLY: u64 = 10000; const MAX_MINT_PER_TRANSACTION: u8 = 10; const MINT_PRICE: u64 = 1000000; // 0.01 MOVE in octas struct Collection has key { total_minted: u64, } public fun mint_nft(account: &signer, quantity: u8) { assert!(quantity <= MAX_MINT_PER_TRANSACTION, ERROR_EXCEEDS_MINT_LIMIT); // Minting logic... } } ``` ### String Constants Use byte vector constants for string-like data: ```move module 0x1::metadata { const COLLECTION_NAME: vector = b"Awesome NFT Collection"; const COLLECTION_DESCRIPTION: vector = b"A collection of unique digital assets"; const BASE_URI: vector = b"https://api.example.com/metadata/"; public fun get_collection_name(): vector { COLLECTION_NAME } } ``` ## Advanced Patterns ### Bitflags and Permissions Use constants to define permission flags: ```move module 0x1::permissions { const PERMISSION_READ: u8 = 1; // 0001 const PERMISSION_WRITE: u8 = 2; // 0010 const PERMISSION_EXECUTE: u8 = 4; // 0100 const PERMISSION_ADMIN: u8 = 8; // 1000 const PERMISSION_ALL: u8 = PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXECUTE | PERMISSION_ADMIN; public fun has_permission(user_permissions: u8, required: u8): bool { (user_permissions & required) == required } } ``` ### Time Constants Define time-related constants for clarity: ```move module 0x1::timelock { const SECONDS_PER_DAY: u64 = 86400; const LOCK_DURATION_DAYS: u64 = 7; const LOCK_DURATION_SECONDS: u64 = LOCK_DURATION_DAYS * SECONDS_PER_DAY; public fun create_timelock(unlock_time: u64): u64 { unlock_time + LOCK_DURATION_SECONDS } } ``` ## Best Practices ### 1. Use Descriptive Names ```move // Good const MAX_TRANSACTION_SIZE: u64 = 1024; const DEFAULT_SLIPPAGE_TOLERANCE: u8 = 50; // 0.5% // Avoid const MAX_SIZE: u64 = 1024; const TOLERANCE: u8 = 50; ``` ### 2. Group Related Constants ```move module 0x1::trading { // Fee constants const TRADING_FEE_RATE: u64 = 30; // 0.3% const WITHDRAWAL_FEE_RATE: u64 = 10; // 0.1% const FEE_DENOMINATOR: u64 = 10000; // Limit constants const MIN_TRADE_AMOUNT: u64 = 1000; const MAX_TRADE_AMOUNT: u64 = 1000000000; // Time constants const COOLDOWN_PERIOD: u64 = 3600; // 1 hour } ``` ### 3. Document Complex Constants ```move module 0x1::math { // Represents 1.0 in fixed-point arithmetic with 8 decimal places const FIXED_POINT_SCALING: u64 = 100000000; // Maximum safe value to prevent overflow in multiplication const MAX_SAFE_MULTIPLIER: u64 = 18446744073709551615 / FIXED_POINT_SCALING; } ``` ## Ownership Constants have **copy semantics** - their values are copied each time they are used. This means: * No ownership transfer occurs when using constants * Constants can be used multiple times without restriction * The original constant value remains unchanged ```move const SHARED_VALUE: u64 = 100; public fun example() { let x = SHARED_VALUE; // Value is copied let y = SHARED_VALUE; // Value is copied again // Both x and y have independent copies of 100 } ``` ## Summary Constants in Move provide: * **Immutable named values** that improve code readability * **Compile-time evaluation** for better performance * **Module-level scope** for encapsulation * **Type safety** with explicit type annotations * **Copy semantics** for safe reuse Use constants for: * Error codes and status values * Configuration parameters * Mathematical constants * Resource limits and constraints * String-like data (as `vector`) Constants are essential for writing maintainable Move code by replacing magic numbers and strings with meaningful, self-documenting names. # Equality And Logical Operator URL: /devs/move-book/equalityAndLogicalOperator # Equality Move supports two equality operations: `==` (equal) and `!=` (not equal). These operators allow you to compare values and determine whether they are the same or different. ## Operations | Syntax | Operation | Description | | ------ | --------- | --------------------------------------------------------------------------- | | `==` | equal | Returns `true` if the two operands have the same value, `false` otherwise | | `!=` | not equal | Returns `true` if the two operands have different values, `false` otherwise | ## Basic Usage Both equality operations work with primitive types and user-defined types: ```move // Primitive types 0 == 0; // `true` 1u128 == 2u128; // `false` b"hello" != x"00"; // `true` ``` ## Typing Requirements Both the equal (`==`) and not-equal (`!=`) operations only work if both operands are the same type: ```move 1u8 == 1u128; // ERROR! // ^^^^^ cannot use `u128` with an operator which expects a value of type `u8` b"" != 0; // ERROR! // ^ cannot use `integer` with an operator which expects a value of type `vector` ``` ## User-Defined Types Equality and non-equality also work over user-defined types: ```move module 0x42::example { struct S has copy, drop { f: u64, s: vector } fun always_true(): bool { let s = S { f: 0, s: b"" }; // parens are not needed but added for clarity in this example (copy s) == s } fun always_false(): bool { let s = S { f: 0, s: b"" }; // parens are not needed but added for clarity in this example (copy s) != s } } ``` ## Typing with References When comparing references, the type of the reference (immutable or mutable) does not matter. You can compare an immutable `&` reference with a mutable `&mut` reference of the same underlying type: ```move let i = &0; let m = &mut 1; i == m; // `false` m == i; // `false` m == m; // `true` i == i; // `true` ``` This is equivalent to applying an explicit `freeze` to each mutable reference where needed: ```move let i = &0; let m = &mut 1; i == freeze(m); // `false` freeze(m) == i; // `false` m == m; // `true` i == i; // `true` ``` However, the underlying type must still be the same: ```move let i = &0; let s = &b""; i == s; // ERROR! // ^ expected an argument of type '&u64' ``` ## Drop Ability Restrictions Both `==` and `!=` consume the value when comparing them. As a result, the type system enforces that the type must have the `drop` ability. Without the `drop` ability, ownership must be transferred by the end of the function, and such values can only be explicitly destroyed within their declaring module. ```move module 0x42::example { struct Coin has store { value: u64 } fun invalid(c1: Coin, c2: Coin) { c1 == c2 // ERROR! // ^^ local `c2` of type `Coin` does not have the `drop` ability } } ``` ## Working with Resources A programmer can always borrow the value first instead of directly comparing the value, since reference types have the `drop` ability: ```move module 0x42::compare_without_copy { struct Coin has store { value: u64 } fun swap_if_equal(c1: Coin, c2: Coin): (Coin, Coin) { let are_equal = &c1 == &c2; // valid if (are_equal) (c2, c1) else (c1, c2) } } ``` ## Avoiding Extra Copies While you can compare any value whose type has `drop`, you should often compare by reference to avoid expensive copies: **Inefficient approach:** ```move let v1: vector = function_that_returns_vector(); let v2: vector = function_that_returns_vector(); assert!(copy v1 == copy v2, 42); // ^^^^ ^^^^ use_two_vectors(v1, v2); let s1: Foo = function_that_returns_large_struct(); let s2: Foo = function_that_returns_large_struct(); assert!(copy s1 == copy s2, 42); // ^^^^ ^^^^ use_two_foos(s1, s2); ``` **Efficient approach:** ```move let v1: vector = function_that_returns_vector(); let v2: vector = function_that_returns_vector(); assert!(&v1 == &v2, 42); // ^ ^ use_two_vectors(v1, v2); let s1: Foo = function_that_returns_large_struct(); let s2: Foo = function_that_returns_large_struct(); assert!(&s1 == &s2, 42); // ^ ^ use_two_foos(s1, s2); ``` The efficiency of the `==` operation itself remains the same, but the copies are removed, making the program more efficient overall. # Logical Operators Move supports two logical operators: `&&` (logical and) and `||` (logical or). These operators work with boolean values and provide short-circuit evaluation. ## Operations | Syntax | Operation | Description | | ------ | ----------- | ------------------------------------------------------------------- | | `&&` | logical and | Returns `true` if both operands are `true`, `false` otherwise | | `\|\|` | logical or | Returns `true` if at least one operand is `true`, `false` otherwise | ## Basic Usage Both logical operators work exclusively with `bool` type operands: ```move true && true; // `true` true && false; // `false` false && true; // `false` false && false; // `false` true || true; // `true` true || false; // `true` false || true; // `true` false || false; // `false` ``` ## Short-Circuit Evaluation Both `&&` and `||` use short-circuit evaluation, meaning the second operand is only evaluated if necessary: ### Logical AND (`&&`) With `&&`, if the first operand is `false`, the second operand is not evaluated since the result will always be `false`: ```move fun example_and() { let x = false && expensive_function(); // expensive_function() is NOT called let y = true && expensive_function(); // expensive_function() IS called } ``` ### Logical OR (`||`) With `||`, if the first operand is `true`, the second operand is not evaluated since the result will always be `true`: ```move fun example_or() { let x = true || expensive_function(); // expensive_function() is NOT called let y = false || expensive_function(); // expensive_function() IS called } ``` ## Typing Requirements Both operands must be of type `bool`. Using non-boolean types will result in a type error: ```move 1 && 2; // error: cannot use `integer` with an operator which expects a value of type `bool` true && 0; // error: cannot use `integer` with an operator which expects a value of type `bool` false || "hello"; // error: cannot use `vector` with an operator which expects a value of type `bool` ``` ## Practical Examples Logical operators are commonly used in conditional statements and assertions: ```move fun validate_user(age: u64, has_permission: bool): bool { // User must be 18 or older AND have permission age >= 18 && has_permission } fun can_access(is_admin: bool, is_owner: bool, has_key: bool): bool { // Access granted if user is admin OR owner OR has key is_admin || is_owner || has_key } fun complex_condition(x: u64, y: u64, flag: bool): bool { // Complex logical expression (x > 10 && y < 5) || (flag && x == y) } ``` ## Combining with Equality Logical operators are often combined with equality operations: ```move fun check_range(value: u64, min: u64, max: u64): bool { value >= min && value <= max } fun is_valid_coordinate(x: u64, y: u64): bool { (x >= 0 && x <= 100) && (y >= 0 && y <= 100) } fun different_values(a: u64, b: u64, c: u64): bool { a != b && b != c && a != c } ``` ## Performance Considerations Due to short-circuit evaluation, place the most likely to fail (for `&&`) or succeed (for `||`) conditions first: ```move // Efficient: cheap check first fun efficient_check(expensive_condition: bool, cheap_value: u64): bool { cheap_value > 0 && expensive_condition } // Less efficient: expensive check might run unnecessarily fun less_efficient_check(expensive_condition: bool, cheap_value: u64): bool { expensive_condition && cheap_value > 0 } ``` ## Operator Precedence Logical operators have specific precedence rules. `&&` has higher precedence than `||`: ```move // This expression: a || b && c // Is evaluated as: a || (b && c) // NOT as: (a || b) && c let result = true || false && false; // `true` (not `false`) ``` Use parentheses for clarity when combining operators: ```move let clear_intent = (a || b) && c; // Explicit grouping let also_clear = a || (b && c); // Explicit grouping ``` ## Summary Move's comparison and logical operators enable safe value comparison and boolean logic: * **Equality**: `==` and `!=` compare values of identical types; require `drop` ability * **Logical**: `&&` and `||` work with `bool` types and use short-circuit evaluation * **Type Safety**: Strict type matching prevents runtime errors * **Performance**: Use references to avoid expensive copies; leverage short-circuiting * **Precedence**: `&&` binds tighter than `||`; use parentheses for clarity These operators are essential for conditional logic and control flow in Move programs. # Friends URL: /devs/move-book/friends # Friends The friend system allows modules to grant trusted access to specific other modules. Friend modules can call functions with `public(friend)` visibility, creating controlled inter-module relationships without exposing functionality publicly. This system enables building modular architectures where related modules can collaborate while maintaining encapsulation from external access. ## Friend Declaration Modules declare friends using `friend` statements to grant access to `public(friend)` functions. Friend declarations can reference modules by their full name or through aliases. **Syntax:** ``` friend ::; friend ; ``` ### Full Module Name Declaration Use the complete module path including address when declaring friends: ```move module 0x42::core { friend 0x42::utils; // Grant friend access to utils module friend 0x42::helpers; // Grant friend access to helpers module public(friend) fun internal_operation(): u64 { 42 } } ``` ### Module Alias Declaration Import modules with `use` statements, then reference them by alias in friend declarations: ```move module 0x42::core { use 0x42::utils; // Import utils module use 0x42::helpers as h; // Import helpers with alias 'h' friend utils; // Declare friend using module name friend h; // Declare friend using alias public(friend) fun internal_operation(): u64 { 42 } } ``` **Declaration rules:** * Friend declarations must be at module scope (not within functions) * Multiple friends can be declared to form a friend list * Recommended to place friend declarations near the top for readability ## Usage Example Here's how friend modules interact: ```move module 0x42::vault { friend 0x42::admin; struct Vault has key, drop { balance: u64 } // Only admin module can call this public(friend) fun emergency_withdraw(vault: &mut Vault): u64 { let amount = vault.balance; vault.balance = 0; amount } // Public function - anyone can call public fun get_balance(vault: &Vault): u64 { vault.balance } public fun deposit(vault: &mut Vault, amount: u64) { vault.balance += amount; } public(friend) fun create_vault(initial_amount: u64): Vault { Vault { balance: initial_amount } } } module 0x42::admin { use 0x42::vault::{Self}; public fun perform_emergency_withdrawal(): u64 { let vault = vault::create_vault(20); vault::emergency_withdraw(&mut vault) } } ``` ## Declaration Rules The friend system enforces several important constraints: ### No Self-Declaration Modules cannot declare themselves as friends: ```move module 0x42::example { friend Self; // ERROR: Cannot declare self as friend friend 0x42::example; // ERROR: Cannot declare self as friend } ``` ### Same Address Requirement Friend modules must be within the same account address: ```move module 0x42::core { friend 0x43::external; // ERROR: Different address } ``` ### No Circular Dependencies Friend relationships cannot create dependency cycles: ```move module 0x42::a { use 0x42::c; friend 0x42::b; // a uses c, friends b } module 0x42::b { friend 0x42::c; // ERROR: Creates cycle (a->c, a friends b, b friends c) } ``` ### No Duplicates Each module can only be declared as a friend once: ```move module 0x42::core { use 0x42::utils as u; friend 0x42::utils; friend u; // ERROR: Duplicate friend declaration } ``` ## Summary The friend system provides controlled access between modules: * **Purpose**: Allows modules to grant selective access to `public(friend)` functions * **Access control**: Enables fine-grained control over which modules can call specific functions * **Declaration rules**: Must prevent self-declaration, circular dependencies, and duplicate declarations * **Same address requirement**: Friend modules must be within the same account address * **Use cases**: Ideal for creating trusted relationships between related modules in a package # Functions URL: /devs/move-book/functions # Functions Functions are the fundamental building blocks of Move programs, enabling code organization, reusability, and modularity. Move supports both **module functions** (reusable across transactions) and **script functions** (single-use transaction entry points). **Key characteristics:** * **Statically typed**: All parameters and return types must be explicitly declared * **Module-scoped**: Functions belong to specific modules and follow visibility rules * **Resource-aware**: Special annotations for global storage access ## Function Declaration Functions use the `fun` keyword followed by a structured declaration syntax that ensures type safety and clear interfaces: **Syntax structure:** ``` fun <[type_parameters: constraint],*>([identifier: type],*): ``` **Complete example:** ```move fun process_transaction(amount: u64, data: T): (bool, T) acquires Account { // function body (true, data) } ``` ## Visibility and Access Control Move enforces strict **module-level encapsulation** through visibility modifiers, ensuring controlled access to functionality: **Visibility levels:** * **Private (default)**: Only callable within the defining module * **Public**: Callable from any module or script * **Public(friend)**: Callable only from explicitly trusted modules * **Entry**: Can serve as transaction entry points ```move module 0x42::defi_pool { fun calculate_fees(): u64 { 100 } fun get_pool_fees(): u64 { calculate_fees() } // valid - same module } module 0x42::trading_bot { fun estimate_costs(): u64 { 0x42::defi_pool::calculate_fees() // ERROR! // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 'calculate_fees' is private to '0x42::defi_pool' } } ``` ```move script { fun calls_m_foo(): u64 { 0x42::defi_pool::calculate_fees() // ERROR! // ^^^^^^^^^^^^ 'foo' is internal to '0x42::m' } } ``` To allow access from other modules or from scripts, the function must be declared `public` or `public(friend)`. ### Public Functions **Public functions** allow access to the function from any module or script. As shown in the following example, a public function can be called by: * other functions defined in the same module, * functions defined in another module, or * the function defined in a script. ```move module 0x42::defi_pool { public fun get_pool_balance(): u64 { 1000000 } fun internal_calculation(): u64 { get_pool_balance() } // valid - same module } module 0x42::trading_bot { fun check_liquidity(): u64 { 0x42::defi_pool::get_pool_balance() // valid - public access } } ``` ```move script { fun calls_m_foo(): u64 { 0x42::m::foo() // valid } } ``` ### Public(friend) Visibility The `public(friend)` visibility modifier provides controlled access between trusted modules. It's more restrictive than `public` but more permissive than private visibility. **Access rules for `public(friend)` functions:** * Functions within the same module can call them * Functions in explicitly declared friend modules can call them * Functions in non-friend modules cannot call them * Script functions cannot call them (scripts cannot be declared as friends) ```move module 0x42::vault_core { friend 0x42::vault_manager; // friend declaration public(friend) fun access_vault_funds(): u64 { 50000 } fun internal_operation(): u64 { access_vault_funds() } // valid - same module } module 0x42::vault_manager { fun manage_funds(): u64 { 0x42::vault_core::access_vault_funds() // valid - trusted friend } } module 0x42::external_user { fun try_access(): u64 { 0x42::vault_core::access_vault_funds() // ERROR! // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 'access_vault_funds' can only be called from a 'friend' of module '0x42::vault_core' } } ``` ```move script { fun calls_m_foo(): u64 { 0x42::m::foo() // ERROR! // ^^^^^^^^^^^^ 'foo' can only be called from a 'friend' of module '0x42::m' } } ``` ### Entry Functions **Entry functions** serve as transaction entry points, enabling direct invocation from external clients while maintaining Move's safety guarantees: **Entry function characteristics:** * **Transaction entry points**: Can be called directly by transactions * **Client interfaces**: Primary way external applications interact with modules * **Flexible visibility**: Can be public, private, or friend-restricted * **Still callable internally**: Other Move functions can invoke them **Design benefits:** * **Clear interfaces**: Explicitly marks functions intended for external use * **Security boundaries**: Helps identify transaction entry points * **Composability**: Entry functions can call other functions normally ```move module 0x42::nft_marketplace { public entry fun create_listing(price: u64): u64 { price } fun internal_setup(): u64 { create_listing(100) } // valid - internal call } module 0x42::auction_house { fun start_auction(): u64 { 0x42::nft_marketplace::create_listing(500) // valid - cross-module call } } module 0x42::trading_platform { public entry fun launch_sale(): u64 { 0x42::nft_marketplace::create_listing(1000) // valid - entry calling entry } } ``` ```move script { fun calls_m_foo(): u64 { 0x42::m::foo() // valid! } } ``` **Private entry functions** provide transaction entry points while maintaining module privacy: ```move module 0x42::admin_panel { entry fun system_initialize(): u64 { 42 } // private entry - transaction accessible but not cross-module } module 0x42::user_interface { fun setup_system(): u64 { 0x42::admin_panel::system_initialize() // ERROR! // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 'system_initialize' is private to '0x42::admin_panel' } } ``` **Key insight**: Entry functions can be private, allowing transaction access while preventing cross-module calls. ## Function Naming Move enforces specific naming conventions for functions to ensure consistency and readability: **Naming rules:** * **First character**: Letters (a-z, A-Z) only * **Subsequent characters**: Letters, digits (0-9), underscores (\_) * **Convention**: snake\_case is strongly recommended * **Restrictions**: Cannot start with underscore or digit ```move // Valid function names fun process_payment() {} fun calculateFees() {} // valid but not recommended (prefer snake_case) fun handle_nft_transfer() {} fun get_balance_v2() {} // Invalid function names fun _private_func() {} // ERROR: cannot start with underscore fun 2nd_attempt() {} // ERROR: cannot start with digit fun process-payment() {} // ERROR: hyphens not allowed ``` **Best practices:** * Use descriptive, action-oriented names * Follow snake\_case convention consistently * Avoid abbreviations that reduce clarity * Consider the function's purpose in naming ## Generic Functions **Generic functions** enable code reuse across different types while maintaining type safety through parameterized types: **Generic syntax:** * **Type parameters**: Declared in angle brackets `` * **Constraints**: Optional ability requirements `` * **Usage**: Type parameters can be used in parameters, return types, and function body ```move // Simple generic function fun swap(x: T, y: T): (T, T) { (y, x) } // Generic with constraints fun duplicate_and_store(item: T): (T, T) { (copy item, item) } // Multiple type parameters fun process_pair( asset: Asset, meta: Metadata ): (Asset, Metadata) { (asset, copy meta) } ``` ## Function Parameters **Parameters** define the inputs a function accepts, with explicit type annotations ensuring type safety: **Parameter syntax:** * **Format**: `name: type` * **Multiple parameters**: Comma-separated list * **Type annotations**: Always required (no type inference) * **Ownership**: Parameters transfer ownership unless borrowed ```move // Single parameter fun calculate_fee(amount: u64): u64 { amount / 100 } // Multiple parameters with different types fun create_trade(trader: address, amount: u64, active: bool): Trade { Trade { trader, amount, active } } // Reference parameters (borrowing) fun read_balance(account: &Account): u64 { account.balance } // Mutable reference parameters fun update_balance(account: &mut Account, new_balance: u64) { account.balance = new_balance; } ``` **Parameter-less functions** are common for constructors and getters: ```move module 0x42::token_factory { struct TokenConfig { decimals: u8, max_supply: u64 } // Constructor with no parameters fun default_config(): TokenConfig { TokenConfig { decimals: 8, max_supply: 1000000 } } // Getter with no parameters fun get_current_timestamp(): u64 { // implementation 1234567890 } } ``` ## Global Storage Access **Acquires annotations** ensure safe access to global storage by explicitly declaring which resources a function will access: **Global storage operations requiring acquires:** * `move_from(address)`: Moving resource from global storage * `borrow_global(address)`: Immutable borrow from global storage * `borrow_global_mut(address)`: Mutable borrow from global storage ```move module 0x42::liquidity_pool { struct Pool has key { reserves: u64, fees_collected: u64 } public fun initialize_pool(admin: &signer, initial_reserves: u64) { move_to(admin, Pool { reserves: initial_reserves, fees_collected: 0 }) } public fun withdraw_reserves(pool_address: address): u64 acquires Pool { let Pool { reserves, fees_collected: _ } = move_from(pool_address); reserves } public fun get_pool_info(pool_address: address): (u64, u64) acquires Pool { let pool = borrow_global(pool_address); (pool.reserves, pool.fees_collected) } public fun add_fees(pool_address: address, fee_amount: u64) acquires Pool { let pool = borrow_global_mut(pool_address); pool.fees_collected = pool.fees_collected + fee_amount; } } ``` ### Transitive Acquires **Transitive acquires** occur when a function calls another function that accesses global storage. The calling function must also declare the acquires: **Key rules:** * **Same module**: Must declare acquires for transitive calls within the module * **Cross-module**: No acquires needed when calling functions from other modules * **Reason**: Cross-module resource access is impossible, so no reference safety issues ```move module 0x42::staking_pool { struct StakeInfo has key { amount: u64, rewards: u64 } public fun create_stake(staker: &signer, amount: u64) { move_to(staker, StakeInfo { amount, rewards: 0 }) } public fun claim_rewards(staker_addr: address): u64 acquires StakeInfo { let stake = borrow_global_mut(staker_addr); let rewards = stake.rewards; stake.rewards = 0; rewards } // Transitive acquires - must declare StakeInfo because it calls claim_rewards public fun compound_rewards(staker_addr: address) acquires StakeInfo { let rewards = claim_rewards(staker_addr); // calls function that acquires StakeInfo let stake = borrow_global_mut(staker_addr); stake.amount = stake.amount + rewards; } } ``` ```move module 0x42::rewards_distributor { fun distribute_rewards(staker_addr: address): u64 { 0x42::staking_pool::claim_rewards(staker_addr) // no acquires needed - cross-module } } ``` ### Multiple Acquires **Multiple acquires** allow functions to access several different resource types from global storage: **Syntax**: List all acquired resources separated by commas **Use cases**: Functions that coordinate between multiple resource types ```move module 0x42::defi_protocol { use std::vector; struct UserAccount has key { balance: u64, locked: u64 } struct RewardPool has key { total_rewards: u64, participants: vector
} struct GovernanceVotes has key { votes: u64, proposals: vector } // Function accessing multiple resource types public fun participate_in_governance( user_addr: address, vote_weight: u64, proposal_id: u64, ) acquires UserAccount, RewardPool, GovernanceVotes { // Access user account let account = borrow_global_mut(user_addr); assert!(account.balance >= vote_weight, 1); account.locked = account.locked + vote_weight; // Update reward pool let pool = borrow_global_mut(user_addr); if (!vector::contains(&pool.participants, &user_addr)) { vector::push_back(&mut pool.participants, user_addr); }; // Record governance vote let votes = borrow_global_mut(user_addr); votes.votes = votes.votes + vote_weight; vector::push_back(&mut votes.proposals, proposal_id); } } ``` ## Return Types **Return types** specify what values a function produces, ensuring type safety and clear interfaces: **Basic return type syntax:** ```move fun calculate_interest(): u64 { 500 } fun get_user_name(): vector { b"Alice" } fun is_valid(): bool { true } ``` ### Multiple Return Values **Tuple returns** enable functions to produce multiple values simultaneously: ```move // Trading pair information fun get_trading_pair(): (u64, u64, bool) { (1000, 2000, true) // (price, volume, active) } // User account details fun get_account_info(addr: address): (vector, u64, bool) { (b"trader_123", 50000, true) // (username, balance, verified) } // Coordinate pair fun get_position(): (u64, u64) { (100, 200) // (x, y) } ``` **Destructuring multiple returns:** ```move fun use_multiple_returns() { let (price, volume, active) = get_trading_pair(); let (name, balance, _) = get_account_info(@0x123); // ignore verified status } ``` ### Unit Return Type **Unit type `()`** represents "no meaningful return value" - used for functions that perform actions rather than compute values: ```move fun just_unit(): () { () } fun just_unit() { () } fun just_unit() { } ``` ### Script Function Return Type Script functions must have a return type of unit `()`: ```move script { fun transfer_tokens() { // transaction logic // must return () - no other return type allowed } fun initialize_account() { // setup logic // implicitly returns () } } ``` As mentioned in the tuples section, these tuple "values" are virtual and do not exist at runtime. So for a function that returns unit `()`, it will not be returning any value at all during execution. ## Function Body **Function bodies** contain the implementation logic using expression blocks where the final expression becomes the return value: ```move fun calculate_fee(amount: u64): u64 { let base_fee = 10; let percentage_fee = amount / 100; base_fee + percentage_fee // this expression is returned } fun process_payment(sender: address, amount: u64): bool { let sender_balance = get_balance(sender); if (sender_balance >= amount) { deduct_balance(sender, amount); true // return success } else { false // return failure } } ``` **Expression block characteristics:** * **Sequential execution**: Statements execute in order * **Final expression**: Last expression becomes the return value * **No explicit return needed**: Final expression is automatically returned * **Type consistency**: Final expression must match declared return type ## Native Functions **Native functions** are implemented in the Move VM rather than in Move code, providing access to system-level operations: **Characteristics of native functions:** * **VM implementation**: Body provided by the virtual machine, not Move code * **Standard library**: Most natives are in `std` modules * **No custom natives**: Developers cannot create new native functions **Common native functions:** ```move module std::vector { native public fun empty(): vector; // ... } ``` ## Calling Functions When calling a function, the name can be specified either through an alias or fully qualified: ```move module 0x42::math { public fun get_pi(): u64 { 314 } } ``` ```move script { use 0x42::example::{Self, zero}; fun call_zero() { // With the `use` above all of these calls are equivalent 0x42::example::zero(); example::zero(); zero(); } } ``` ### Function Arguments **Arguments** must be provided for every parameter when calling functions, with strict type matching: ```move module 0x42::trading_engine { public fun get_base_fee(): u64 { 25 } public fun apply_discount(fee: u64): u64 { fee * 90 / 100 } public fun calculate_total(principal: u64, fee: u64): u64 { principal + fee } public fun create_order(trader: address, amount: u64, price: u64): bool { true } } ``` **Calling with correct arguments:** ```move script { use 0x42::trading_engine; fun execute_trades() { // No parameters let base_fee = trading_engine::get_base_fee(); // Single parameter let discounted_fee = trading_engine::apply_discount(base_fee); // Multiple parameters let total_cost = trading_engine::calculate_total(1000, discounted_fee); // Complex parameter types let success = trading_engine::create_order(@0x123, 500, 2000); } } ``` **Argument requirements:** * **Exact count**: Must provide argument for every parameter * **Type matching**: Arguments must match parameter types exactly * **Order matters**: Arguments passed in parameter declaration order ### Generic Type Arguments **Type arguments** for generic functions can be explicitly specified or automatically inferred: ```move module 0x42::container_utils { public fun create_empty(): vector { vector::empty() } public fun swap(x: T, y: T): (T, T) { (y, x) } public fun first_element(v: &vector): T { *vector::borrow(v, 0) } } ``` **Type argument usage:** ```move script { use 0x42::container_utils; fun demonstrate_generics() { // Explicit type arguments let empty_u64_vec = container_utils::create_empty(); let empty_bool_vec = container_utils::create_empty(); // Type inference (compiler determines types) let (b, a) = container_utils::swap(100u64, 200u64); // infers T = u64 let (second, first) = container_utils::swap(true, false); // infers T = bool // Mixed approach let numbers = vector[1, 2, 3]; let first_num = container_utils::first_element(&numbers); } } ``` **When to use explicit type arguments:** * **Ambiguous contexts**: When compiler cannot infer the type * **Clarity**: When explicit types improve code readability * **Empty containers**: When creating empty generic containers ## Return Value Mechanics **Return values** are produced by the final expression in a function's body, enabling both simple and complex computations: **Simple return example:** ```move fun calculate_tax(amount: u64): u64 { amount * 8 / 100 // final expression becomes return value } ``` **Complex return with intermediate calculations:** ```move fun calculate_compound_yield(principal: u64, rate: u64, periods: u64): u64 { let rate_per_period = rate / periods; let compound_factor = 100 + rate_per_period; let final_amount = principal * compound_factor / 100; final_amount // this final expression is returned } ``` **Conditional returns:** ```move fun determine_fee_tier(volume: u64): u64 { if (volume >= 1000000) { 5 // premium tier } else if (volume >= 100000) { 10 // standard tier } else { 25 // basic tier } // the if-else expression result is returned } ``` ### Explicit Return Statements **Explicit returns** provide early exit from functions, especially useful in complex control flow: **Basic explicit return:** ```move fun validate_amount(amount: u64): u64 { if (amount == 0) return 0; // early exit amount * 105 / 100 // normal calculation } ``` These two functions are equivalent. In this slightly more involved example, the function subtracts two `u64` values, but returns early with 0 if the second value is too large: ```move fun safe_sub(x: u64, y: u64): u64 { if (y > x) return 0; x - y } ``` Note that the body of this function could also have been written as `if (y > x) 0 else x - y`. However `return` really shines is in exiting deep within other control flow constructs. In this example, the function iterates through a vector to find the index of a given value: ```move use std::vector; use std::option::{Self, Option}; fun index_of(v: &vector, target: &T): Option { let i = 0; let n = vector::length(v); while (i < n) { if (vector::borrow(v, i) == target) return option::some(i); i = i + 1 }; option::none() } ``` Using `return` without an argument is shorthand for `return ()`. That is, the following two functions are equivalent: ```move fun foo() { return } fun foo() { return () } ``` ## Function Visibility Summary | Visibility | Callable From | Use Case | Example | | ---------------- | ----------------------- | ----------------- | -------------------------------------- | | (default) | Same module only | Internal helpers | `fun calculate_internal_fee()` | | `public` | Any module or script | Public APIs | `public fun get_balance()` | | `public(friend)` | Same module + friends | Controlled access | `public(friend) fun admin_operation()` | | `entry` | Transaction entry point | Main functions | `entry fun create_account()` | ## Summary Functions are the primary building blocks for organizing and reusing code in Move: * **Declaration**: Use `fun` keyword with parameters, return types, and optional `acquires` annotations * **Visibility**: Control access with `public`, `public(friend)`, or `entry` modifiers * **Safety**: `acquires` annotations ensure safe global storage access * **Flexibility**: Support generics, multiple return values, and early returns * **Patterns**: Common patterns include constructors, accessors, mutators, and utilities Functions enable modular, safe, and reusable code organization in Move programs. # Generics URL: /devs/move-book/generics # Generics Generics enable defining functions and structs that work with multiple data types while maintaining type safety. This feature, also known as **parametric polymorphism**, allows you to write reusable code that operates on any type satisfying specified constraints. **Key benefits:** * **Code reuse**: Write once, use with multiple types * **Type safety**: Compile-time type checking prevents runtime errors * **Library development**: Essential for building flexible, reusable components Generics are extensively used in Move's standard library (like `vector`) and are crucial for building type-safe, reusable smart contract components. ## Type Parameters Both functions and structs can accept type parameters enclosed in angle brackets `<...>`. These parameters act as placeholders for concrete types that will be specified later. ### Generic Functions Type parameters are placed after the function name and before the parameter list: ```move module 0x42::utils { // Generic identity function - works with any type fun id(x: T): T { x } // Generic swap function fun swap(x: T, y: T): (T, T) { (y, x) } } ``` ### Generic Structs Type parameters follow the struct name and can be used in field definitions: ```move module 0x42::containers { struct Box has copy, drop { value: T } struct Pair has copy, drop { first: T1, second: T2, } } ``` ## Using Generics ### Function Calls You can explicitly specify type arguments or let Move's type inference determine them: ```move fun example() { // Explicit type specification let x = id(true); // Type inference (preferred when possible) let y = id(42u64); // T inferred as u64 } ``` ### Struct Construction Similar to functions, type arguments can be explicit or inferred: ```move fun create_containers() { // Explicit types let box = Box { value: 100 }; // Type inference let pair = Pair { first: true, second: 42 }; // Pair } ``` ## Type Inference Move's compiler automatically infers types in most cases, reducing verbosity while maintaining safety: ```move fun inference_examples() { let numbers = vector[1, 2, 3]; // vector inferred vector::push_back(&mut numbers, 4); // T inferred from usage } ``` **When manual annotation is required:** * Functions with type parameters only in return positions * Ambiguous contexts where multiple types are possible ## Phantom Type Parameters Phantom type parameters solve a critical problem with ability derivation in generic structs. When a struct has generic parameters, it can only have abilities that **all** its type parameters possess - even if those parameters aren't actually used in the struct fields. ### The Problem Consider this currency system without phantom parameters: ```move module 0x42::currency { struct Currency1 {} // No abilities struct Currency2 {} // No abilities // This struct wants 'store' ability struct Coin has store { value: u64 } } ``` **Issue**: Even though `Currency1` and `Currency2` are never used in `Coin`'s fields, the struct `Coin` **cannot** have the `store` ability because `Currency1` lacks it. This prevents storing coins in global storage! ### The Solution: Phantom Parameters Phantom parameters are excluded from ability derivation, solving this problem: ```move module 0x42::currency { struct Currency1 {} // Still no abilities needed struct Currency2 {} // phantom means Currency doesn't affect abilities struct Coin has store { value: u64 } public fun mint(amount: u64): Coin { Coin { value: amount } // Now Coin has 'store'! } } ``` ### Declaration Rules Phantom parameters can only appear in "phantom positions": ```move // Valid: T1 not used at all struct S1 { f: u64 } // Valid: T1 only used as phantom argument struct S2 { f: S1 } // Invalid: T used in non-phantom position struct S3 { f: T } // Invalid: T used as argument to non-phantom parameter struct S4 { f: vector } ``` **Key benefits:** * **Ability independence**: Phantom parameters don't affect struct abilities * **Type safety**: Different phantom arguments create distinct types * **Zero runtime cost**: No storage or computation overhead ## Constraints By default, generic type parameters have **no abilities**, making the type system very conservative. Constraints allow you to specify what abilities unknown types must have, enabling safe operations that would otherwise be forbidden. ### Why Constraints Are Needed Without constraints, generic functions are severely limited: ```move fun unsafe_consume(x: T) { // error! x does not have 'drop' } fun unsafe_double(x: T) { (copy x, x) // error! x does not have 'copy' } ``` ### Declaring Constraints Constraints specify required abilities using the `:` syntax: ```move // Single constraint fun consume(x: T) { // valid! // x will be dropped automatically } // Multiple constraints fun double(x: T) { (copy x, x) // valid! } // All four abilities T: copy + drop + store + key ``` ### Constraint Verification Constraints are checked at **call sites**, not definitions: ```move struct R {} fun foo() { let r = R {}; consume(r); // ^ error! R does not have 'drop' } fun foo(): (R, R) { let r = R {}; double(r) // ^ error! R does not have 'copy' } ``` ### Practical Examples ```move module 0x42::safe_operations { // Safe resource cleanup fun cleanup(items: vector) { // All items automatically dropped } // Safe value duplication fun backup(original: T): (T, T) { (copy original, original) } // Safe global storage struct Container has key { contents: T } } ``` ## Unused Type Parameters Move allows type parameters that don't appear in struct fields, enabling powerful **type-level programming**. These "unused" parameters provide compile-time type distinctions without runtime overhead. ### Type-Level Distinctions Unused parameters create different types that are structurally identical but logically distinct: ```move module 0x42::currency_system { // Currency specifier types (empty structs) struct USD {} struct EUR {} struct BTC {} // Generic coin - Currency parameter is "unused" struct Coin has store { value: u64 // Currency doesn't appear here! } // Each currency creates a distinct type public fun mint_usd(value: u64): Coin { Coin { value } } public fun mint_eur(value: u64): Coin { Coin { value } } // Type safety: can't mix currencies! public fun exchange_usd_to_eur(usd: Coin): Coin { let Coin { value } = usd; Coin { value: value * 85 / 100 } // 85% exchange rate } } ``` ### Benefits of Unused Parameters **Type Safety**: Prevents mixing incompatible values: ```move fun example() { let dollars = mint_usd(100); let euros = mint_eur(85); // Compile error: type mismatch! // exchange_usd_to_eur(euros); // EUR ≠ USD // Correct usage let converted = exchange_usd_to_eur(dollars); } ``` **Generic Programming**: Write code that works with any currency: ```move // Generic function works with any currency public fun get_value(coin: &Coin): u64 { coin.value } // Specific function for USD only public fun get_usd_value(coin: &Coin): u64 { coin.value } ``` ### Real-World Applications ```move module 0x42::access_control { // Permission types struct AdminRole {} struct UserRole {} struct GuestRole {} // Capability with role-based access struct Capability has store { permissions: u64 } // Only admins can create admin capabilities public fun create_admin_cap(): Capability { Capability { permissions: 0xFF } } // Role-specific operations public fun admin_only(_cap: &Capability, data: T): T { data // Only callable with admin capability } } ``` **Key advantages:** * **Zero runtime cost**: No extra storage or computation * **Compile-time safety**: Type errors caught early * **Clear intent**: Types express domain concepts * **Flexible design**: Easy to extend with new "categories" ## Limitations ### Recursive Restrictions Move prevents certain recursive patterns to ensure type system soundness: ```move // Direct recursion not allowed struct Node { value: T, next: Node // ERROR: recursive struct } // Infinite type generation not allowed fun recursive_types() { recursive_types>(); // ERROR: infinite types } ``` ### Type-Level Recursion The compiler conservatively prevents patterns that could generate infinite types, even if they would terminate at runtime: ```move fun controlled_recursion(depth: u64) { if (depth > 0) { controlled_recursion>(depth - 1); // ERROR: still forbidden } } ``` ## Best Practices * Use descriptive type parameter names (`Currency` vs `T`) * Prefer type inference over explicit specification * Use phantom parameters for type-level distinctions * Apply minimal necessary constraints ## Summary Generics enable writing reusable, type-safe code that works across multiple data types: * **Code reuse**: Write once, use with multiple types while maintaining type safety * **Type parameters**: Functions and structs accept placeholders for concrete types * **Constraints**: Specify required abilities for safe operations on unknown types * **Phantom parameters**: Enable type-level programming without affecting abilities * **Unused parameters**: Create type distinctions without runtime overhead * **Type inference**: Compiler automatically determines types in most cases * **Zero runtime cost**: All generic resolution happens at compile time * **Essential for libraries**: Critical for building robust, reusable Move components # Global Storage URL: /devs/move-book/globalStorage # Global Storage Move programs operate on **persistent global storage** - a tree-structured data store that maintains state across transactions. Organized as a forest of trees rooted at account addresses. Global storage serves as Move's database, enabling smart contracts to maintain state, store user data, and coordinate between different program executions. ## Storage Structure Global storage conceptually resembles this structure: ``` GlobalStorage { resources: Map<(address, ResourceType), ResourceValue> modules: Map<(address, ModuleName), ModuleBytecode> } ``` **Organization principles:** * Each **address** acts as a namespace for both resources and modules * **Resources** store data values with the `key` ability * **Modules** contain executable code and type definitions * **Uniqueness** constraint: one resource per type per address ## Storage Operations Move provides five core operations for interacting with global storage: | Operation | Description | Aborts If | | --------------------------------------- | --------------------------------------- | ----------------------- | | `move_to(&signer, T)` | Publish resource under signer's address | Resource already exists | | `move_from(address): T` | Remove and return resource | Resource doesn't exist | | `borrow_global_mut(address): &mut T` | Get mutable reference | Resource doesn't exist | | `borrow_global(address): &T` | Get immutable reference | Resource doesn't exist | | `exists(address): bool` | Check resource existence | Never | **Access control:** * All operations require the resource type `T` to have the `key` ability * Type `T` must be declared in the current module (module privacy) * Operations use `address` or `&signer` to specify the storage location ## Basic Usage Example Here's a simple counter module demonstrating storage operations: ```move module 0x42::counter { use std::signer; struct Counter has key { value: u64 } public fun create(account: &signer, initial: u64) { move_to(account, Counter { value: initial }); } public fun increment(addr: address) acquires Counter { let counter = borrow_global_mut(addr); counter.value = counter.value + 1; } public fun get_value(addr: address): u64 acquires Counter { borrow_global(addr).value } public fun destroy(account: &signer): u64 acquires Counter { let Counter { value } = move_from(signer::address_of(account)); value } } ``` ## The `acquires` Annotation Functions that access global storage must declare which resources they acquire: ```move // Required when function directly accesses global storage public fun read_counter(addr: address): u64 acquires Counter { borrow_global(addr).value } // Required when calling functions that acquire resources (same module only) fun increment_twice(addr: address) acquires Counter { increment(addr); // increment() has acquires Counter increment(addr); } ``` **Annotation rules:** Required for direct global storage access: ```move fun get_value(addr: address): u64 acquires Counter { borrow_global(addr).value } ``` Required when calling functions with `acquires` (same module only): ```move fun double_increment(addr: address) acquires Counter { increment(addr); // increment() has acquires Counter } ``` Multiple resources and generic resources: ```move fun multi_access(addr: address) acquires Counter, Profile { // Multiple: acquires Counter, Profile } ``` ## Storage Polymorphism Global storage operations work with generic types, enabling powerful design patterns: ```move struct Container has key { data: T } // Store any type T in global storage fun store_data(account: &signer, data: T) { move_to>(account, Container { data }); } // Retrieve specific type at runtime fun get_u64_data(addr: address): u64 acquires Container { borrow_global>(addr).data } ``` **Benefits of storage polymorphism:** * Write generic storage functions once * Type safety maintained at compile time * Enables flexible, reusable storage patterns ## Reference Safety Move prevents dangling references through strict rules and the `acquires` annotation to ensure static reference safety. ### No Returning Global References Functions cannot return references pointing to global storage: ```move // Not allowed - prevents dangling references fun get_counter_ref(addr: address): &Counter { borrow_global(addr) // ERROR! } // Allowed - return owned values or local references fun get_counter_value(addr: address): u64 acquires Counter { borrow_global(addr).value } ``` ### Acquires Annotation Protection The `acquires` annotation prevents dangling references by tracking resource access: ```move module 0x42::safety { struct T has key { f: u64 } fun borrow_then_remove_bad(a: address) acquires T { let t_ref: &mut T = borrow_global_mut(a); let t = remove_t(a); // ERROR: type system prevents this // t_ref would be dangling! } fun remove_t(a: address): T acquires T { move_from(a) } } ``` ### Reference Lifetime Rules Global references must be used within the same function scope: ```move fun safe_usage(addr: address) acquires Counter { let counter_ref = borrow_global_mut(addr); counter_ref.value = 100; // Safe - same scope // Reference automatically expires at function end } ``` These restrictions ensure **static reference safety** - no dangling references, null dereferences, or memory safety violations at compile time. ## Best Practices * Use descriptive struct names with the `key` ability * Leverage module privacy for access control * Use `exists()` to check before accessing resources * Minimize storage operations for better performance ## Summary Global storage provides Move's persistent data layer: * **Tree structure**: Organized as a forest rooted at account addresses * **Core operations**: Five operations (move\_to, move\_from, borrow\_global, borrow\_global\_mut, exists) * **Reference safety**: Strict rules prevent dangling references and memory violations * **Acquires annotation**: Tracks resource access for safe operations * **Storage polymorphism**: Generic storage patterns with compile-time type safety * **State persistence**: Essential for maintaining data across transactions in Move applications # Integer URL: /devs/move-book/integer # Integer Types Move supports unsigned integers of various sizes, from 8-bit to 256-bit. These are the fundamental numeric types for calculations and data storage. ## Integer Types Move provides six integer types: | Type | Size | Range | | ------ | ------- | ------------------------ | | `u8` | 8-bit | 0 to 28 - 1 | | `u16` | 16-bit | 0 to 216 - 1 | | `u32` | 32-bit | 0 to 232 - 1 | | `u64` | 64-bit | 0 to 264 - 1 | | `u128` | 128-bit | 0 to 2128 - 1 | | `u256` | 256-bit | 0 to 2256 - 1 | ## Integer Literals You can write integer literals in several ways: ### Decimal Literals ```move let small = 42; // Defaults to u64 let tiny = 255u8; // Explicit u8 type let big = 1000u128; // Explicit u128 type ``` ### Hexadecimal Literals ```move let hex_value = 0xFF; // 255 in decimal let hex_u32 = 0xDEADBEEFu32; ``` ### Underscores for Readability ```move let million = 1_000_000; let hex_readable = 0xAB_CD_EF_12u32; ``` ## Type Inference The compiler tries to infer integer types from context: ```move let x = 42; // Inferred as u64 (default) let y: u8 = 42; // Explicitly u8 let z = 42u16; // Explicitly u16 using suffix ``` ## Arithmetic Operations All integer types support basic arithmetic operations. Both operands must be the same type: ```move let a = 10u8; let b = 5u8; let sum = a + b; // 15 let difference = a - b; // 5 let product = a * b; // 50 let quotient = a / b; // 2 let remainder = a % b; // 0 ``` ### Safety Features Move prevents common arithmetic errors: * **Overflow**: Operations that exceed the type's maximum value will abort * **Underflow**: Subtracting to get below zero will abort * **Division by zero**: Will abort the program ```move let max_u8 = 255u8; // let overflow = max_u8 + 1; // This would abort! let zero = 0u8; // let div_by_zero = 10 / zero; // This would abort! ``` ## Comparison Operations Integers can be compared using standard operators: ```move let x = 10; let y = 20; let less = x < y; // true let greater = x > y; // false let less_equal = x <= y; // true let greater_equal = x >= y; // false let equal = x == y; // false let not_equal = x != y; // true ``` ## Type Casting You can convert between integer types using the `as` operator: ```move let small: u8 = 42; let medium: u16 = small as u16; // u8 to u16 let large: u64 = medium as u64; // u16 to u64 ``` ### Safe Casting Casting to a larger type is always safe: ```move let tiny: u8 = 255; let big: u64 = tiny as u64; // Always works ``` ### Unsafe Casting Casting to a smaller type can fail if the value is too large: ```move let big: u64 = 1000; // let small: u8 = big as u8; // Would abort! (1000 > 255) ``` ### Preventing Overflow with Casting Cast to larger types before operations that might overflow: ```move let a: u8 = 200; let b: u8 = 100; // let overflow = a + b; // Would abort! // Safe approach: let safe_sum: u16 = (a as u16) + (b as u16); // 300 ``` ## Practical Examples Here are common integer usage patterns: ```move // Age validation fun is_adult(age: u8): bool { age >= 18 } // Price calculation fun calculate_total(price: u64, quantity: u32): u64 { price * (quantity as u64) } // Range checking fun is_valid_percentage(value: u8): bool { value <= 100 } ``` ## Ownership As with the other scalar values built-in to the language, integer values are implicitly copyable, meaning they can be copied without an explicit instruction such as `copy`. ## Summary Integer types in Move are: * **Unsigned only** - no negative numbers * **Size-specific** - choose the right size for your data * **Safe by default** - operations abort on overflow/underflow * **Castable** - convert between sizes with `as` * **Copyable** - no explicit `copy` needed Choose the smallest type that fits your data to save storage space, but use larger types for calculations to avoid overflow. # Loop URL: /devs/move-book/loop # Loop Move offers multiple constructs for looping: `while`, `loop`, and `for`. These constructs allow you to repeat code execution based on conditions or iterate over ranges and collections. ## While Loops The `while` construct repeats the body (an expression of type unit) until the condition (an expression of type `bool`) evaluates to `false`. ### Basic While Loop Here is an example of a simple while loop that calculates the product of numbers from 1 to n (factorial): ```move fun factorial(n: u64): u64 { let result = 1; let i = 1; while (i <= n) { result *= i; i += 1; }; result } ``` ### Infinite Loops Infinite loops are allowed but should be used with caution due to computational cost: ```move fun foo() { while (true) { } } ``` ## Break Statement The `break` expression can be used to exit a loop before the condition evaluates to `false`. For example, this loop uses `break` to find the first power of 2 that exceeds a given threshold: ```move fun first_power_exceeding(threshold: u64): u64 { let power = 1; let exponent = 0; while (true) { if (power > threshold) break; power *= 2; exponent += 1; }; power } ``` The `break` expression cannot be used outside of a loop. ## Continue Statement The `continue` expression skips the rest of the loop and continues to the next iteration. This loop uses `continue` to count only odd numbers from 1 to n: ```move fun count_odd_numbers(n: u64): u64 { let count = 0; let i = 0; while (i < n) { i += 1; if (i % 2 == 0) continue; count += 1; }; count } ``` The `continue` expression cannot be used outside of a loop. ## The Type of Break and Continue `break` and `continue`, much like `return` and `abort`, can have any type. The following examples illustrate where this flexible typing can be helpful: ```move fun merge_until_duplicate( v1: vector, v2: vector, ): vector { let result = vector::empty(); while (!vector::is_empty(&v1) && !vector::is_empty(&v2)) { let val1 = *vector::borrow(&v1, 0); let val2 = *vector::borrow(&v2, 0); let next_val = if (val1 < val2) vector::remove(&mut v1, 0) else if (val2 < val1) vector::remove(&mut v2, 0) else break; // Here, `break` has type `u8` vector::push_back(&mut result, next_val); }; result } ``` ```move fun collect_valid_strings( lengths: vector, v1: &vector>, v2: &vector> ): vector> { let len1 = vector::length(v1); let len2 = vector::length(v2); let result = vector::empty(); while (!vector::is_empty(&lengths)) { let target_len = vector::pop_back(&mut lengths); let chosen_vector = if (target_len <= len1) v1 else if (target_len <= len2) v2 else continue; // Here, `continue` has type `&vector>` vector::push_back(&mut result, *vector::borrow(chosen_vector, target_len - 1)) }; result } ``` ## The Loop Expression The `loop` expression repeats the loop body (an expression with type `()`) until it hits a `break`. Without a `break`, the loop will continue forever: ```move fun infinite_counter() { let counter = 0; loop { counter = counter + 1 } } ``` ### Loop with Break Here is an example that uses `loop` to calculate the greatest common divisor (GCD): ```move fun gcd(a: u64, b: u64): u64 { let x = a; let y = b; loop { if (y == 0) break; let temp = y; y = x % y; x = temp }; x } ``` ### Loop with Continue As you might expect, `continue` can also be used inside a `loop`. Here is an example that counts prime numbers up to n: ```move fun count_primes(n: u64): u64 { let count = 0; let num = 2; loop { if (num > n) break; if (!is_prime(num)) { num = num + 1; continue; }; count = count + 1; num = num + 1 }; count } fun is_prime(n: u64): bool { if (n < 2) return false; let i = 2; while (i * i <= n) { if (n % i == 0) return false; i = i + 1 }; true } ``` ## The Type of While and Loop Move loops are typed expressions. A `while` expression always has type `()`: ```move let () = while (i < 10) { i = i + 1 }; ``` If a `loop` contains a `break`, the expression has type unit `()`: ```move (loop { if (i < 10) i = i + 1 else break }: ()); let () = loop { if (i < 10) i = i + 1 else break }; ``` If `loop` does not have a `break`, `loop` can have any type much like `return`, `abort`, `break`, and `continue`: ```move (loop (): u64); (loop (): address); (loop (): &vector>); ``` ## For Loops For loops are used to iterate over a range of values, providing a more concise syntax for common iteration patterns. ### Basic For Loop Syntax ```move for (i in 1..n) { // code to be executed } ``` ### Range Iteration For loops can iterate over numeric ranges: ```move fun product_range(start: u64, end: u64): u64 { let product = 1; for (i in start..end) { product *= i; }; product } ``` ### Vector Iteration For loops can iterate over vector indices: ```move fun find_minimum(v: &vector): u64 { let min_val = *vector::borrow(v, 0); for (i in 1..vector::length(v)) { let current = *vector::borrow(v, i); if (current < min_val) { min_val = current; }; }; min_val } ``` ### For Loop with Break and Continue `break` and `continue` work in for loops just like in while loops: ```move fun find_first_perfect_square(start: u64, end: u64): u64 { for (i in start..end) { let sqrt_i = integer_sqrt(i); if (sqrt_i * sqrt_i != i) continue; return i }; abort 1 // No perfect square found } fun multiply_until_overflow(start: u64, end: u64, threshold: u64): u64 { let product = 1; for (i in start..end) { if (product > threshold / i) break; // Prevent overflow product = product * i; }; product } fun integer_sqrt(n: u64): u64 { if (n == 0) return 0; let x = n; let y = (x + 1) / 2; while (y < x) { x = y; y = (x + n / x) / 2; }; x } ``` ## Practical Examples ### Vector Processing ```move fun find_second_largest_index(v: &vector): u64 { let largest = 0; let second_largest = 0; let second_idx = 0; for (i in 0..vector::length(v)) { let val = *vector::borrow(v, i); if (val > largest) { second_largest = largest; second_idx = if (largest > 0) i - 1 else 0; largest = val; } else if (val > second_largest && val < largest) { second_largest = val; second_idx = i; }; }; second_idx } ``` ### Nested Loops ```move fun matrix_diagonal_product(matrix: &vector>): u64 { let product = 1; let size = vector::length(matrix); for (i in 0..size) { let row = vector::borrow(matrix, i); if (i < vector::length(row)) { let diagonal_val = *vector::borrow(row, i); product = product * diagonal_val; }; }; product } ``` ### Fibonacci Calculation ```move fun fibonacci(n: u64): u64 { if (n <= 1) return n; let prev = 0; let curr = 1; for (i in 2..=n) { let next = prev + curr; prev = curr; curr = next; }; curr } ``` ## Performance Considerations 1. **Choose the right loop type**: * Use `for` loops for known ranges * Use `while` loops for condition-based iteration * Use `loop` for infinite loops with explicit breaks 2. **Minimize work inside loops**: ```move // Inefficient: repeated calculation for (i in 0..n) { let expensive_value = expensive_calculation(); process(i, expensive_value); }; // Efficient: calculate once let expensive_value = expensive_calculation(); for (i in 0..n) { process(i, expensive_value); }; ``` 3. **Use early termination** when possible with `break` ## Best Practices 1. **Use descriptive loop variables** - `i`, `j`, `k` for simple counters, meaningful names for complex logic 2. **Avoid infinite loops** unless specifically needed 3. **Use `for` loops for ranges** - more readable than manual while loop counters 4. **Consider vector iteration patterns** - use appropriate vector functions when possible 5. **Handle edge cases** - empty vectors, zero ranges, etc. ## Common Patterns ### Accumulator Pattern ```move fun sum_of_squares(v: &vector): u64 { let sum = 0; for (i in 0..vector::length(v)) { let val = *vector::borrow(v, i); sum = sum + (val * val); }; sum } ``` ### Search Pattern ```move fun find_last_occurrence(v: &vector, target: u64): u64 { let last_index = vector::length(v); // Use length as "not found" indicator for (i in 0..vector::length(v)) { if (*vector::borrow(v, i) == target) { last_index = i; }; }; last_index } ``` ### Filter Pattern ```move fun filter_multiples_of_three(v: &vector): vector { let result = vector::empty(); for (i in 0..vector::length(v)) { let val = *vector::borrow(v, i); if (val % 3 == 0 && val > 0) { vector::push_back(&mut result, val); }; }; result } ``` ## Summary Move provides three loop constructs for repetitive execution and iteration: * **`while`**: Condition-based loops that repeat while a boolean condition is true * **`loop`**: Infinite loops that require explicit `break` statements to exit * **`for`**: Range-based iteration over numeric ranges with `start..end` syntax * **Control flow**: Use `break` to exit loops early and `continue` to skip to next iteration * **Performance**: Choose the right loop type and minimize work inside loop bodies Loops enable efficient iteration patterns while maintaining Move's safety guarantees. # Modules URL: /devs/move-book/modules # Modules Modules are the fundamental building blocks of on-chain logic. They act as libraries that define structs (data types) and functions (behaviors) that operate on those structs. Structs describe the schema of Move’s global storage, determining how data is organized and stored on-chain, while module functions define the rules and permissions for creating, reading, and updating that data. ## Key Characteristics of Modules: * **Encapsulation**: Modules group related structs and functions together * **Reusability**: Modules can be imported and used by other modules * **Security**: Modules enforce access control and data privacy * **Global Storage**: Modules define how data is stored on the blockchain ## Syntax of Modules Modules are defined using the module keyword, followed by the address and identifier of the module. Modules can be declared using named addresses. Named addresses are declared at the source language level in Move syntax and replaced at the bytecode level with the actual address. ```move module
:: { ( | | | | )* } ``` **Module members include:** * **use**: Import declarations (`use
::`) * **friend**: Friend module declarations (`friend
::`) * **struct**: Type definitions (`struct { ... }`) * **function**: Function definitions (`fun () -> { ... }`) * **const**: Constant declarations (`const : = `) The module members can be defined at the source code level in this way: ```move module test::MyModule { // import use std::debug::print; // friend friend 0x1::otherModule; // a struct struct Struct {} // function fun function(_: &Struct) { /* function body */ } // a constant const CONST: u8 = 0; } ``` ## Example of Modules Let's create a simple module that defines a struct and a function to create an instance of that struct. ```move module test::MyModule { use std::debug::print; struct MyStruct { field: u64 } fun create_my_struct(field: u64): MyStruct { let my_struct = MyStruct { field } print(&my_struct); my_struct } } ``` In this example, we define a module named `MyModule` using the named address `test`. The module contains: * A struct named `MyStruct` with a single field of type `u64` * A function named `create_my_struct` that takes a `u64` parameter and returns an instance of `MyStruct` > **Note:** During compilation, the named address `test` is replaced with a real address (like `0x1` in the example below). ```move module 0x1::MyModule { use std::debug::print; struct MyStruct { field: u64 } fun create_my_struct(field: u64): MyStruct { let my_struct = MyStruct { field } print(&my_struct); my_struct } } ``` ## Module Naming Conventions Module names must adhere to the following rules: * Must start with a letter (`a-z` or `A-Z`) * After the first character, can contain underscores (`_`), letters (`a-z`, `A-Z`), or digits (`0-9`) * Cannot be Move keywords (e.g., `move`, `public`, `fun`, etc.) ## Module Addresses Modules are published at specific addresses on the blockchain. There are two ways to specify addresses: ### Named Addresses Used during development for readability: ```move module my_project::token { // module content } ``` ### Literal Addresses The actual hexadecimal addresses used on-chain: ```move module 0x1::token { // module content } ``` Named addresses must be configured in your `Move.toml` file to map to actual addresses during compilation. ## Module Publication Once a module is published to an address: * All its public functions and structs become available to other modules * The module's bytecode is stored permanently on the blockchain * Gas fees are required for publication # Options URL: /devs/move-book/options # Options The `Option` type defines a generic option that represents a value of type `T` that may, or may not, be present. It defined in the standard library and provides a type-safe way to handle optional values. ```move /// Generic type abstraction of a value that may, or may not, be present. /// Can contain a value of either resource or copyable kind. struct Option; ``` ## Option Variants The Option type has two conceptual variants: * **Some**: Contains a value of type `T` * **None**: Represents the absence of a value These variants provide a type-safe way to handle optional data without relying on null pointers or special sentinel values. ## Why Use Option? Consider an application that takes user input where some fields are required and others are optional. For example, a user's middle name is optional. While we could use an empty string to represent the absence of a middle name, it would require extra checks to differentiate between an empty string and a missing middle name. Instead, we can use the Option type: ```move module book::user_registry; use std::string::String; use std::option::Option; /// A struct representing a user record. public struct User has drop { first_name: String, middle_name: Option, last_name: String, } /// Create a new `User` struct with the given fields. public fun register( first_name: String, middle_name: Option, last_name: String, ): User { User { first_name, middle_name, last_name } } ``` In this example, the `middle_name` field is of type `Option`. This makes the optional nature of the field clear and type-safe. ## Creating Option Values ### Creating Empty Options Use `option::none()` to create an empty Option that can contain a value of type `T`: ```move let empty_name: Option> = option::none(); let empty_age: Option = option::none(); let empty_address: Option
= option::none(); ``` ### Creating Non-Empty Options Use `option::some(value)` to create an Option containing a value: ```move let name_opt = option::some(b"Alice"); let age_opt = option::some(25u8); let address_opt = option::some(@0x1); ``` ## Option Operations ### Checking Option State ```move let opt = option::some(b"Alice"); let empty: Option = option::none(); // Check if option contains a value assert!(option::is_some(&opt) == true); assert!(option::is_some(&empty) == false); // Check if option is empty assert!(option::is_none(&opt) == false); assert!(option::is_none(&empty) == true); ``` ### Borrowing Values Return references to the value inside the option: ```move let opt = option::some(b"Alice"); // Borrow immutable reference (aborts if empty) let name_ref = option::borrow(&opt); assert!(*name_ref == b"Alice"); // Borrow with default reference (never aborts) let default_name = b"Unknown"; let name_or_default = option::borrow_with_default(&opt, &default_name); // Borrow mutable reference (aborts if empty) let opt = option::some(100u64); let value_ref = option::borrow_mut(&mut opt); *value_ref ``` ### Extracting and Destroying Values ```move let opt = option::some(b"Alice"); // Extract value, leaving option empty (aborts if empty) let inner = option::extract(&opt); assert!(inner == b"Alice"); assert!(option::is_none(&opt)); // Destroy and return value (aborts if empty) let opt2 = option::some(42u64); let value = option::destroy_some(opt2); assert!(value == 42); // Destroy with default (requires copyable type) let opt3: Option = option::none(); let value_or_default = option::destroy_with_default(opt3, 100); assert!(value_or_default == 100); // Destroy empty option (aborts if contains value) let empty_opt: Option = option::none(); option::destroy_none(empty_opt); ``` ### Advanced Operations ```move // Get value with default (requires copyable type) let opt: Option = option::some(42); let value = option::get_with_default(&opt, 100); assert!(value == 42); let empty_opt: Option = option::none(); let default_value = option::get_with_default(&empty_opt, 100); assert!(default_value == 100); // Fill empty option with value (aborts if already contains value) let empty_opt: Option = option::none(); option::fill(&empty_opt, 42); assert!(option::is_some(&empty_opt)); // Swap value in option (aborts if empty) let opt = option::some(100u64); let old_value = option::swap(&opt, 200); assert!(old_value == 100); assert!(*option::borrow(&opt) == 200); // Check if option contains specific value let opt = option::some(42u64); assert!(option::contains(&opt, &42) == true); assert!(option::contains(&opt, &100) == false); ``` ## Common Usage Patterns ### Safe Value Access with Default ```move public fun get_user_display_name(user: &User): vector { if (option::is_some(&user.middle_name)) { let middle = option::borrow(&user.middle_name); // Construct full name with middle name b"Full name with middle" } else { // Construct name without middle name b"Name without middle" } } ``` ### Option in Function Parameters ```move public fun create_user_with_optional_email( name: vector, email: Option> ): User { User { name, email, verified: false } } // Usage let user1 = create_user_with_optional_email( b"Alice", option::some(b"alice@example.com") ); let user2 = create_user_with_optional_email( b"Bob", option::none() ); ``` ### Option in Return Values ```move public fun find_user_by_id(users: &vector, id: u64): Option { let i = 0; let len = vector::length(users); while (i < len) { let user = vector::borrow(users, i); if (user.id == id) { return option::some(*user) }; i = i + 1; }; option::none() } ``` ## Practical Examples ### Configuration with Defaults ```move module app::config { public struct Config has key { max_users: Option, timeout: Option, } public fun get_max_users_or_default(config: &Config): u64 { option::get_with_default(&config.max_users, 1000) } public fun get_timeout_or_default(config: &Config): u64 { option::get_with_default(&config.timeout, 30) } } ``` ### Safe Mathematical Operations ```move module app::math { public fun safe_divide(a: u64, b: u64): Option { if (b == 0) { option::none() } else { option::some(a / b) } } public fun safe_sqrt(x: u64): Option { // Simplified square root that only works for perfect squares let i = 0; while (i * i <= x) { if (i * i == x) { return option::some(i) }; i = i + 1; }; option::none() } } ``` ## Best Practices ### 1. Use Option for Truly Optional Data ```move // Good: Optional fields that may genuinely be absent public struct User { name: vector, email: Option>, // Optional phone: Option>, // Optional } // Avoid: Using Option for required data public struct User { name: Option>, // Don't do this if name is required } ``` ### 2. Prefer get\_with\_default for Copyable Types ```move // Good: Use get_with_default for simple defaults public fun get_retry_count(config: &Config): u8 { option::get_with_default(&config.retry_count, 3) } // Less ideal: Manual checking public fun get_retry_count_manual(config: &Config): u8 { if (option::is_some(&config.retry_count)) { *option::borrow(&config.retry_count) } else { 3 } } ``` ### 3. Handle Resource Types Carefully ```move // For resource types, you cannot use get_with_default public fun process_resource_option(opt: Option): SomeResource { if (option::is_some(&opt)) { option::extract(&opt) } else { // Must create new resource, cannot use default create_new_resource() } } ``` ## Ownership Option values follow standard Move ownership rules: * **Copy**: If `T` has `copy`, then `Option` has `copy` * **Drop**: If `T` has `drop`, then `Option` has `drop` * **Store**: Option always has `store` ability ```move let opt1 = option::some(10u64); // u64 has copy let opt2 = copy opt1; // Can copy because u64 has copy let opt3 = option::some(vector[1, 2, 3]); // vector doesn't have copy // let opt4 = copy opt3; // Error: cannot copy let opt4 = opt3; // Must move ``` **Important**: Some operations like `get_with_default` and `destroy_with_default` require the element type `T` to have the `copy` ability. ## Summary The Option type in Move provides: * **Type-safe handling** of values that may or may not be present * **Resource-aware operations** that work with both copyable and resource types * **Comprehensive API** for creating, checking, borrowing, and destroying options * **Explicit null handling** without null pointer errors Key operations: * **Creation**: `none()`, `some()` * **Checking**: `is_some()`, `is_none()`, `contains()` * **Access**: `borrow()`, `borrow_mut()`, `borrow_with_default()` * **Extraction**: `extract()`, `get_with_default()`, `swap()` * **Destruction**: `destroy_some()`, `destroy_none()`, `destroy_with_default()` Use Option for: * Optional struct fields * Function parameters that may be omitted * Return values from operations that may fail * Configuration settings with defaults * Safe handling of potentially absent data The Option type is essential for writing robust Move code that handles the absence of values safely and explicitly. # Packages URL: /devs/move-book/package # Packages Move packages provide a structured system for organizing code, managing dependencies, and sharing reusable components across projects. The package system enables modular development with clear dependency relationships and parameterized addresses. **Key capabilities:** * **Code organization**: Structure modules and scripts in a standardized layout * **Dependency management**: Import and version external packages * **Address parameterization**: Configure named addresses for different environments * **Build artifacts**: Generate bytecode, documentation, and source maps * **Reusability**: Share packages across multiple projects Packages are fundamental to professional Move development, enabling teams to build maintainable, modular applications with clear separation of concerns. ## Package Structure ### Directory Layout A Move package follows a standardized directory structure: ``` my_defi_project/ ├── Move.toml # Package manifest (required) ├── sources/ # Move modules (required) │ ├── token.move │ └── pool.move ├── tests/ # Unit tests (optional) │ └── token_tests.move ├── scripts/ # Move scripts (optional) │ └── mint_script.move ├── examples/ # Example code (optional) │ └── basic_usage.move └── doc_templates/ # Documentation templates (optional) ``` **Directory purposes:** * **sources/**: Contains the main Move modules and scripts * **tests/**: Test modules included only in test mode * **examples/**: Tutorial and development code (dev/test mode only) * **doc\_templates/**: Templates for generated documentation ## Package Manifest (Move.toml) ### Basic Configuration The `Move.toml` file defines package metadata and dependencies: ```toml [package] name = "hello_blockchain" version = "1.0.0" authors = [] [addresses] hello_blockchain = "0xc48ec9d15e56d71e035333c7d1d7c3c80d66210a940f6583d63a63608a274ab1" [dev-addresses] [dependencies.AptosFramework] git = "https://github.com/movement-network/aptos-core.git" rev = "movement" subdir = "aptos-move/framework/aptos-framework" [dev-dependencies] ``` **Configuration sections:** * **\[package]**: Basic metadata like name, version, and authors * **\[addresses]**: Named address declarations and assignments * **\[dependencies]**: External package dependencies * **\[dev-dependencies]**: Development-only dependencies ### Dependency Types ```toml [dependencies.AptosFramework] git = "https://github.com/movement-network/aptos-core.git" rev = "movement" subdir = "aptos-move/framework/aptos-framework" # Local dependency [dependencies.Utils] local = "../shared-utils" [dev-dependencies.TestHelpers] local = "../test-utils" ``` **Dependency options:** * **git**: Remote Git repository with revision and subdirectory * **local**: Path to local package directory * **rev**: Specific commit or branch to use * **subdir**: Subdirectory within the repository ## Named Addresses ### Address Declaration Named addresses provide flexible address management across environments: ```move // In Move.toml [addresses] protocol = "_" # Unassigned - can be set by importing packages treasury = "0xTREAS" # Fixed - always this specific address // In Move code module protocol::vault { struct Treasury has key { balance: u64 } public fun get_treasury_address(): address { @treasury } } ``` ### Address Configuration Packages can configure addresses for different environments: ```toml [addresses] hello_blockchain = "0xc48ec9d15e56d71e035333c7d1d7c3c80d66210a940f6583d63a63608a274ab1" protocol = "0x1234567890abcdef" [dev-addresses] protocol = "0xDEV123" treasury = "0xDEVTREAS" ``` **Address configuration:** * **\[addresses]**: Production addresses used in normal builds * **\[dev-addresses]**: Development addresses used with `--dev` flag * **Named addresses**: Can be overridden via CLI with `--named-addresses` ## Build System ### Compilation Process ```bash # Compile the package movement move compile # Compile in development mode movement move compile --dev # Fetch dependencies only movement move compile --fetch-deps-only # Save metadata during compilation movement move compile --save-metadata ``` ### Build Artifacts The build process generates organized artifacts: ``` build/ ├── BuildInfo.yaml ├── bytecode_modules/ │ ├── dependencies/ │ │ └── MoveStdlib/ │ │ └── *.mv │ └── *.mv ├── source_maps/ │ └── *.mvsm └── docs/ └── *.md ``` **Artifact types:** * **bytecode\_modules/**: Compiled Move bytecode (.mv files) * **source\_maps/**: Debug information (.mvsm files) * **docs/**: Generated documentation * **BuildInfo.yaml**: Build metadata and configuration ### Compilation Features Movement's compilation system provides advanced features: * **Dependency management**: Automatic fetching and version resolution * **Optimization levels**: None, default, or extra optimization * **Bytecode versioning**: Specify target bytecode version * **Development mode**: Use dev-addresses and dev-dependencies with `--dev` * **Metadata generation**: Save compilation metadata for debugging * **Standard library overrides**: Choose between mainnet or testnet versions ## Package Development Workflow ### Project Setup ```bash # Create new package movement move init my_project cd my_project # Add dependencies to Move.toml # Implement modules in sources/ # Add tests in tests/ # Compile and test movement move compile movement move test ``` ### Compilation Options ```bash # Fetch dependencies only movement move compile --fetch-deps-only # Skip git dependency updates movement move compile --skip-fetch-latest-git-deps # Override standard library version movement move compile --override-std testnet # Set optimization level movement move compile --optimize default # Specify named addresses movement move compile --named-addresses protocol=0x123 ``` ## Best Practices * Use clear, descriptive package names * Organize related modules in the same package * Pin specific git revisions for production dependencies * Use dev-addresses for testing and development * Specify optimization levels appropriate for your use case * Use `--save-metadata` for debugging and analysis ## Summary Move packages provide structured code organization with standardized directory layouts, dependency management through Move.toml manifests, and parameterized named addresses for flexible deployment. The build system generates bytecode, documentation, and source maps, while Move.lock ensures reproducible builds. Packages enable modular development, code reuse, and professional project management in the Move ecosystem. # References URL: /devs/move-book/references # References Move has two types of references: **immutable** `&` and **mutable** `&mut`. References allow you to borrow values without taking ownership, enabling safe access and modification of data. ## What are References? References are "borrows" that provide temporary access to a value without transferring ownership. Think of them as safe pointers that the compiler tracks to prevent memory errors. | Type | Symbol | Purpose | | ----------------------- | ----------- | ------------------------------------------------------------ | | Immutable reference | `&T` | Read-only access to value of type `T` | | Mutable reference | `&mut T` | Read and write access to value of type `T` | | Field reference | `&e.f` | Create an immutable reference to field f of struct e. | | Mutable field reference | `&mut e.f` | Create a mutable reference to field f of struct e. | | Freeze | `freeze(e)` | Convert the mutable reference e into an immutable reference. | **Key Concept**: References are ephemeral - they exist only during program execution and cannot be stored in structs or global storage. ## Creating References ### Basic Reference Creation ```move fun basic_references() { let x = 42u64; let x_ref: &u64 = &x; // Immutable reference let x_mut_ref: &mut u64 = &mut x; // Mutable reference } ``` ### Field References The `&e.f` and `&mut e.f` operators can be used both to create a new reference into a struct or to extend an existing reference: ```move struct S has drop { f: u64 } fun field_references() { let s = S { f: 10 }; let f_ref1: &u64 = &s.f; // works let s_ref: &S = &s; let f_ref2: &u64 = &s_ref.f; // also works } ``` ### Multiple Field Access A reference expression with multiple fields works as long as both structs are in the same module: ```move struct A has drop { b: B } struct B has drop { c: u64 } fun f(a: &A): &u64 { &a.b.c } ``` ### References to References Finally, note that references to references are not allowed: ```move fun invalid_references() { let x = 7; let y: &u64 = &x; let z: &&u64 = &y; // will not compile } ``` ## Reading and Writing Through References Both mutable and immutable references can be read to produce a copy of the referenced value. Only mutable references can be written. A write `*x = v` discards the value previously stored in `x` and updates it with `v`. Both operations use the C-like `*` syntax. However, note that a read is an expression, whereas a write is a mutation that must occur on the left hand side of an equals. | Syntax | Type | Description | | ---------- | ----------------------------------- | ---------------------------------- | | `*e` | `T` where `e` is `&T` or `&mut T` | Read the value pointed to by `e` | | `*e1 = e2` | `()` where `e1: &mut T` and `e2: T` | Update the value in `e1` with `e2` | ### Reading Through References ```move fun reading_example() { let x = 42u64; let x_ref = &x; let value: u64 = *x_ref; // Read the value (creates a copy) assert!(value == 42, 0); } ``` ### Writing Through References ```move fun writing_example() { let x = 42u64; let x_ref = &mut x; *x_ref = 100; // Write new value assert!(x == 100, 0); } ``` ### Ability Requirements In order for a reference to be read, the underlying type must have the `copy` ability as reading the reference creates a new copy of the value. This rule prevents the copying of resource values: ```move struct Coin has store { value: u64 } fun copy_resource_via_ref_bad(c: Coin) { let c_ref = &c; let counterfeit: Coin = *c_ref; // local `c_ref` of type `Coin` does not have the `copy` ability // pay(c); // pay(counterfeit); } ``` Dually: in order for a reference to be written to, the underlying type must have the `drop` ability as writing to the reference will discard (or "drop") the old value. This rule prevents the destruction of resource values: ```move fun destroy_resource_via_ref_bad(ten_coins: Coin, c: Coin) { let ref = &mut ten_coins; *ref = c; // local `ref` of type `Coin` does not have the `drop` ability } ``` ## Freeze Inference A mutable reference can be used in a context where an immutable reference is expected: ```move fun freeze_example() { let x = 7; let y: &u64 = &mut x; } ``` This works because the under the hood, the compiler inserts `freeze` instructions where they are needed. Here are a few more examples of freeze inference in action: ```move fun takes_immut_returns_immut(x: &u64): &u64 { x } // freeze inference on return value fun takes_mut_returns_immut(x: &mut u64): &u64 { x } fun expression_examples() { let x = 0; let y = 0; takes_immut_returns_immut(&x); // no inference takes_immut_returns_immut(&mut x); // inferred freeze(&mut x) takes_mut_returns_immut(&mut x); // no inference assert!(&x == &mut y, 42); // inferred freeze(&mut y) } fun assignment_examples() { let x = 0; let y = 0; let imm_ref: &u64 = &x; imm_ref = &x; // no inference imm_ref = &mut y; // inferred freeze(&mut y) } ``` ## Subtyping With this freeze inference, the Move type checker can view `&mut T` as a subtype of `&T`. As shown above, this means that anywhere for any expression where a `&T` value is used, a `&mut T` value can also be used. This terminology is used in error messages to concisely indicate that a `&mut T` was needed where a `&T` was supplied. For example: ```move module 0x42::example { fun read_and_assign(store: &mut u64, new_value: &u64) { *store = *new_value } fun subtype_examples() { let x: &u64 = &0; let y: &mut u64 = &mut 1; x = &mut 1; // valid y = &2; // invalid! read_and_assign(y, x); // valid read_and_assign(x, y); // invalid! } } ``` The invalid assignments will yield error messages indicating subtype mismatches: ``` error: ┌── example.move:12:9 ─── │ 12 │ y = &2; // invalid! │ ^ Invalid assignment to local 'y' · 12 │ y = &2; // invalid! │ -- The type: '&{integer}' · 9 │ let y: &mut u64 = &mut 1; │ -------- Is not a subtype of: '&mut u64' ``` **Note**: The only other types currently that have subtyping are tuples. ## Ownership Both mutable and immutable references can always be copied and extended even if there are existing copies or extensions of the same reference: ```move struct S { f: u64 } fun reference_copies(s: &mut S) { let s_copy1 = s; // ok let s_extension = &mut s.f; // also ok let s_copy2 = s; // still ok // ... } ``` This might be surprising for programmers familiar with Rust's ownership system, which would reject the code above. Move's type system is more permissive in its treatment of copies, but equally strict in ensuring unique ownership of mutable references before writes. ## Storage Limitations References and tuples are the only types that cannot be stored as a field value of structs, which also means that they cannot exist in global storage. All references created during program execution will be destroyed when a Move program terminates; they are entirely ephemeral. This invariant is also true for values of types without the `store` ability, but note that references and tuples go a step further by never being allowed in structs in the first place. ```move struct Container { // value_ref: &u64, // Error: references cannot be stored value: u64, // OK: store the value directly } ``` **Important**: References are ephemeral and exist only during program execution. ## Common Use Cases ### Function Parameters ```move fun process_data(data: &vector) { // Read-only access to vector without taking ownership let length = vector::length(data); } fun modify_data(data: &mut vector) { // Can modify the vector vector::push_back(data, 42); } ``` ### Struct Field Access ```move struct Account { balance: u64 } fun check_balance(account: &Account): u64 { account.balance // Access field through reference } fun deposit(account: &mut Account, amount: u64) { account.balance = account.balance + amount; } ``` ## Summary References in Move provide: * **Safe borrowing** without ownership transfer * **Two types**: immutable `&T` for reading, mutable `&mut T` for reading and writing * **Automatic conversions** from mutable to immutable references when needed * **Ephemeral nature** - exist only during execution, cannot be stored * **Resource safety** by enforcing ability requirements References are essential for efficient data access without transferring ownership in Move. # Scripts URL: /devs/move-book/scripts # Scripts Scripts in Move are executable entry points similar to a `main` function in conventional programming languages. They serve as ephemeral code snippets that orchestrate interactions with published modules and facilitate updates to global storage. ## Overview Scripts are **not published** to global storage, making them temporary execution units that primarily invoke functions from published modules. They provide a way to execute Move code without permanently storing it on the blockchain. ## Script Structure A script follows a specific structural pattern that must be adhered to: ```move script { * * fun <[type parameters: constraint]*>([identifier: type]*) } ``` ### Structure Requirements The script block must follow this exact order: 1. **Use declarations** - All import statements must come first 2. **Constants** - Any constant definitions follow the imports 3. **Main function** - A single function declaration that serves as the entry point ### Main Function Characteristics * Can have **any name** (doesn't need to be called `main`) * Must be the **only function** in the script block * Can accept **any number of arguments** * **Must not return a value** ## Example Here's a complete example demonstrating all components of a valid script: ```move script { // Import use std::debug::print; // A constant const CONST: u8 = 0; // Main function (entry point) fun main(value: u64) { let result = value + (CONST as u64); print(&result); } } ``` ## What's NOT ALLOWED in Scripts The following code demonstrates elements that are **forbidden** in scripts and would cause compilation errors: ```move // This is INVALID in a script - for illustration only script { // Imports are ALLOWED use std::debug::print; // Friend declarations are NOT ALLOWED friend 0x1::otherModule; // Struct definitions are NOT ALLOWED public struct Struct {} // Additional function definitions are NOT ALLOWED fun function(_: &Struct) { /* function body */ } // Constants are ALLOWED const CONST: u8 = 0; // Only ONE main function is ALLOWED fun main() { print(&CONST); } } ``` ## Limitations Scripts have intentionally limited capabilities to maintain security and clarity: * Cannot declare `friend` functions * Cannot define `struct` types * Cannot directly access global storage * Can only invoke functions from published modules ## Use Cases Scripts are primarily used for: * **Transaction execution** - Orchestrating complex operations across multiple modules * **Testing and debugging** - Running temporary code without publishing # Signer URL: /devs/move-book/signer # Signer Type `signer` is a built-in Move resource type that represents a capability allowing the holder to act on behalf of a particular address. It's a fundamental security primitive that ensures only authenticated users can perform operations on their accounts. ## What is a Signer? A `signer` is conceptually similar to a cryptographic signature or authentication token: ```move // Conceptual representation (actual implementation is native) struct signer has drop { a: address } ``` **Key Concept**: A `signer` proves that the holder has the authority to act on behalf of a specific address, similar to how a Unix UID represents an authenticated user. ## Signer vs Address Understanding the difference between `signer` and `address` is crucial: | Type | Creation | Purpose | Security | | --------- | ------------------------ | --------------------- | -------------------------- | | `address` | Can be created by anyone | Represents a location | No authentication required | | `signer` | Only created by the VM | Proves authentication | Cannot be forged | ### Address Creation Anyone can create any address value without special permission: ```move fun address_examples() { let a1 = @0x1; let a2 = @0x2; let a3 = @0xABCD; // ... any address can be created } ``` ### Signer Creation However, signer values are special because they cannot be created via literals or instructions--only by the Move VM. Before the VM runs a script with parameters of type signer, it will automatically create signer values and pass them into the script: ```move script { use std::signer; fun main(s: signer) { assert!(signer::address_of(&s) == @0x42, 0); } } ``` **Security**: This function will abort if sent from any address other than `0x42`, providing authentication. ## Using Signers in Functions Functions can accept signers as parameters to perform authenticated operations: ```move use std::signer; public entry fun create_account(account: &signer) { let addr = signer::address_of(account); // Only the account owner can call this for their address // ... account creation logic } ``` **Multi-Signer Support**: Functions can accept multiple signers when operations require multiple parties to authenticate. ## Signer Operators The `std::signer` standard library provides utility functions: | Function | Signature | Description | | ---------------- | --------------------- | ------------------------------------------ | | `address_of` | `(&signer): address` | Returns the address wrapped by the signer | | `borrow_address` | `(&signer): &address` | Returns a reference to the wrapped address | ### Using Signer Functions ```move use std::signer; public fun example_usage(account: &signer) { // Get the address from a signer let addr: address = signer::address_of(account); // Get a reference to the address let addr_ref: &address = signer::borrow_address(account); // Use the address for operations assert!(addr == @0x123, 0); } ``` ## Global Storage Operations The `signer` is required for publishing resources to global storage: ```move use std::signer; struct UserProfile has key { name: vector, } public fun create_profile(account: &signer, name: vector) { let profile = UserProfile { name }; // move_to requires a signer to ensure only the account owner // can publish resources under their address move_to(account, profile); } ``` **Security Guarantee**: Only the authenticated user can publish resources under their address using `move_to(&signer, T)`. ## Ownership and Abilities Unlike simple scalar values, signer values are **not copyable**: ```move public fun ownership_example(s: &signer) { // Signers are NOT copyable // let s_copy = *s; // Error: signer does not have copy ability // Must use references to pass signers around let addr = signer::address_of(s); // OK: borrowing } ``` **Important**: Always use references (`&signer`) when passing signers to functions since they cannot be copied. ## Summary The `signer` type in Move provides: * **Authentication** - proves the holder can act for a specific address * **Security** - cannot be forged, only created by the VM * **Access Control** - required for publishing resources and state changes * **Multi-party Operations** - enables atomic transactions requiring multiple signers * **Non-copyable** - must be passed by reference to maintain security Signers are essential for secure Move programming, ensuring that only authenticated users can perform operations on their accounts and resources. # Structs and Resources URL: /devs/move-book/structsAndResources # Structs and Resources Structs are custom data types that contain typed fields and form the foundation of Move's type system. They can hold any non-reference values, including other structs, and are essential for modeling both simple data and complex data structures. Move structs have unique **ownership semantics** by default: * **Linear**: Values cannot be copied - they must be explicitly moved or transferred * **Ephemeral**: Values cannot be dropped - they must be consumed or destructured * **Private**: Values cannot be stored in global storage without explicit abilities When structs have these default properties, we call them **resources** - perfect for representing valuable assets like tokens, NFTs, or account balances where duplication or accidental loss would be problematic. You can grant structs **abilities** (`copy`, `drop`, `store`, `key`) to relax these restrictions and enable different behaviors based on your use case. ## Defining Structs Structs are declared within modules using the `struct` keyword. Each struct defines a custom type with named, typed fields that can hold data: ```move module 0x2::game { struct Player { level: u64, active: bool } // struct with fields struct Team {} // empty struct struct Match { player: Player, } // nested struct (trailing comma allowed) } ``` **Key rules for struct definitions:** * Must be declared inside a module * Can contain zero or more typed fields * Fields can be primitive types, other structs, or generic types * Trailing commas are permitted for better code formatting ### Recursive Structs Move prevents recursive struct definitions to ensure memory safety and avoid infinite data structures: ```move struct Node { next: Node } // ERROR: cyclic data struct Tree { left: Tree } // ERROR: cyclic data ``` **Why this restriction exists:** * Prevents infinite memory allocation * Ensures predictable struct sizes at compile time * Maintains Move's safety guarantees ### Struct Abilities Abilities control what operations are permitted on struct values. By default, structs have **no abilities**, making them resources. You can grant specific abilities using the `has` keyword: ```move module 0x2::game { struct Player has copy, drop { level: u64, active: bool } // copyable and droppable struct Token has store { amount: u64 } // storable only struct Account has key { balance: u64 } // global storage capable struct Resource { value: u64 } // no abilities (pure resource) } ``` **The four abilities:** * **`copy`**: Enables value duplication with `copy` operator * **`drop`**: Allows automatic cleanup when values go out of scope * **`store`**: Permits storage inside other structs and global storage * **`key`**: Enables top-level global storage operations **Tip**: Only grant abilities your struct actually needs. Resources (no abilities) are perfect for valuable assets. ## Naming Conventions Move enforces specific naming rules for structs to maintain consistency and reserve space for future language features: **Naming Rules:** * **First character**: Must be uppercase letter (A-Z) * **Subsequent characters**: Letters (a-z, A-Z), digits (0-9), underscores (\_) * **Case style**: PascalCase is the recommended convention ```move // Valid struct names struct Player {} struct GameState {} struct NFT_Metadata {} struct Account2024 {} // Invalid struct names struct player {} // starts with lowercase struct _Config {} // starts with underscore struct 2Player {} // starts with digit ``` **Best Practices:** * Use descriptive, meaningful names * Follow PascalCase convention * Avoid abbreviations when clarity matters * Consider the domain context (e.g., `UserProfile` vs `Profile`) ## Working with Structs ### Creating Struct Values Struct values are created using **struct literal syntax** - specify the struct name followed by field values in braces: ```move module 0x2::game { struct Player has drop { level: u64, active: bool } struct Match has drop { player: Player, round: u64 } fun create_game_data() { // Basic struct creation let player = Player { level: 1, active: true }; // Nested struct creation let match = Match { player: Player { level: 5, active: true }, round: 1 }; // Using existing values let another_match = Match { player, round: 2 }; } } ``` **Important notes:** * All fields must be provided (no default values) * Field order doesn't matter in struct literals * Values are moved into the struct (ownership transfer) #### Field Name Punning **Field punning** allows shorthand syntax when a local variable name matches the field name: ```move fun create_player_data(level: u64, active: bool) { // Verbose syntax let player1 = Player { level: level, active: active }; // Punning syntax (equivalent) let player2 = Player { level, active }; // Mixed usage let player3 = Player { level, active: true }; } ``` **When to use punning:** * SUCCESS: When variable names naturally match field names * SUCCESS: In constructor functions with matching parameters * ERROR: Avoid when it reduces code clarity ### Destructuring Structs Struct values are **consumed** (destroyed) through pattern matching, which extracts field values and transfers ownership: **Why destructuring matters:** * Only way to access fields of structs without abilities * Ensures resources are properly handled * Enables clean data extraction patterns ```move module 0x2::game { struct Player { level: u64, active: bool } struct Team { player: Player } struct Tournament {} // Basic destructuring with field renaming fun extract_player_data() { let player = Player { level: 5, active: true }; let Player { level, active: is_active } = player; // ^ field punning ^ field renaming // Creates two new variables: // level: u64 = 5 // is_active: bool = true } // Partial destructuring with wildcards fun get_player_level() { let player = Player { level: 10, active: false }; let Player { level, active: _ } = player; // ignore 'active' field // Only 'level' is bound, 'active' is discarded } // Assignment to existing variables fun update_from_player() { let level: u64; let active: bool; // Destructure directly into existing variables Player { level, active } = Player { level: 8, active: true }; } // Destructuring references (non-consuming) fun read_player_data() { let player = Player { level: 3, active: true }; let Player { level, active } = &player; // borrow, don't consume // Creates references: // level: &u64 // active: &bool // 'player' is still available for use } // Destructuring mutable references fun modify_player_data() { let mut player = Player { level: 1, active: false }; let Player { level, active } = &mut player; // Creates mutable references: // level: &mut u64 // active: &mut bool *level = 99; *active = true; } // Nested destructuring fun extract_nested_data() { let team = Team { player: Player { level: 7, active: true } }; let Team { player: Player { level, active } } = team; // ^ nested pattern matching // Directly extracts from nested struct: // level: u64 = 7 // active: bool = true } // Empty struct destructuring fun handle_tournament() { let tournament = Tournament {}; let Tournament {} = tournament; // must still destructure empty structs } } ``` ## Borrowing and References Borrowing creates references to structs and fields without transferring ownership. This is essential for reading data without consuming resources: ```move fun demonstrate_borrowing() { let mut player = Player { level: 3, active: true }; // Borrow entire struct (immutable) let player_ref: &Player = &player; let current_level = player_ref.level; // read through reference // Borrow specific field (immutable) let level_ref: &u64 = &player.level; let level_value = *level_ref; // dereference to get value // Borrow field (mutable) let level_mut: &mut u64 = &mut player.level; *level_mut = 42; // modify through mutable reference // player is still accessible after borrowing let final_level = player.level; // final_level = 42 } ``` **Key borrowing concepts:** * **Immutable borrow (`&`)**: Read-only access, multiple borrows allowed * **Mutable borrow (`&mut`)**: Read-write access, exclusive borrow * **Field borrowing**: Can borrow individual fields directly * **Non-consuming**: Original value remains available after borrowing ### Advanced Borrowing Patterns **Nested field borrowing** allows direct access to deeply nested data: ```move fun nested_borrowing_examples() { let player = Player { level: 3, active: true }; let team = Team { player }; // Direct nested field access let level_ref = &team.player.level; let active_ref = &team.player.active; // Mutable nested borrowing let mut team2 = Team { player: Player { level: 1, active: false } }; let level_mut = &mut team2.player.level; *level_mut = 50; } ``` **Borrowing through references** - you can chain reference operations: ```move fun chained_borrowing() { let player = Player { level: 5, active: true }; let player_ref = &player; // These are equivalent: let level1 = &player.level; // direct field borrow let level2 = &player_ref.level; // borrow through reference // Both create &u64 references to the same field } ``` **Borrowing rules:** * Can borrow nested fields arbitrarily deep * Reference chains are automatically dereferenced * Mutable borrows require mutable access at every level ## Field Access Patterns ### Reading Field Values Move provides multiple ways to read field values depending on the struct's abilities and your needs: ```move fun reading_examples() { let player = Player { level: 3, active: true }; let team = Team { player: copy player }; // requires 'copy' ability // Method 1: Explicit borrow and dereference let level: u64 = *&player.level; let active: bool = *&player.active; // Method 2: Copy entire struct (if it has 'copy' ability) let player_copy: Player = *&team.player; // Method 3: Borrow for temporary access let level_ref = &player.level; let level_value = *level_ref; } ``` **When to use each method:** * **Borrow + dereference (`*&`)**: When you need the value but struct lacks `copy` * **Direct copy**: When struct has `copy` ability and you need ownership * **Reference**: When you only need temporary access ### Implicit Field Access For **primitive types** (integers, booleans, addresses), Move allows direct field access without explicit borrowing: ```move fun implicit_access_examples() { let player = Player { level: 3, active: true }; // Implicit copying for primitive fields let level = player.level; // automatically copies u64 let active = player.active; // automatically copies bool // Works with nested primitive fields let match = Match { player: Player { level: 5, active: true }, round: 1 }; let nested_level = match.player.level; // copies nested u64 let round_num = match.round; // copies u64 } ``` **What gets implicit copying:** * SUCCESS: Primitive types: `u8`, `u16`, `u32`, `u64`, `u128`, `u256`, `bool`, `address` * ERROR: Complex types: structs, vectors, references * ERROR: Types without `copy` ability **Chaining field access:** ```move // Multiple levels of nesting work automatically let deep_value = game.tournament.match.player.level; ``` ### Explicit Copying Requirements For **complex types** (structs, vectors), Move requires explicit syntax to make copying operations visible and intentional: ```move fun explicit_copying_examples() { let player = Player { level: 3, active: true }; let team = Team { player }; // SUCCESS: Explicit copy syntax required for structs let player_copy: Player = *&team.player; // ERROR: This would fail - implicit copying not allowed // let player_copy2: Player = team.player; // SUCCESS: For vectors, also need explicit copying let scores = vector[100, 200, 300]; let scores_copy = *&scores; // explicit copy required } ``` **Why explicit copying is required:** * **Performance awareness**: Copying large structs/vectors can be expensive * **Intentional design**: Forces developers to think about copy costs * **Code clarity**: Makes copying operations visible to code reviewers * **Memory safety**: Prevents accidental expensive operations **Alternatives to copying:** * Use references (`&`) for read-only access * Use mutable references (`&mut`) for modifications * Restructure code to avoid unnecessary copies ### Modifying Fields Field modification uses dot notation and requires mutable access to the containing struct: ```move fun field_modification_examples() { let mut player = Player { level: 3, active: true }; // Direct field modification player.level = 42; player.active = !player.active; // Nested field modification let mut team = Team { player }; team.player.level = 52; // modify nested field // Replace entire nested struct team.player = Player { level: 100, active: true }; // Modification through mutable reference let player_ref = &mut team.player; player_ref.level = player_ref.level + 10; } ``` **Field modification rules:** * Struct must be declared with `mut` for modifications * Can modify fields at any nesting level * Can replace entire field values * Works through mutable references (`&mut`) **Common modification patterns:** ```move // Increment/update patterns player.level += 1; player.active = check_player_status(); // Conditional updates if (player.level < 10) { player.level = player.level * 2; }; ``` ## Module Privacy and Access Control Move enforces strict **module-level encapsulation** for struct operations, ensuring data integrity and controlled access: **Private operations (module-only):** * **Creating structs**: Only the defining module can construct struct values * **Destructuring structs**: Only the defining module can pattern match and extract fields * **Field access**: Only the defining module can directly read/write fields **Public operations (cross-module):** * **Type usage**: Other modules can use the struct type in function signatures * **Value passing**: Struct values can be passed between modules * **Reference creation**: Can create references to structs from other modules ```move module 0x2::bank { struct Account { balance: u64 } // private fields // Public constructor public fun create_account(initial_balance: u64): Account { Account { balance: initial_balance } // SUCCESS: allowed in defining module } // Public accessor public fun get_balance(account: &Account): u64 { account.balance // SUCCESS: field access allowed in defining module } } module 0x2::user { use 0x2::bank; fun use_account() { let account = bank::create_account(100); // SUCCESS: use public constructor let balance = bank::get_balance(&account); // SUCCESS: use public accessor // ERROR: These would fail: // let Account { balance } = account; // can't destructure // let direct_balance = account.balance; // can't access fields } } ``` ### Cross-Module Usage Patterns While struct internals are private, the **type itself** is public and can be used across modules: ```move // game.move module 0x2::game { struct Player has drop { level: u64 } public fun new_player(): Player { Player { level: 1 } } } ``` ```move // tournament.move module 0x2::tournament { use 0x2::game; struct Roster has drop { player: game::Player } fun f1(player: game::Player) { let level = player.level; // ^ error! cannot access fields of `player` here } fun f2() { let roster = Roster { player: game::new_player() }; } } ``` Note that structs do not have visibility modifiers (e.g., `public` or `private`). ## Resource Ownership Model Move's **ownership system** ensures safe handling of valuable digital assets through strict compile-time checks: **Default struct behavior (resources):** * **Cannot be copied**: Prevents accidental duplication of valuable assets * **Cannot be dropped**: Prevents accidental loss or destruction * **Must be consumed**: All values must be explicitly handled This model is perfect for representing digital assets like tokens, NFTs, or account balances where duplication or loss would be catastrophic. ```move module 0x2::inventory { struct Item { rarity: u64 } public fun copying_resource() { let item = Item { rarity: 100 }; let item_copy = copy item; // error! 'copy'-ing requires the 'copy' ability let item_ref = &item; let another_copy = *item_ref // error! dereference requires the 'copy' ability } public fun destroying_resource1() { let item = Item { rarity: 100 }; // error! when the function returns, item still contains a value. // This destruction requires the 'drop' ability } public fun destroying_resource2(i: &mut Item) { *i = Item { rarity: 100 } // error! // destroying the old value via a write requires the 'drop' ability } } ``` ### Manual Resource Destruction To fix the second example (`fun destroying_resource1`), you would need to manually "unpack" the resource: ```move module 0x2::inventory { struct Item { rarity: u64 } public fun destroying_resource1_fixed() { let item = Item { rarity: 100 }; let Item { rarity: _ } = item; } } ``` Recall that you are only able to deconstruct a resource within the module in which it is defined. This can be leveraged to enforce certain invariants in a system, for example, conservation of money. ### Adding Copy and Drop Abilities If on the other hand, your struct does not represent something valuable, you can add the abilities `copy` and `drop` to get a struct value that might feel more familiar from other programming languages: ```move module 0x2::collectible { struct Card has copy, drop { power: u64 } public fun run() { let card = Card { power: 100 }; let card_copy = copy card; // ^ this code copies card, whereas `let x = card` or // `let x = move card` both move card let power = card.power; // power = 100 let power_copy = card_copy.power; // power_copy = 100 // both card and card_copy are implicitly discarded when the function returns } } ``` ## Storing Resources in Global Storage Only structs with the `key` ability can be saved directly in persistent global storage. All values stored within those `key` structs must have the `store` ability. See the ability and global storage chapters for more detail. ## Examples Here are two short examples of how you might use structs to represent valuable data (in the case of Coin) or more classical data (in the case of Point and Circle). ### Example 1: Token ```move module 0x2::token { // We do not want the Token to be copied because that would be duplicating this "asset", // so we do not give the struct the 'copy' ability. // Similarly, we do not want programmers to destroy tokens, so we do not give the struct the // 'drop' ability. // However, we *want* users of the modules to be able to store this token in persistent global // storage, so we grant the struct the 'store' ability. This struct will only be inside of // other resources inside of global storage, so we do not give the struct the 'key' ability. struct Token has store { amount: u64, } public fun create(amount: u64): Token { // You would want to gate this function with some form of access control to prevent // anyone using this module from creating an infinite amount of tokens. Token { amount } } public fun extract(token: &mut Token, amount: u64): Token { assert!(token.amount >= amount, 1000); token.amount = token.amount - amount; Token { amount } } public fun combine(token: &mut Token, other: Token) { let Token { amount } = other; token.amount = token.amount + amount; } public fun divide(token: Token, amount: u64): (Token, Token) { let other = extract(&mut token, amount); (token, other) } public fun join(token1: Token, token2: Token): Token { combine(&mut token1, token2); token1 } public fun burn_empty(token: Token) { let Token { amount } = token; assert!(amount == 0, 1001); } } ``` ### Example 2: Coordinates ```move module 0x2::location { struct Position has copy, drop, store { latitude: u64, longitude: u64, } public fun create(latitude: u64, longitude: u64): Position { Position { latitude, longitude } } public fun get_latitude(pos: &Position): u64 { pos.latitude } public fun get_longitude(pos: &Position): u64 { pos.longitude } fun abs_difference(a: u64, b: u64): u64 { if (a < b) { b - a } else { a - b } } public fun distance_squared(pos1: &Position, pos2: &Position): u64 { let lat_diff = abs_difference(pos1.latitude, pos2.latitude); let lon_diff = abs_difference(pos1.longitude, pos2.longitude); lat_diff*lat_diff + lon_diff*lon_diff } } ``` ```move module 0x2::region { use 0x2::location::{Self, Position}; struct Area has copy, drop, store { center: Position, range: u64, } public fun create(center: Position, range: u64): Area { Area { center, range } } public fun overlaps(area1: &Area, area2: &Area): bool { let distance = location::distance_squared(&area1.center, &area2.center); let r1 = area1.range; let r2 = area2.range; distance*distance <= r1*r1 + 2*r1*r2 + r2*r2 } } ``` ## Summary Structs are user-defined data types that enable safe resource modeling in Move: * **Definition**: Custom data structures with typed fields, defined within modules * **Resources**: Linear values that cannot be copied or dropped by default - perfect for valuable assets * **Abilities**: Control struct behavior with `copy`, `drop`, `store`, and `key` abilities * **Privacy**: Struct operations are module-private; public APIs required for external access * **Patterns**: Use appropriate abilities for your use case (value types vs resources vs global storage) Structs provide the foundation for safe, ownership-based programming in Move. ``` ``` # Tuple and Unit URL: /devs/move-book/tupleAndUnit # Tuple and Unit Types Tuples in Move are used to support multiple return values from functions and temporary grouping of values. Move's tuple support is limited compared to other programming languages. These expressions do not result in a concrete value at runtime (there are no tuples in the bytecode), and as a result they are very limited: * They can only appear in expressions (usually in the return position for a function). * They cannot be bound to local variables. * They cannot be stored in structs. * Tuple types cannot be used to instantiate generics. ## Tuple Syntax Tuples are created by a comma-separated list of expressions inside parentheses: ```move // Creating tuples (compile-time only) (10, true) (1, 2, 3) (@0x1, b"hello", 42u8) ``` **Important**: Tuples cannot be assigned to variables or stored - they can only be used in function returns and immediate destructuring. ## Unit Type The unit type `()` represents "no value" and is used when a function doesn't return anything. Unit is similar to `void` in other programming languages. The following three functions are equivalent: ```move fun do_something(): () { // Function body // Implicitly returns () } // Equivalent to: fun do_something() { // Function body } ``` ## Function Returns Tuples are primarily used for returning multiple values from functions: ```move fun get_name_and_age(): (vector, u8) { (b"Alice", 25) } fun get_coordinates(): (u64, u64) { (100, 200) } fun multiple_values(): (bool, u64, address) { (true, 42, @0x1) } ``` ## Destructuring The main operation for tuples is destructuring - extracting individual values: ```move fun example() { // Destructure tuple from function return let (name, age) = get_name_and_age(); let (x, y) = get_coordinates(); let (flag, number, addr) = multiple_values(); // Use the extracted values assert!(age == 25, 0); assert!(x == 100, 1); } ``` ### Partial Destructuring You can ignore values you don't need using underscore: ```move fun partial_example() { let (name, _) = get_name_and_age(); // Ignore age let (_, y) = get_coordinates(); // Ignore x coordinate let (flag, _, _) = multiple_values(); // Only use the boolean } ``` ## Limitations Tuples in Move have several important limitations: * **No storage**: Cannot be stored in global storage or struct fields ```move module examples::no_storage { struct Holder { field: (u64, bool) // Error: tuple type `(u64, bool)` is not allowed as a field type } } ``` * **No variables**: Cannot assign tuples to variables ```move fun invalid_local() { let t: (u64, bool) = (1, true); // Error: tuple type `(u64, bool)` is not allowed as a local variable type } ``` * **Compile-time only**: Exist only during compilation, not at runtime * **No operations**: Cannot perform operations on tuples directly ```move fun invalid_operations() { let x = (1, 2) + (3, 4); // Error: cannot use `(integer, integer)` with an operator which expects a value of type `integer` } ``` * **No Nested tuple**: Cannot be nested ```move fun no_nested_tuples() { let t = ((1, 2), 3); // Error: nested tuples are not allowed } ``` ## Practical Examples Here are common tuple usage patterns: ```move // Swapping values using tuples fun swap_values(x: u64, y: u64): (u64, u64) { (y, x) } // Multiple calculations fun calculate_stats(numbers: &vector): (u64, u64, u64) { let sum = 0; let min = 0; let max = 0; // ... calculation logic ... (sum, min, max) } // Error handling pattern fun divide_safe(a: u64, b: u64): (bool, u64) { if (b == 0) { (false, 0) // Error case } else { (true, a / b) // Success case } } fun use_divide() { let (success, result) = divide_safe(10, 2); if (success) { // Use result } } ``` ## Subtyping Along with references, tuples are the only types that have subtyping in Move. Tuples have subtyping only in the sense that they are covariant with references. This means that if you have a tuple containing references, you can use it where a tuple with less restrictive reference types is expected: ```move let x: &u64 = &0; let y: &mut u64 = &mut 1; // (&u64, &mut u64) is a subtype of (&u64, &u64) // since &mut u64 is a subtype of &u64 let (a, b): (&u64, &u64) = (x, y); // (&mut u64, &mut u64) is a subtype of (&u64, &u64) // since &mut u64 is a subtype of &u64 let (c, d): (&u64, &u64) = (y, y); // Error! (&u64, &mut u64) is NOT a subtype of (&mut u64, &mut u64) // since &u64 is NOT a subtype of &mut u64 // let (e, f): (&mut u64, &mut u64) = (x, y); ``` This subtyping relationship follows the same rules as reference subtyping: * `&mut T` is a subtype of `&T` (you can use a mutable reference where an immutable one is expected) * `&T` is NOT a subtype of `&mut T` (you cannot use an immutable reference where a mutable one is expected) ## Ownership Tuples themselves don't have ownership semantics since they cannot be stored. However, the values within tuples follow normal Move ownership rules: * Values are moved into tuples when created * Values are moved out when destructured * Copy types can be copied, non-copy types are moved ```move fun ownership_example() { let x = 10u64; // Copy type let v = vector[1, 2, 3]; // Non-copy type let (a, b) = (x, v); // x is copied, v is moved // x can still be used, but v cannot } ``` ## Summary Tuples in Move are: * **Compile-time constructs** for grouping values temporarily * **Used primarily** for multiple function returns * **Destructured immediately** - cannot be stored as variables * **Limited in scope** - no runtime existence or operations * **Useful for** returning multiple values and swapping The unit type `()` represents the absence of a value and is the default return type for functions that don't explicitly return anything. # Type Abilities URL: /devs/move-book/typeAbilities # Type Abilities Abilities are a typing feature in Move that control what actions are permissible for values of a given type. This system grants fine-grained control over the "linear" typing behavior of values, as well as if and how values are used in global storage. This is implemented by gating access to certain bytecode instructions so that for a value to be used with the bytecode instruction, it must have the ability required (if one is required at all—not every instruction is gated by an ability). ## The Four Abilities The four abilities are: | Ability | Description | | ------- | ----------------------------------------------------------------------------------- | | `copy` | Allows values of types with this ability to be copied | | `drop` | Allows values of types with this ability to be popped/dropped | | `store` | Allows values of types with this ability to exist inside a struct in global storage | | `key` | Allows the type to serve as a key for global storage operations | ## Copy The `copy` ability allows values of types with that ability to be copied. It gates the ability to copy values out of local variables with the `copy` operator and to copy values via references with dereference `*e`. ```move struct Copyable has copy { value: u64 } fun example() { let x = Copyable { value: 10 }; let y = copy x; // Valid: Copyable has 'copy' let z = *&x; // Valid: dereference requires 'copy' } ``` **Important rule**: If a value has `copy`, all values contained inside of that value have `copy`. ## Drop The `drop` ability allows values of types with that ability to be dropped. By "dropped," we mean that value is not transferred and is effectively destroyed as the Move program executes. As such, this ability gates the ability to ignore values in a multitude of locations, including: * Not using the value in a local variable or parameter * Not using the value in a sequence via `;` * Overwriting values in variables in assignments * Overwriting values via references when writing `*e1 = e2` ```move struct Droppable has drop { value: u64 } fun example() { let x = Droppable { value: 10 }; // Valid: x is automatically dropped at end of scope Droppable { value: 20 }; // Valid: value is ignored/dropped let mut y = Droppable { value: 30 }; y = Droppable { value: 40 }; // Valid: old value is dropped } ``` **Important rule**: If a value has `drop`, all values contained inside of that value have `drop`. ## Store The `store` ability allows values of types with this ability to exist inside of a struct (resource) in global storage, but not necessarily as a top-level resource in global storage. This is the only ability that does not directly gate an operation. Instead it gates the existence in global storage when used in tandem with `key`. ```move struct Storable has store { value: u64 } struct Container has key { item: Storable // Valid: Storable has 'store' } ``` **Important rule**: If a value has `store`, all values contained inside of that value have `store`. ## Key The `key` ability allows the type to serve as a key for global storage operations. It gates all global storage operations, so in order for a type to be used with `move_to`, `borrow_global`, `move_from`, etc., the type must have the `key` ability. Note that the operations still must be used in the module where the key type is defined (in a sense, the operations are private to the defining module). ```move struct Resource has key { value: u64 } public fun create_resource(account: &signer) { move_to(account, Resource { value: 42 }); // Valid: Resource has 'key' } ``` **Important rule**: If a value has `key`, all values contained inside of that value have `store`. This is the only ability with this sort of asymmetry. ## Builtin Types Most primitive, builtin types have `copy`, `drop`, and `store` with the exception of `signer`, which just has `drop`: ### Primitive Types * `bool`, `u8`, `u16`, `u32`, `u64`, `u128`, `u256`, and `address` all have `copy`, `drop`, and `store` * `signer` has `drop` * Cannot be copied and cannot be put into global storage ### Collection Types * `vector` may have `copy`, `drop`, and `store` depending on the abilities of `T` * See Conditional Abilities and Generic Types for more details ### Reference Types * Immutable references `&` and mutable references `&mut` both have `copy` and `drop` * This refers to copying and dropping the reference itself, not what they refer to * References cannot appear in global storage, hence they do not have `store` ### Global Storage * None of the primitive types have `key`, meaning none of them can be used directly with the global storage operations ## Annotating Structs To declare that a struct has an ability, it is declared with `has ` after the struct name but before the fields. For example: ```move struct Ignorable has drop { f: u64 } struct Pair has copy, drop, store { x: u64, y: u64 } ``` In this case: `Ignorable` has the `drop` ability. `Pair` has `copy`, `drop`, and `store`. ### Field Requirements All of these abilities have strong guarantees over these gated operations. The operation can be performed on the value only if it has that ability; even if the value is deeply nested inside of some other collection! As such: when declaring a struct's abilities, certain requirements are placed on the fields. All fields must satisfy these constraints. These rules are necessary so that structs satisfy the reachability rules for the abilities given above. If a struct is declared with the ability... * `copy`, all fields must have `copy` * `drop`, all fields must have `drop` * `store`, all fields must have `store` * `key`, all fields must have `store` `key` is the only ability currently that doesn't require itself. ### Examples of Field Requirements ```move // A struct without any abilities struct NoAbilities {} struct WantsCopy has copy { f: NoAbilities, // ERROR 'NoAbilities' does not have 'copy' } ``` And similarly: ```move // A struct without any abilities struct NoAbilities {} struct MyResource has key { f: NoAbilities, // Error 'NoAbilities' does not have 'store' } ``` ## Conditional Abilities and Generic Types When abilities are annotated on a generic type, not all instances of that type are guaranteed to have that ability. Consider this struct declaration: ```move struct Cup has copy, drop, store, key { item: T } ``` It might be very helpful if `Cup` could hold any type, regardless of its abilities. The type system can see the type parameter, so it should be able to remove abilities from `Cup` if it sees a type parameter that would violate the guarantees for that ability. This behavior might sound a bit confusing at first, but it might be more understandable if we think about collection types. We could consider the builtin type `vector` to have the following type declaration: ```move vector has copy, drop, store; ``` We want vectors to work with any type. We don't want separate vector types for different abilities. So what are the rules we would want? Precisely the same that we would want with the field rules above. So, it would be safe to copy a vector value only if the inner elements can be copied. It would be safe to ignore a vector value only if the inner elements can be ignored/dropped. And, it would be safe to put a vector in global storage only if the inner elements can be in global storage. ### Conditional Ability Rules To have this extra expressiveness, a type might not have all the abilities it was declared with depending on the instantiation of that type; instead, the abilities a type will have depends on both its declaration and its type arguments. For any type, type parameters are pessimistically assumed to be used inside of the struct, so the abilities are only granted if the type parameters meet the requirements described above for fields. Taking `Cup` from above as an example: * `Cup` has the ability `copy` only if `T` has `copy` * It has `drop` only if `T` has `drop` * It has `store` only if `T` has `store` * It has `key` only if `T` has `store` ## Examples of Conditional Abilities ### Example: Conditional Copy ```move struct NoAbilities {} struct S has copy, drop { f: bool } struct Cup has copy, drop, store { item: T } fun example(c_x: Cup, c_s: Cup) { // Valid, 'Cup' has 'copy' because 'u64' has 'copy' let c_x2 = copy c_x; // Valid, 'Cup' has 'copy' because 'S' has 'copy' let c_s2 = copy c_s; } fun invalid(c_account: Cup, c_n: Cup) { // Invalid, 'Cup' does not have 'copy'. // Even though 'Cup' was declared with copy, the instance does not have 'copy' // because 'signer' does not have 'copy' let c_account2 = copy c_account; // Invalid, 'Cup' does not have 'copy' // because 'NoAbilities' does not have 'copy' let c_n2 = copy c_n; } ``` ### Example: Conditional Drop ```move struct NoAbilities {} struct S has copy, drop { f: bool } struct Cup has copy, drop, store { item: T } fun unused() { Cup { item: true }; // Valid, 'Cup' has 'drop' Cup { item: S { f: false }}; // Valid, 'Cup' has 'drop' } fun left_in_local(c_account: Cup): u64 { let c_b = Cup { item: true }; let c_s = Cup { item: S { f: false }}; // Valid return: 'c_account', 'c_b', and 'c_s' have values // but 'Cup', 'Cup', and 'Cup' have 'drop' 0 } fun invalid_unused() { // Invalid, Cannot ignore 'Cup' because it does not have 'drop'. // Even though 'Cup' was declared with 'drop', the instance does not have 'drop' // because 'NoAbilities' does not have 'drop' Cup { item: NoAbilities {}}; } fun invalid_left_in_local(): u64 { let n = Cup { item: NoAbilities {}}; // Invalid return: 'n' has a value // and 'Cup' does not have 'drop' 0 } ``` ### Example: Conditional Store ```move struct Cup has copy, drop, store { item: T } // 'MyInnerResource' is declared with 'store' so all fields need 'store' struct MyInnerResource has store { yes: Cup, // Valid, 'Cup' has 'store' // no: Cup, Invalid, 'Cup' does not have 'store' } // 'MyResource' is declared with 'key' so all fields need 'store' struct MyResource has key { yes: Cup, // Valid, 'Cup' has 'store' inner: Cup, // Valid, 'Cup' has 'store' // no: Cup, Invalid, 'Cup' does not have 'store' } ``` ### Example: Conditional Key ```move struct NoAbilities {} struct MyResource has key { f: T } fun valid(account: &signer) acquires MyResource { let addr = signer::address_of(account); // Valid, 'MyResource' has 'key' let has_resource = exists>(addr); if (!has_resource) { // Valid, 'MyResource' has 'key' move_to(account, MyResource { f: 0 }) }; // Valid, 'MyResource' has 'key' let r = borrow_global_mut>(addr); r.f = r.f + 1; } fun invalid(account: &signer) { let addr = signer::address_of(account); // Invalid, 'MyResource' does not have 'key' let has_it = exists>(addr); // Invalid, 'MyResource' does not have 'key' let NoAbilities {} = move_from>(addr); // Invalid, 'MyResource' does not have 'key' move_to(account, MyResource { f: NoAbilities {} }); // Invalid, 'MyResource' does not have 'key' borrow_global>(addr); } ``` ## Best Practices 1. **Minimal abilities** - Only grant abilities that are necessary for your use case 2. **Resource safety** - Don't give `copy` or `drop` to valuable resources like tokens 3. **Storage design** - Use `key` for top-level resources, `store` for nested data 4. **Generic constraints** - Consider how type parameters affect conditional abilities 5. **Documentation** - Clearly document why certain abilities are or aren't granted ## Common Ability Patterns ### Value Types (Data) ```move struct Point has copy, drop, store { x: u64, y: u64, } ``` ### Resource Types (Assets) ```move struct Coin has store { value: u64, } ``` ### Global Resources ```move struct Account has key { balance: u64, sequence_number: u64, } ``` ### Generic Containers ```move struct Box has copy, drop, store { item: T, } // Abilities depend on T's abilities ``` ## Ability Interactions Summary | Ability | Gates | Field Requirements | Notes | | ------- | -------------------------------- | ---------------------------- | ---------------------------- | | `copy` | `copy` operator, `*` dereference | All fields must have `copy` | Enables value duplication | | `drop` | Ignoring values, scope exit | All fields must have `drop` | Enables automatic cleanup | | `store` | Nested in global storage | All fields must have `store` | Required for storage nesting | | `key` | Global storage operations | All fields must have `store` | Enables top-level storage | ## Summary Type abilities control what operations are permitted on values in Move: * **Four abilities**: `copy`, `drop`, `store`, and `key` gate different operations * **Field requirements**: Struct abilities require all fields to have compatible abilities * **Conditional abilities**: Generic types inherit abilities based on type parameters * **Resource safety**: Abilities prevent accidental duplication or loss of valuable resources * **Storage control**: `store` and `key` abilities manage global storage access Abilities provide fine-grained control over type behavior and resource safety in Move. # Unit Tests URL: /devs/move-book/unitTest # Unit Tests Unit testing in Move provides a robust framework for validating code correctness and catching bugs early in development. Move's testing system uses three key annotations to create comprehensive test suites that ensure your smart contracts behave as expected. **Key benefits:** * **Early bug detection**: Catch issues before deployment * **Code reliability**: Verify functions work under various conditions * **Regression prevention**: Ensure changes don't break existing functionality * **Documentation**: Tests serve as executable specifications Move's testing framework integrates seamlessly with the compiler and provides detailed feedback on test failures, making it an essential tool for professional Move development. ## Test Annotations ### Basic Test Functions Use `#[test]` to mark functions as executable unit tests: ```move module 0x42::calculator { public fun add(a: u64, b: u64): u64 { a + b } public fun divide(a: u64, b: u64): u64 { assert!(b != 0, 1); a / b } #[test] fun test_addition() { assert!(add(2, 3) == 5, 0); assert!(add(0, 100) == 100, 0); } #[test] fun test_division() { assert!(divide(10, 2) == 5, 0); assert!(divide(7, 3) == 2, 0); } } ``` **Test function requirements:** * Must have `#[test]` annotation * Cannot take parameters (except with signer injection) * Can have any visibility level * Should use descriptive names starting with `test_` ### Test-Only Code Use `#[test_only]` to include code exclusively for testing. This annotation excludes code from production bytecode: ```move module 0x42::token { struct Token has key { balance: u64 } public fun transfer(from: &signer, to: address, amount: u64) { // Transfer implementation } #[test_only] fun create_test_token(account: &signer, amount: u64) { move_to(account, Token { balance: amount }); } #[test_only] use std::debug; #[test(alice = @0x1)] fun test_token_creation(alice: signer) { create_test_token(&alice, 100); assert!(exists(@0x1), 0); } } ``` **Test-only scope:** * Functions, modules, structs, constants, and imports can be marked `#[test_only]` * Only available in test builds * Enables creating test utilities without bloating production code ### Expected Failures Use `#[expected_failure]` to test error conditions: ```move module 0x42::vault { const EInsufficientBalance: u64 = 1; public fun withdraw(amount: u64, balance: u64) { assert!(balance >= amount, EInsufficientBalance); } #[test] #[expected_failure(abort_code = EInsufficientBalance)] fun test_insufficient_balance() { withdraw(100, 50); // Should abort with EInsufficientBalance } #[test] #[expected_failure] // Any failure is acceptable fun test_division_by_zero() { let _result = 10 / 0; } } ``` **Expected failure options:** * `#[expected_failure]`: Test should abort with any error * `#[expected_failure(abort_code = )]`: Test should abort with specific code * `#[expected_failure(arithmetic_error, location = Self)]`: Arithmetic errors * `#[expected_failure(out_of_gas, location = Self)]`: Gas limit errors ## Signer Injection Tests can receive signer parameters for testing account-specific functionality: ```move module 0x42::account_manager { struct Account has key { balance: u64, active: bool, } public fun create_account(owner: &signer, initial_balance: u64) { move_to(owner, Account { balance: initial_balance, active: true, }); } #[test(user = @0x123)] fun test_account_creation(user: signer) { create_account(&user, 1000); assert!(exists(@0x123), 0); } #[test(alice = @0x1, bob = @0x2)] fun test_multiple_accounts(alice: signer, bob: signer) { create_account(&alice, 500); create_account(&bob, 750); assert!(exists(@0x1), 0); assert!(exists(@0x2), 0); } } ``` **Signer injection syntax:** * `#[test(param_name = @address)]` for single signer * `#[test(alice = @0x1, bob = @0x2)]` for multiple signers * Parameter names must match function parameter names * Only `signer` type parameters are supported ## Running Tests ```bash # Run all tests in the package movement move test # Run tests matching a pattern movement move test --filter "account" ``` **Test results:** * **PASS**: Test completed successfully * **FAIL**: Test failed with error details * **TIMEOUT**: Test exceeded gas/instruction limits ## Best Practices * Group related tests in the same module as the code being tested * Use descriptive test names that explain what is being verified * Test both success and failure cases with `#[expected_failure]` * Keep tests lightweight for fast execution ## Summary Move's unit testing framework provides comprehensive testing capabilities: * **Test annotations**: `#[test]` for executable tests, `#[test_only]` for test helpers * **Error testing**: `#[expected_failure]` with optional specific abort codes * **Signer injection**: Automatic test account creation for testing functions requiring signers * **Test organization**: Tests can be in dedicated modules or alongside source code * **Bytecode exclusion**: Test code is excluded from production compilation * **Detailed reporting**: Clear failure messages with abort codes and locations * **CLI integration**: Run tests with `movement move test` command # Uses and Aliases URL: /devs/move-book/usesAndAliases # Uses and Aliases The `use` syntax creates aliases for modules and their members, making code more readable and reducing repetitive fully-qualified names. Aliases can be scoped to entire modules or specific expression blocks, providing flexible import management. **Key benefits:** * **Cleaner code**: Shorter, more readable function calls * **Namespace management**: Avoid naming conflicts with local aliases * **Selective imports**: Import only needed functions and types * **Flexible scoping**: Module-level or block-level alias control Uses and aliases are essential for organizing complex Move projects and creating maintainable code that clearly expresses dependencies and relationships between modules. ## Basic Module Aliases ### Simple Module Import **Import patterns:** * `use std::vector` - Import with original name * `use std::option as opt` - Import with custom alias * Aliases must follow Move naming conventions Import entire modules with optional custom names: ```move module 0x42::defi_app { use std::vector; use std::option as opt; public fun create_portfolio(): vector> { let mut portfolio = vector::empty(); vector::push_back(&mut portfolio, opt::some(100)); vector::push_back(&mut portfolio, opt::none()); portfolio } } ``` ### Member-Specific Imports **Member import syntax:** * `use module::{member1, member2}` - Multiple members * `use module::{member as alias}` - Member with custom name * `use module::{Self, member}` - Module and member together Import specific functions, structs, or constants: ```move module 0x42::token_utils { use std::vector::{push_back, pop_back, length}; use std::option::{Option, some, none}; public fun process_batch(items: &mut vector): Option { if (length(items) > 0) { some(pop_back(items)) } else { none() } } } ``` ## Advanced Import Patterns ### Self References and Multiple Aliases Combine module and member imports efficiently: ```move module 0x42::math_utils { use std::vector::{Self as vec, push_back, length as len}; public fun calculate_average(numbers: vector): u64 { let total = 0; let count = len(&numbers); while (!vec::is_empty(&numbers)) { total = total + vec::pop_back(&mut numbers); } total / count } } ``` **Self reference rules:** * `Self` refers to the module itself * Can be aliased like any other import * Useful for mixing module and member access patterns ## Scoping Rules ### Module-Level Imports Imports at module level are available throughout the entire module: ```move module 0x42::storage_manager { use std::vector; use std::option::Option; struct Storage has key { data: vector } public fun store_data(account: &signer, values: vector) { move_to(account, Storage { data: values }); } public fun get_data(addr: address): Option> { if (exists(addr)) { std::option::some(borrow_global(addr).data) } else { std::option::none() } } } ``` ### Block-Level Imports Imports within expression blocks have limited scope: ```move module 0x42::calculator { public fun complex_calculation(x: u64, y: u64): u64 { let result = { use std::vector::{push_back, pop_back}; let mut temp = std::vector::empty(); push_back(&mut temp, x * 2); push_back(&mut temp, y * 3); pop_back(&mut temp) + pop_back(&mut temp) }; // push_back not available here - would cause error result } } ``` **Block scoping rules:** * Imports must be first statements in blocks * Aliases expire at block end * Can shadow outer scope names * Useful for temporary, localized imports ## Naming and Uniqueness Rules ### Naming Conventions Aliases must follow standard Move naming rules: ```move module 0x42::examples { use std::vector::{ push_back as add_item, // Valid function alias length as get_size, // Valid function alias // push_back as AddItem, // ERROR: Functions use snake_case }; use std::option::{ Option as Opt, // Valid struct alias // Option as opt, // ERROR: Structs use PascalCase }; } ``` ### Uniqueness Requirements Aliases within the same scope must be unique: ```move module 0x42::conflict_examples { // This would cause errors: // use std::vector::push_back as add; // use std::option::some as add; // Duplicate 'add' // This is valid: use std::vector::push_back as vec_add; use std::option::some as opt_add; public fun example() { let mut v = std::vector::empty(); vec_add(&mut v, opt_add(42)); } } ``` ## Shadowing Use aliases inside of an expression block can shadow names (module members or aliases) from the outer scope. As with shadowing of locals, the shadowing ends at the end of the expression block: ```move module 0x42::math_library { use std::vector; public fun sum(numbers: vector): u64 { vector::length(&numbers) // Uses std::vector::length } public fun process_data(): u64 { let result = { use std::vector::length as size; // 'length' is shadowed by 'size' alias in this block let data = vector::empty(); vector::push_back(&mut data, 10); vector::push_back(&mut data, 20); size(&data) // Returns 2 }; // 'length' shadow ends here result + vector::length(&vector::singleton(5)) // Uses original std::vector::length } public fun nested_shadowing(): bool { use std::vector::is_empty; let outer_result = { use std::vector::is_empty as empty_check; // 'is_empty' is shadowed by 'empty_check' let inner_result = { use std::vector::length as is_empty; // 'is_empty' is now shadowed by 'length' function! let v = vector::singleton(42); is_empty(&v) == 1 // Actually calls length, returns true }; // 'is_empty' refers to 'empty_check' again let v = vector::empty(); empty_check(&v) && inner_result }; // 'is_empty' refers to original function again outer_result && is_empty(&vector::empty()) } } ``` **Key shadowing behaviors:** * **Block scope**: Aliases only shadow names within their expression block * **Nested shadowing**: Inner blocks can shadow aliases from outer blocks * **Name restoration**: Original names become available again when the block ends * **Any name can be shadowed**: Module members, previous aliases, or even unrelated functions ## Unused Use or Alias An unused use will result in a warning when compiling: ```move module 0x42::example { use std::vector::{empty, push_back}; // WARNING! // ^^^^^^^^^ Unused 'use' of alias 'push_back'. Consider removing it fun example(): vector { empty() } } ``` ## Best Practices * Place imports at the top of modules for clarity * Use descriptive aliases that clarify intent * Use module-level imports for frequent dependencies * Keep import scopes as narrow as practical ## Summary The `use` syntax provides flexible import management for Move code: * **Module aliases**: Import entire modules with original or custom names * **Member imports**: Import specific functions, structs, or constants * **Flexible scoping**: Module-level or block-level alias control * **Self references**: Combine module and member imports efficiently * **Namespace management**: Avoid naming conflicts with local aliases * **Cleaner code**: Shorter, more readable function calls * **Naming conventions**: Aliases must follow Move naming rules # Variables, Assignment and Scope URL: /devs/move-book/variablesAssignmentAndScope # Variables, Assignment and Scope Variables in Move store data that your program can use and manipulate. This chapter covers how to declare variables, assign values to them, and understand their scope. ## Variable Declaration Variables are declared using the `let` keyword: ```move let x = 10; let y = 20; ``` By default, variables are **mutable** - their value can be changed after being declared. ```move let count = 0; count = 5; // This is valid as variables are mutable by default ``` ## Type Annotations Move can usually infer the type of your variables, but you can explicitly specify types when needed: ```move let age: u8 = 25; let price: u64 = 1000; let score: u32 = 0; ``` ## Assignment and Mutation Variables can be reassigned as they are mutable by default: ```move let balance = 100; balance = 150; balance = balance + 50; // balance is now 200 ``` ## Variable Shadowing You can declare a new variable with the same name as a previous variable. This is called **shadowing**: ```move let x = 10; let x = 20; // This shadows the previous x let x = x + 5; // x is now 25 ``` Shadowed variables can even have different types: ```move let value = 42; // u64 (inferred) let value = 100u8; // u8 (explicit) ``` ## Variable Naming Rules Variable names must follow these rules: * Start with a letter (`a-z`) or underscore (`_`) * Can contain letters, numbers, and underscores * Cannot start with uppercase letters ```move // Valid names let age = 25; let _temp = 10; let user_count = 0; let value2 = 100; // Invalid names // let Age = 25; // ERROR: starts with uppercase // let 2value = 100; // ERROR: starts with number ``` ## Delayed Assignment You can declare a variable without immediately assigning a value, but you must assign it before use: ```move let result; if (condition) { result = 10; } else { result = 20; } // result can now be used ``` ## Using Variables Before Assignment Move enforces that variables must be assigned a value before they can be used. This prevents common programming errors and ensures memory safety: ```move let x; // let y = x + 10; // ERROR: use of unassigned local `x` x = 5; let y = x + 10; // OK: x has been assigned ``` The Move compiler performs **definite assignment analysis** to ensure all code paths assign a value before use: ```move let result; if (some_condition) { result = 100; } else { }; // ERROR: use of possibly unassigned local `result` ``` To fix this, ensure all code paths assign the variable: ```move let result; if (some_condition) { result = 100; } else { result = 200; }; // OK: result is assigned in all paths ``` ### Partial Assignment in Complex Control Flow The compiler tracks assignment across complex control structures: ```move let value; if (condition1) { if (condition2) { value = 10; } else { value = 20; } } else { value = 30; } // OK: all paths assign value ``` ## Scope Variables are only accessible within the **scope** where they are declared. Scopes are defined by curly braces `{}`: ```move let x = 10; { let y = 20; let z = x + y; // x is accessible here } // y and z are no longer accessible // x is still accessible here ``` ### Nested Scopes Variables from outer scopes can be used in inner scopes: ```move let outer = 100; { let inner = 50; let sum = outer + inner; // Both variables accessible { let result = sum + outer; // All variables accessible } } ``` ### Scope and Mutation Variables can be mutated in any scope where they're accessible: ```move let counter = 0; { counter = counter + 1; // Mutation survives the scope } // counter is now 1 ``` ## Multiple Variable Declaration You can declare multiple variables at once using tuples: ```move let (x, y) = (10, 20); let (a, b, c) = (1, 2, 3); ``` This is useful for functions that return multiple values: ```move fun get_coordinates(): (u64, u64) { (100, 200) } let (x_pos, y_pos) = get_coordinates(); ``` ## Expression Blocks Expression blocks are sequences of statements enclosed in curly braces. The value of the last expression becomes the block's value: ```move let result = { let a = 10; let b = 20; a + b // This value (30) is returned from the block }; ``` ## Summary Variables in Move provide a way to store and manipulate data in your programs. Key points to remember: * Variables are **mutable by default** * Variables have **scope** - they're only accessible within their declaration block * **Shadowing** allows redeclaring variables with the same name * **Type annotations** can be explicit or inferred by the compiler * Variables must be **assigned before use** Understanding these concepts will help you write clear and safe Move code. In the next chapters, we'll explore specific data types like integers, booleans, addresses, and vectors. # Vector URL: /devs/move-book/vector # Vector Type `vector` is Move's built-in collection type for storing multiple values of the same type. Think of it as a dynamic array that can grow and shrink during program execution. ## What is a Vector? A vector is a homogeneous collection that stores elements of the same type: * **Dynamic size**: Can grow or shrink at runtime * **Ordered**: Elements maintain their insertion order * **Indexed**: Access elements by their position (0-based) * **Homogeneous**: All elements must be the same type ## Creating Vectors ### Empty Vectors ```move use std::vector; let empty_numbers: vector = vector[]; let empty_addresses = vector
[]; let empty_with_function = vector::empty(); ``` ### Vectors with Initial Values ```move let numbers = vector[1, 2, 3, 4, 5]; let addresses = vector[@0x1, @0x2, @0x3]; let single_item = vector[42u8]; ``` ### Type Inference Move can often infer the vector type from context: ```move let numbers = vector[10, 20, 30]; // Inferred as vector let explicit: vector = vector[10, 20, 30]; // Explicit type ``` ## Special Vector Types ### Byte Vectors (`vector`) Byte vectors are commonly used for strings and binary data: ```move // Byte strings (ASCII) let message = b"Hello, Move!"; let empty_bytes = b""; // Hex strings let hex_data = x"48656C6C6F"; // "Hello" in hex let hash = x"DEADBEEF"; ``` ## Common Vector Operations ### Adding Elements ```move use std::vector; let numbers = vector::empty(); vector::push_back(&mut numbers, 10); vector::push_back(&mut numbers, 20); vector::push_back(&mut numbers, 30); // numbers is now [10, 20, 30] ``` ### Accessing Elements ```move let numbers = vector[10, 20, 30]; // Get element by index (returns reference) let first = vector::borrow(&numbers, 0); // &10 let second = vector::borrow(&numbers, 1); // &20 // Check if vector contains an element let has_twenty = vector::contains(&numbers, &20); // true ``` ### Modifying Elements ```move let numbers = vector[10, 20, 30]; // Modify element at index let element_ref = vector::borrow_mut(&mut numbers, 1); *element_ref = 25; // numbers is now [10, 25, 30] // Remove and return last element let last = vector::pop_back(&mut numbers); // 30 // numbers is now [10, 25] ``` ### Vector Information ```move let numbers = vector[10, 20, 30]; // Get vector length let length = vector::length(&numbers); // 3 // Check if empty let is_empty = vector::is_empty(&numbers); // false // Find element index let (found, index) = vector::index_of(&numbers, &20); // (true, 1) ``` ## Advanced Operations ### Combining Vectors ```move let first = vector[1, 2, 3]; let second = vector[4, 5, 6]; // Append second vector to first vector::append(&mut first, second); // first is now [1, 2, 3, 4, 5, 6] ``` ### Removing Elements ```move let numbers = vector[10, 20, 30, 40]; // Remove element at specific index let removed = vector::remove(&mut numbers, 1); // 20 // numbers is now [10, 30, 40] // Swap remove (faster but changes order) let items = vector[1, 2, 3, 4]; let removed = vector::swap_remove(&mut items, 1); // 2 // items is now [1, 4, 3] (last element moved to removed position) ``` ### Vector Utilities ```move let numbers = vector[3, 1, 4, 1, 5]; // Reverse the vector vector::reverse(&mut numbers); // numbers is now [5, 1, 4, 1, 3] // Swap elements vector::swap(&mut numbers, 0, 4); // numbers is now [3, 1, 4, 1, 5] ``` ## Additional Methods While the operations above are the most common, the `std::vector` module provides a few other useful functions. ### `singleton` Creates a vector with a single element. This is a convenient shorthand for creating a vector and pushing one item. ```move use std::vector; let single_element_vec = vector::singleton(100); // single_element_vec is now [100] ``` ### `destroy_empty` Destroys an empty vector. This is necessary for vectors containing elements that do not have the `drop` ability. If the vector is not empty, the operation will abort. ```move use std::vector; let empty_vec = vector::empty(); vector::destroy_empty(empty_vec); // This is successful let non_empty_vec = vector[1]; // vector::destroy_empty(non_empty_vec); // This would abort! ``` ## Practical Examples Here are common vector usage patterns: ```move // Managing a list of user addresses fun add_user(users: &mut vector
, new_user: address) { if (!vector::contains(users, &new_user)) { vector::push_back(users, new_user); } } // Processing scores fun calculate_average(scores: &vector): u64 { let sum = 0; let i = 0; let len = vector::length(scores); while (i < len) { sum += *vector::borrow(scores, i); i += 1; }; if (len == 0) { len } else { sum / len } } // Working with byte data fun create_message(): vector { let message = b"Hello, "; vector::append(&mut message, b"Move!"); message // Returns b"Hello, Move!" } ``` ## Destroying and Copying Vectors The behavior of a `vector` can depend on the abilities of its element type, `T`. ### Dropping Vectors Vectors containing elements that do not have the `drop` ability cannot be implicitly discarded. They must be explicitly destroyed using `vector::destroy_empty`, which aborts if the vector is not empty. ```move fun destroy_any_vector(vec: vector) { vector::destroy_empty(vec); // This is required. } ``` However, if a vector's elements have the `drop` ability, the vector can be dropped without any explicit action, and its contents will be properly destroyed. ```move fun destroy_droppable_vector(vec: vector) { // Valid! No explicit action is needed to destroy the vector. } ``` ### Copying Vectors A `vector` has the `copy` ability if and only if its element type `T` has `copy`. However, even copyable vectors are never implicitly copied. You must use an explicit `copy` instruction. ```move let v1 = vector::singleton(10); let v2 = copy v1; // An explicit `copy` is required. // let v3 = v1; // This would be a compiler error! ``` This design choice makes expensive copies of large vectors obvious in the code. For more details, see the sections on **type abilities** and **generics**. ## Safety and Performance ### Index Bounds Vector operations check bounds automatically: ```move let numbers = vector[1, 2, 3]; // let item = vector::borrow(&numbers, 5); // Would abort! Index out of bounds ``` ### Memory Management Vectors automatically manage memory: * Elements are automatically cleaned up when the vector is dropped * Efficient resizing as elements are added ## Ownership A `vector` can only be copied if the element type `T` has the copy ability. Move does not allow implicit copies of vectors. You must explicitly perform the copy using either: the copy keyword, or a dereference (\*). ## Summary Vectors in Move are: * **Dynamic collections** that can grow and shrink * **Type-safe** - all elements must be the same type * **Indexed** - access elements by position (0-based) * **Bounds-checked** - operations abort on invalid indices * **Memory-safe** - automatic cleanup and management Common use cases include: * Storing lists of addresses, IDs, or other data * Managing collections that change size during execution * Working with byte data and strings * Building more complex data structures Vectors are essential for most Move programs that need to work with collections of data. # Token Standards URL: /devs/tokenStandard/overview ## Overview Standards define the behavior and structure of essential blockchain features, including tokens, wallets, and on-chain objects. They ensure seamless interoperability between contracts from different developers, enabling assets to maintain consistent appearance, predictable interactions, and universal support across wallets and marketplaces. Movement implements three primary standard categories: * **Move Standards** — Core object model defining data and resource structure * **Asset Standards** — Formal specifications for tokens and NFTs * **Legacy Standards** — Older token systems maintained for backward compatibility *** ## Move Object Model Movement leverages the **Move Object Model** to represent real-world entities on-chain. Rather than creating custom contract layouts for each use case, developers utilize a standardized object-based format. These objects encapsulate resources with strict ownership, transfer, and mutation rules, significantly reducing bugs and enhancing composability. **Key Benefits:** * Provides the foundation for all asset standards (fungible and non-fungible) * Enforces safety guarantees through type system constraints * Eliminates common vulnerabilities associated with ad-hoc implementations *** ## Current Asset Standards The current standards represent the modern approach to token and asset management on Movement. ### Digital Asset (DA) Standard The Digital Asset standard is the recommended framework for non-fungible and semi-fungible assets. **Core Features:** * Tokens implemented as Move Objects * NFTs organized within collections with comprehensive metadata support * Direct transfer capability without recipient opt-in requirements * Composable architecture allowing NFTs to own other NFTs All new NFT projects should implement the DA standard to ensure maximum flexibility and ecosystem compatibility. ### Fungible Asset (FA) Standard The Fungible Asset standard provides a comprehensive framework for representing fungible tokens. **Design Principles:** * Type-safe token operations with compile-time guarantees * Automatic recipient storage initialization * Granular control over minting, burning, transfers, and permission management * Enhanced flexibility for diverse applications **Advantages Over Legacy Systems:** FA offers superior expressiveness and adaptability compared to the older Coin standard, making it suitable for use cases ranging from currencies to in-game assets. *** ## Legacy Standards While current standards are recommended for all new development, legacy standards remain supported to ensure backward compatibility with existing applications. ### Token (Legacy) The [Token](https://github.com/movement-network/aptos-core/blob/m1/aptos-move/framework/aptos-token/sources/token.move) standard provides a versatile framework for managing multiple token types within a single implementation. * **Versatile multi-token support** - Handles NFTs, fungibles, and semi-fungibles in a single standard * **Customizable properties** - Offers flexible on-chain property configuration * **Backward compatible** - Remains functional for existing applications ### Coin (Legacy) The [Coin](https://github.com/movement-network/aptos-core/blob/m1/aptos-move/framework/aptos-framework/sources/coin.move) standard offers a straightforward approach to fungible token implementation with high performance and low gas overhead. * **Simple fungible implementation** - Straightforward token standard for fungible assets * **Superseded standard** - Replaced by the more capable Fungible Asset standard * **Continued support** - Still functional for existing deployments # Build URL: /devs/tutorials/build In this guide, we will learn how to build an end-to-end "onchain bio" dApp. We'll start with the Move contract. Then we'll build a React frontend and let wallets connect and register a bio. ## Requirements Make sure to have [Movement CLI](/devs/movementcli) installed. If you are using Aptos CLI - ensure that you are using version 3.5 or lower. View an example of the finished dApp [here](https://github.com/movement-network/onchain-bio-dapp). ## Initialize your Environment Initialize your profile for your package development and add Movement as a custom network. Movement is our current blockchain that supports Aptos deployments. ```bash movement init ``` ## Setup Create a new directory and navigate into it: ```bash mkdir my-onchain-bio && cd my-onchain-bio ``` Open a code editor so you can see your dApp's file structure. Here we'll use VS Code: ```bash code . ``` ## Creating and publishing the Onchain Bio smart contract Now that our environment is set up, let's write some code, starting with the Move contract! First, create a move directory and navigate into it: ```bash mkdir move && cd move ``` Then create a move project, containing code for an onchain package: ```bash movement move init --name my_todo_list ``` Your file structure should now look like this: ![folder\_structure](./imgs/structure.webp) ## Creating a Move Module Create a new file named onchain\_bio.move within the sources directory and add the following to that file: ```rust module onchain_bio_addr::onchain_bio { use std::string::{String}; use std::signer; struct Bio has key, store, drop { name: String, bio: String, } #[view] public fun signature() : address { @
} public entry fun register(account: &signer, name: String, bio: String) acquires Bio { // Check if a Bio already exists for the account if (exists(signer::address_of(account))) { // Remove the existing Bio let _old_Bio = move_from(signer::address_of(account)); }; // Create the new Bio let bio = Bio { name, bio, }; // Store the new Bio under the account move_to(account, bio); } } ``` Next, let's deploy to testnet! ## Deploying the Module to Movement's Testnetwork First, from within the move directory, initialize your Movement configuration: ```bash movement init ``` When asked for a private key, press Enter to generate a new keypair. Upon successful initialization, you'll see the success message (with your own account instead of the one below): ```json No key given, generating key... Account 0x39883cbc29500a8bf79911ea1469e1c3b58104547a88fb0fbdf17470f80b2a91 doesn't exist, creating it and funding it with 100000000 Octas Account 0x39883cbc29500a8bf79911ea1469e1c3b58104547a88fb0fbdf17470f80b2a91 funded successfully --- Movement CLI is now set up for account 0x39883cbc29500a8bf79911ea1469e1c3b58104547a88fb0fbdf17470f80b2a91 as profile default! Run `movement --help` for more information about commands { "Result": "Success" } ``` Now your move directory will contain a hidden .movement folder containing a config.yaml file. You can view the contents of that file to see your private and public keys. In the line under \[addresses] in Move.toml, add your account address: ```json onchain_bio_addr = "" ``` After compiling, you'll see a result message formatted like this: ```json { "Result": [ "39883cbc29500a8bf79911ea1469e1c3b58104547a88fb0fbdf17470f80b2a91::onchain_bio" ] } ``` Now you can publish the package: ```bash movement move publish ``` After confirming that the price is okay, your transaction will be submitted to the blockchain. You'll get a result formatted like this, with values associated with your transaction: ```json { "Result": { "transaction_hash": "0x37ea722ad4f1ff0d8d0710965a47354cc903579d38b659d90c4286ddab946151", "gas_used": 1236, "gas_unit_price": 100, "sender": "39883cbc29500a8bf79911ea1469e1c3b58104547a88fb0fbdf17470f80b2a91", "sequence_number": 0, "success": true, "timestamp_us": 1707748417269022, "version": 248, "vm_status": "Executed successfully" } } ``` Congratulations! Your module is now deployed. Let's shift over to the frontend. ## Building a React app for users to register and view their onchain bio Navigate to the root of our project directory: ``` cd .. ``` Create a new React app named `client`: ``` npx create-react-app client --template typescript ``` At the root of your project, you'll have two directories: `client` and `move`. cd into the client directory and run `npm start`. Your app should now be running on [http://localhost:3000](http://localhost:3000), displaying the default React layout. Your apps files are in the `client/src` directory. Replace the code in `Index.tsx` with the following: ```jsx import { PetraWallet } from "petra-plugin-wallet-adapter"; import { AptosWalletAdapterProvider } from "@aptos-labs/wallet-adapter-react"; import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; import './index.css'; const wallets = [new PetraWallet()]; const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); root.render( , ); reportWebVitals(); ``` This wraps our app in the ` ` tag so we can connect a wallet. To add support for more wallets, see the [Aptos Wallet Adapter README](https://github.com/aptos-labs/aptos-wallet-adapter#supported-wallet-packages). Now replace your App.tsx code with the following: ```jsx import { WalletSelector } from "@aptos-labs/wallet-adapter-ant-design"; import "@aptos-labs/wallet-adapter-ant-design/dist/index.css"; import { useRef, useState, useEffect } from "react"; import { useWallet, InputTransactionData } from '@aptos-labs/wallet-adapter-react'; import { Movement, MovementConfig, Network } from "@moveindustries/ts-sdk"; import { ONCHAIN_BIO } from "./constants"; import './index.css'; // with custom configuration const movementConfig = new MovementConfig({ network: Network.CUSTOM }); const movement = new Movement(movementConfig); function App() { const { signAndSubmitTransaction, account } = useWallet(); const name = useRef(null); const bio = useRef(null); const [accountHasBio, setAccountHasBio] = useState(false); const [currentName, setCurrentName] = useState(null); const [currentBio, setCurrentBio] = useState(null); const fetchBio = async () => { if (!account) { console.log("No account") return []; } try { const bioResource = await movement.getAccountResource( { accountAddress:account?.address, resourceType:`${ONCHAIN_BIO}::onchain_bio::Bio` } ); console.log("Name:", bioResource.name, "Bio:", bioResource.bio); setAccountHasBio(true); if (bioResource) { setCurrentName(bioResource.name); setCurrentBio(bioResource.bio); } else { console.log("no bio") } } catch (e: any) { setAccountHasBio(false); } }; async function registerBio() { if (bio.current !== null && name.current !== null) { const onchainName = name.current.value; const onchainBio = bio.current.value; const transaction: InputTransactionData = { data: { function:`${ONCHAIN_BIO}::onchain_bio::register`, functionArguments:[onchainName, onchainBio] } } try { // sign and submit transaction to chain const response = await signAndSubmitTransaction(transaction); // wait for transaction console.log(`Success! View your transaction at https://explorer.movementnetwork.xyz/txn/${response.hash}?network=bardock+testnet`) await movement.waitForTransaction({transactionHash:response.hash}); fetchBio(); } catch (error: any) { console.log("Error:", error) } } } return ( <>
Your Onchain Bio

You Onchain Bio

Your name:

Your Bio: