# 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 :: {
(