diff --git a/docs/dev-tools/api/graphql/index.mdx b/docs/dev-tools/api/graphql/index.mdx
new file mode 100644
index 0000000..23d7170
--- /dev/null
+++ b/docs/dev-tools/api/graphql/index.mdx
@@ -0,0 +1,43 @@
+---
+title: GraphQL API
+sidebar_label: GraphQL API
+sidebar_position: 2
+---
+
+import { CardList } from '@site/src/components/CardList';
+
+# GraphQL API
+
+The GraphQL API is stored full data history. Use it for historical data, complex filtering, and aggregations. For current state and submitting transactions, use the REST API instead.
+
+
+
+
+
+
+
+---
+
+## Available Data
+
+**Core**: `account_transactions`, `user_transactions`, `events`, `block_metadata_transactions`. Query transaction history, filter by account, look up events emitted by smart contracts.
+
+**Tokens**: `current_fungible_asset_balances`, `fungible_asset_activities`, `fungible_asset_metadata`. Get token balances, track transfers, look up token metadata.
+
+**NFTs**: `current_token_ownerships_v2`, `token_activities_v2`, `current_token_datas_v2`, `collections_v2`. Query NFT ownership, collection info, transfer history.
+
+**Staking**: `delegator_balances`, `delegated_staking_activities`, `current_delegated_staking_pool_balances`. Look up staking positions and delegation history.
+
+**Names**: `current_cedra_names`. Resolve Cedra name service lookups.
diff --git a/docs/dev-tools/api/graphql/queries.mdx b/docs/dev-tools/api/graphql/queries.mdx
new file mode 100644
index 0000000..7dece36
--- /dev/null
+++ b/docs/dev-tools/api/graphql/queries.mdx
@@ -0,0 +1,263 @@
+---
+title: Query Examples
+sidebar_label: Query Examples
+sidebar_position: 1
+---
+
+# Query Examples
+
+Practical GraphQL queries for common operations. Test these in the [GraphQL Playground](https://cloud.hasura.io/public/graphiql?endpoint=https://graphql.cedra.dev/v1/graphql).
+
+---
+
+## Account History
+
+**Get account transactions**: Returns paginated transaction history for an address. Use `limit` and `offset` for pagination. Results are ordered by version descending (newest first).
+
+```graphql
+query GetAccountTransactions($address: String!, $limit: Int, $offset: Int) {
+ account_transactions(
+ where: { account_address: { _eq: $address } }
+ order_by: { transaction_version: desc }
+ limit: $limit
+ offset: $offset
+ ) {
+ transaction_version
+ account_address
+ }
+}
+```
+
+Variables:
+
+```json
+{
+ "address": "0x1",
+ "limit": 10,
+ "offset": 0
+}
+```
+
+**Get transaction by version**: Look up a specific transaction by its version number. Useful when you have a version from another query or event.
+
+```graphql
+query GetTransaction($version: bigint!) {
+ user_transactions(where: { version: { _eq: $version } }) {
+ version
+ sender
+ sequence_number
+ entry_function_id_str
+ timestamp
+ }
+}
+```
+
+**Count account transactions**: Get total transaction count for an account. Useful for pagination or analytics.
+
+```graphql
+query CountTransactions($address: String!) {
+ account_transactions_aggregate(
+ where: { account_address: { _eq: $address } }
+ ) {
+ aggregate {
+ count
+ }
+ }
+}
+```
+
+---
+
+## Token Balances
+
+**Get all fungible asset balances**: Returns all token balances for an account including the native token. The `amount` field is in the smallest unit (octas for CDRA).
+
+```graphql
+query GetBalances($address: String!) {
+ current_fungible_asset_balances(
+ where: { owner_address: { _eq: $address } }
+ ) {
+ asset_type
+ amount
+ storage_id
+ }
+}
+```
+
+**Get token metadata**: Look up token name, symbol, and decimals for a fungible asset.
+
+```graphql
+query GetTokenMetadata($assetType: String!) {
+ fungible_asset_metadata(
+ where: { asset_type: { _eq: $assetType } }
+ ) {
+ name
+ symbol
+ decimals
+ asset_type
+ }
+}
+```
+
+**Get transfer history**: Track incoming and outgoing transfers for an account. Filter by `is_gas_fee` to exclude gas payments.
+
+```graphql
+query GetTransfers($address: String!, $limit: Int) {
+ fungible_asset_activities(
+ where: {
+ _or: [
+ { owner_address: { _eq: $address } }
+ ]
+ is_gas_fee: { _eq: false }
+ }
+ order_by: { transaction_version: desc }
+ limit: $limit
+ ) {
+ transaction_version
+ amount
+ asset_type
+ type
+ owner_address
+ }
+}
+```
+
+---
+
+## NFTs
+
+**Get NFTs owned by account**: Returns all NFTs currently owned by an address with collection and token data.
+
+```graphql
+query GetNFTs($address: String!) {
+ current_token_ownerships_v2(
+ where: { owner_address: { _eq: $address }, amount: { _gt: "0" } }
+ ) {
+ token_data_id
+ amount
+ current_token_data {
+ token_name
+ token_uri
+ description
+ collection_id
+ }
+ }
+}
+```
+
+**Get collection info**: Fetch collection metadata by collection ID.
+
+```graphql
+query GetCollection($collectionId: String!) {
+ current_collections_v2(
+ where: { collection_id: { _eq: $collectionId } }
+ ) {
+ collection_id
+ collection_name
+ creator_address
+ description
+ uri
+ current_supply
+ max_supply
+ }
+}
+```
+
+**Get NFT transfer history**: Track ownership changes for NFTs. Useful for provenance tracking.
+
+```graphql
+query GetNFTTransfers($tokenDataId: String!, $limit: Int) {
+ token_activities_v2(
+ where: { token_data_id: { _eq: $tokenDataId } }
+ order_by: { transaction_version: desc }
+ limit: $limit
+ ) {
+ transaction_version
+ type
+ from_address
+ to_address
+ token_amount
+ }
+}
+```
+
+---
+
+## Staking
+
+**Get delegator positions**: Returns staking positions for a delegator address including pool info and balances.
+
+```graphql
+query GetDelegatorPositions($address: String!) {
+ delegator_balances(
+ where: { delegator_address: { _eq: $address } }
+ ) {
+ pool_address
+ delegator_address
+ shares
+ table_handle
+ }
+}
+```
+
+**Get staking pool info**: Look up pool configuration and current balances.
+
+```graphql
+query GetPoolInfo($poolAddress: String!) {
+ current_delegated_staking_pool_balances(
+ where: { staking_pool_address: { _eq: $poolAddress } }
+ ) {
+ staking_pool_address
+ active_table_handle
+ inactive_table_handle
+ operator_commission_percentage
+ }
+}
+```
+
+---
+
+## Events
+
+**Get events by account**: Query events emitted in transactions involving an account.
+
+```graphql
+query GetEvents($address: String!, $limit: Int) {
+ events(
+ where: { account_address: { _eq: $address } }
+ order_by: { transaction_version: desc }
+ limit: $limit
+ ) {
+ transaction_version
+ event_index
+ type
+ data
+ }
+}
+```
+
+**Filter events by type**: Look up specific event types across all accounts. Useful for tracking specific contract events.
+
+```graphql
+query GetEventsByType($eventType: String!, $limit: Int) {
+ events(
+ where: { type: { _like: $eventType } }
+ order_by: { transaction_version: desc }
+ limit: $limit
+ ) {
+ transaction_version
+ account_address
+ type
+ data
+ }
+}
+```
+
+Variables:
+
+```json
+{
+ "eventType": "%::coin::WithdrawEvent",
+ "limit": 10
+}
+```
diff --git a/docs/dev-tools/api/index.mdx b/docs/dev-tools/api/index.mdx
new file mode 100644
index 0000000..71a9d55
--- /dev/null
+++ b/docs/dev-tools/api/index.mdx
@@ -0,0 +1,43 @@
+---
+title: API Reference
+sidebar_label: API Reference
+sidebar_position: 1
+---
+
+import { CardList } from '@site/src/components/CardList';
+
+# API Reference
+
+Cedra exposes a REST API on every node for querying blockchain state and submitting transactions. The API follows OpenAPI 3.0 specification and returns JSON responses.
+
+| Network | URL |
+|---------|-----|
+| Testnet | `https://testnet.cedra.dev/v1` |
+| Devnet | `https://devnet.cedra.dev/v1` |
+
+- **Building a wallet**: Query account balances, submit transfers, track transaction status
+- **dApp backends**: Read on-chain state, call view functions, submit signed transactions
+- **Block explorers**: Fetch blocks, transactions, events, account resources
+
+
+
+
+
+
+
diff --git a/docs/dev-tools/api/rest/errors.mdx b/docs/dev-tools/api/rest/errors.mdx
new file mode 100644
index 0000000..5048c21
--- /dev/null
+++ b/docs/dev-tools/api/rest/errors.mdx
@@ -0,0 +1,92 @@
+---
+title: Error Handling
+sidebar_label: Errors
+sidebar_position: 3
+---
+
+# Error Handling
+
+When an API request fails, the response includes an error object with details about what went wrong.
+
+```json
+{
+ "message": "Account not found by Address(0x123...)",
+ "error_code": "account_not_found",
+ "vm_error_code": null
+}
+```
+
+The `vm_error_code` field is populated when the error originates from Move VM execution (e.g., abort codes from smart contracts).
+
+---
+
+## Common Errors
+
+**`account_not_found`**: Account has no on-chain state. This happens when querying an address that has never received tokens or published modules.
+
+```bash
+# Fix: Fund the account first
+curl -X POST https://faucet.testnet.cedra.dev/mint?address=0x...
+```
+
+**`resource_not_found`**: The requested resource type doesn't exist on the account. Double-check the full resource path including generics.
+
+```bash
+# Wrong
+/accounts/0x1/resource/0x1::coin::CoinStore
+
+# Correct (with URL-encoded generics)
+/accounts/0x1/resource/0x1::coin::CoinStore%3C0x1::cedra_coin::CedraCoin%3E
+```
+
+**`module_not_found`**: No module with that name exists at the address. Verify the module is published.
+
+**`transaction_not_found`**: Transaction hash not found. Either the transaction hasn't been indexed yet, or the hash is incorrect. Try waiting a few seconds and retry.
+
+**`sequence_number_too_old`**: The sequence number in your transaction has already been used. Query the current sequence number and use a fresh value.
+
+```bash
+curl https://testnet.cedra.dev/v1/accounts/0x.../
+# Use the returned sequence_number for your next transaction
+```
+
+**`insufficient_balance_for_transaction_fee`**: Account doesn't have enough tokens to pay gas fees. Fund the account before submitting transactions.
+
+---
+
+## Pruned Data (410)
+
+Cedra nodes prune historical state to save storage. When you request a `ledger_version` that's been pruned, you get a 410 Gone response.
+
+Check the available range in response headers:
+- `X-CEDRA-LEDGER-VERSION`: current (latest) version
+- `X-CEDRA-LEDGER-OLDEST-VERSION`: oldest available version
+
+To fix:
+1. Query without `ledger_version` parameter to get current state
+2. Use the GraphQL API for historical data (indexes full history)
+
+---
+
+## Rate Limiting (403)
+
+Public endpoints have rate limits. If you hit them:
+
+1. Add delays between requests
+2. Use `limit` and pagination parameters to fetch less data per request
+3. Cache responses when possible
+4. Run your own node for unlimited access
+
+---
+
+## HTTP Status Codes
+
+| Code | Meaning | When it happens |
+|------|---------|-----------------|
+| 200 | Success | Request completed |
+| 400 | Bad Request | Invalid parameters, malformed JSON |
+| 403 | Forbidden | Rate limited |
+| 404 | Not Found | Account, resource, or transaction doesn't exist |
+| 410 | Gone | Requested ledger version has been pruned |
+| 500 | Internal Error | Node error |
+| 503 | Unavailable | Node is syncing or overloaded |
diff --git a/docs/dev-tools/api/rest/examples.mdx b/docs/dev-tools/api/rest/examples.mdx
new file mode 100644
index 0000000..ae3fd68
--- /dev/null
+++ b/docs/dev-tools/api/rest/examples.mdx
@@ -0,0 +1,164 @@
+---
+title: REST API Examples
+sidebar_label: Examples
+sidebar_position: 2
+---
+
+# REST API Examples
+
+Practical curl examples for common operations. Replace `0x1` with your address.
+
+---
+
+## General
+
+**Get ledger info**: Returns current chain state including block height, epoch, and timestamp. Use this to check if the node is synced and get the current ledger version.
+
+```bash
+curl https://testnet.cedra.dev/v1/
+```
+
+**Health check**: Simple endpoint for load balancers and monitoring.
+
+```bash
+curl https://testnet.cedra.dev/v1/-/healthy
+```
+
+Returns `cedra-node:ok` when the node is healthy.
+
+**Get gas price estimate**: Returns recommended gas prices for different priority levels. Use `gas_estimate` for normal transactions, `prioritized_gas_estimate` when you need faster inclusion.
+
+```bash
+curl https://testnet.cedra.dev/v1/estimate_gas_price
+```
+
+```json
+{
+ "gas_estimate": 100,
+ "deprioritized_gas_estimate": 100,
+ "prioritized_gas_estimate": 150
+}
+```
+
+**Get block by height**: Fetch block metadata and transactions for a specific block height.
+
+```bash
+curl https://testnet.cedra.dev/v1/blocks/by_height/1000
+```
+
+**Get recent transactions**: List the latest transactions on the chain. Add `?limit=10` to control the number of results.
+
+```bash
+curl https://testnet.cedra.dev/v1/transactions?limit=10
+```
+
+---
+
+## Accounts
+
+**Get account info**: Returns sequence number (transaction counter) and authentication key.
+
+```bash
+curl https://testnet.cedra.dev/v1/accounts/0x1
+```
+
+```json
+{
+ "sequence_number": "0",
+ "authentication_key": "0x..."
+}
+```
+
+**Get account balance**: Query balance for a specific fungible asset. The asset type follows the format `{address}::{module}::{struct}`.
+
+```bash
+curl https://testnet.cedra.dev/v1/accounts/0x1/balance/0x1::cedra_coin::CedraCoin
+```
+
+```json
+{
+ "balance": "1000000000"
+}
+```
+
+**Get all resources**: Returns all Move resources stored under an account. Useful for exploring what an account holds.
+
+```bash
+curl https://testnet.cedra.dev/v1/accounts/0x1/resources
+```
+
+**Get specific resource**: Fetch a single resource by type. URL-encode the type (replace `<` with `%3C`, `>` with `%3E`).
+
+```bash
+curl https://testnet.cedra.dev/v1/accounts/0x1/resource/0x1::coin::CoinStore%3C0x1::cedra_coin::CedraCoin%3E
+```
+
+**Get account modules**: List all Move modules published by an account.
+
+```bash
+curl https://testnet.cedra.dev/v1/accounts/0x1/modules
+```
+
+---
+
+## View Functions
+
+Call read-only Move functions without submitting a transaction. Useful for reading contract state, checking balances, or previewing calculations.
+
+```bash
+curl -X POST https://testnet.cedra.dev/v1/view \
+ -H "Content-Type: application/json" \
+ -d '{
+ "function": "0x1::coin::balance",
+ "type_arguments": ["0x1::cedra_coin::CedraCoin"],
+ "arguments": ["0x1"]
+ }'
+```
+
+```json
+["1000000000"]
+```
+
+The response is an array of return values from the function.
+
+---
+
+## Transactions
+
+**Get transaction by hash**: Look up a transaction after submission.
+
+```bash
+curl https://testnet.cedra.dev/v1/transactions/by_hash/0x...
+```
+
+**Wait for transaction**: Blocks until the transaction is committed or fails. Useful in scripts to wait for confirmation before proceeding.
+
+```bash
+curl https://testnet.cedra.dev/v1/transactions/wait_by_hash/0x...
+```
+
+**Simulate transaction**: Test a transaction without submitting it. Returns gas usage and any errors. The transaction does not need to be signed.
+
+```bash
+curl -X POST https://testnet.cedra.dev/v1/transactions/simulate \
+ -H "Content-Type: application/json" \
+ -d '{
+ "sender": "0x1",
+ "sequence_number": "0",
+ "max_gas_amount": "100000",
+ "gas_unit_price": "100",
+ "expiration_timestamp_secs": "1735689600",
+ "payload": {
+ "type": "entry_function_payload",
+ "function": "0x1::cedra_account::transfer",
+ "type_arguments": [],
+ "arguments": ["0x2", "1000"]
+ }
+ }'
+```
+
+**Get account transactions**: List all transactions sent by a specific account.
+
+```bash
+curl https://testnet.cedra.dev/v1/accounts/0x1/transactions
+```
diff --git a/docs/dev-tools/api/rest/index.mdx b/docs/dev-tools/api/rest/index.mdx
new file mode 100644
index 0000000..81f126d
--- /dev/null
+++ b/docs/dev-tools/api/rest/index.mdx
@@ -0,0 +1,96 @@
+---
+title: REST API
+sidebar_label: REST API
+sidebar_position: 1
+---
+
+import { CardList } from '@site/src/components/CardList';
+
+# REST API
+
+The Cedra REST API provides direct access to blockchain state and transaction submission. Every response includes headers with current chain state (`X-CEDRA-LEDGER-VERSION`, `X-CEDRA-BLOCK-HEIGHT`, etc.).
+
+Browse all endpoints in the interactive explorer: [testnet.cedra.dev/v1/spec](https://testnet.cedra.dev/v1/spec)
+
+
+
+
+
+
+---
+
+**Accounts**
+
+| Method | Path | Description |
+|--------|------|-------------|
+| GET | `/accounts/{address}` | Get account info |
+| GET | `/accounts/{address}/resources` | Get all resources |
+| GET | `/accounts/{address}/resource/{type}` | Get specific resource |
+| GET | `/accounts/{address}/modules` | Get all modules |
+| GET | `/accounts/{address}/module/{name}` | Get specific module |
+| GET | `/accounts/{address}/balance/{asset_type}` | Get balance |
+| GET | `/accounts/{address}/transactions` | Get account transactions |
+| GET | `/accounts/{address}/events/{creation_number}` | Get events |
+
+**Transactions**
+
+| Method | Path | Description |
+|--------|------|-------------|
+| GET | `/transactions` | List transactions |
+| POST | `/transactions` | Submit transaction |
+| GET | `/transactions/by_hash/{hash}` | Get by hash |
+| GET | `/transactions/by_version/{version}` | Get by version |
+| GET | `/transactions/wait_by_hash/{hash}` | Wait for transaction |
+| POST | `/transactions/simulate` | Simulate transaction |
+| POST | `/transactions/encode_submission` | Encode for signing |
+| POST | `/transactions/batch` | Submit batch |
+
+**Blocks**
+
+| Method | Path | Description |
+|--------|------|-------------|
+| GET | `/blocks/by_height/{height}` | Get block by height |
+| GET | `/blocks/by_version/{version}` | Get block by version |
+
+**View Functions**
+
+| Method | Path | Description |
+|--------|------|-------------|
+| POST | `/view` | Execute read-only Move function |
+
+**General**
+
+| Method | Path | Description |
+|--------|------|-------------|
+| GET | `/` | Ledger info |
+| GET | `/info` | Node info |
+| GET | `/-/healthy` | Health check |
+| GET | `/estimate_gas_price` | Gas price estimate |
+
+## Response Headers
+
+All responses include blockchain state:
+
+| Header | Description |
+|--------|-------------|
+| `X-CEDRA-CHAIN-ID` | Chain ID |
+| `X-CEDRA-LEDGER-VERSION` | Current ledger version |
+| `X-CEDRA-BLOCK-HEIGHT` | Current block height |
+| `X-CEDRA-EPOCH` | Current epoch |
+| `X-CEDRA-LEDGER-TIMESTAMPUSEC` | Timestamp (microseconds) |
+| `X-CEDRA-CURSOR` | Pagination cursor |
+
+## Content Types
+
+| Accept Header | Description |
+|---------------|-------------|
+| `application/json` | JSON (default) |
+| `application/x-bcs` | Binary Canonical Serialization |
diff --git a/docs/guides/escrow.md b/docs/guides/escrow.md
index 7de8168..e6899d7 100644
--- a/docs/guides/escrow.md
+++ b/docs/guides/escrow.md
@@ -7,10 +7,14 @@ sidebar_position: 3
In this guide, we'll walk through how escrow works by explaining its flow. We'll cover how funds are locked, released, or returned in a secure and predictable way.
-The escrow system supports two types of locking:
+The escrow system supports:
* Simple escrow (manual release)
* Time-locked escrow (claimable after a specific time)
+* Partial withdrawals (withdraw portions of escrowed funds)
+* Batch operations (gas-efficient multi-user transactions)
+* Emergency pause controls (halt operations during security incidents)
+* Lockup cleanup (delete empty contracts to reclaim storage)
:::tip Prerequisites
Before starting this guide, make sure you have:
@@ -66,13 +70,17 @@ enum Lockup has key {
extend_ref: ExtendRef,
/// Used to cleanup the Lockup object
delete_ref: DeleteRef,
- escrows: SmartTable
+ /// Maps (asset, user) pairs to their escrow data
+ escrows: BigOrderedMap,
+ /// Emergency pause flag
+ paused: bool
}
}
```
-* It has a field called `escrows`, which is a dynamic map of all ongoing escrows the user has opened.
-* It also holds `extend_ref` and `delete_ref` to manage storage properly.
+* The `escrows` field uses `BigOrderedMap` to store escrow data inline (asset+user key → escrow details).
+* It holds `extend_ref` and `delete_ref` to manage storage and enable cleanup.
+* The `paused` flag allows the creator to halt new escrows during emergencies.
* **Why it's important**: This is your actual escrow registry. Every deposit, claim, or refund is routed through it.
### `Escrow`
@@ -80,17 +88,17 @@ enum Lockup has key {
This is the structure that holds the funds and defines when they can be withdrawn.
```rust
-/// An escrow object for a single user and a single FA
-enum Escrow has key {
+/// An escrow entry stored inline in the Lockup's BigOrderedMap
+enum Escrow has store {
Simple {
original_owner: address,
- delete_ref: DeleteRef,
+ amount: u64,
},
TimeUnlock {
original_owner: address,
/// Time that the funds can be unlocked
unlock_secs: u64,
- delete_ref: DeleteRef,
+ amount: u64,
}
}
```
@@ -99,10 +107,10 @@ enum Escrow has key {
* `Simple`: can be claimed or refunded anytime.
* `TimeUnlock`: can only be touched after a specific unlock time.
-* Each escrow also knows who originally sent the funds and has a `delete_ref` for cleanup.
+* Each escrow stores the `amount` inline and knows who originally sent the funds.
:::info **Storage Management**
-Notice how each structure has a `delete_ref`? This enables automatic storage cleanup and refunds when escrows are completed, keeping the blockchain efficient.
+The `Lockup` object has a `delete_ref` that enables cleanup when all escrows are cleared, allowing creators to reclaim storage deposits.
:::
## Step 1: Creating a Lockup
@@ -408,7 +416,175 @@ After confirming that you're allowed to withdraw, the function double-checks tha
Finally, the funds are moved from the escrow's storage back into your account, and the escrow object is deleted to clean up the on-chain state.
-## Step 4: Checking Status
+### Partial Withdrawal
+
+The `partial_withdraw` function lets you withdraw a specific amount while keeping the rest locked.
+
+```move
+public entry fun partial_withdraw(
+ caller: &signer,
+ lockup_obj: Object,
+ fa_metadata: Object,
+ amount: u64,
+) acquires Lockup
+```
+
+The function:
+1. Checks time lock has expired (if applicable)
+2. Verifies you have enough balance
+3. Transfers the requested amount to your account
+4. Updates or removes the escrow record
+
+Useful for vesting schedules where team members withdraw tokens gradually instead of all at once.
+
+## Step 4: Batch Operations
+
+When dealing with multiple users - like setting up team vesting or distributing rewards - individual transactions become expensive. Batch operations solve this by processing multiple escrows in a single transaction.
+
+### Batch Escrow
+
+The `batch_escrow_with_time` function allows the lockup creator to deposit funds for multiple users at once:
+
+```rust
+/// Batch escrow funds for multiple users with time lock
+public entry fun batch_escrow_with_time(
+ caller: &signer,
+ lockup_obj: Object,
+ fa_metadata: Object,
+ users: vector,
+ amounts: vector,
+ lockup_time_secs: u64,
+) acquires Lockup {
+ let caller_address = signer::address_of(caller);
+ let lockup = get_lockup_mut(&lockup_obj);
+ assert!(caller_address == lockup.creator, E_NOT_ORIGINAL_OR_LOCKUP_OWNER);
+ assert!(!lockup.paused, E_CONTRACT_PAUSED);
+
+ let len = vector::length(&users);
+ assert!(len > 0, E_EMPTY_BATCH);
+ assert!(len == vector::length(&amounts), E_LENGTH_MISMATCH);
+
+ let unlock_secs = timestamp::now_seconds() + lockup_time_secs;
+
+ for (i in 0..len) {
+ let user = *vector::borrow(&users, i);
+ let amount = *vector::borrow(&amounts, i);
+ // Create or update escrow for each user
+ // ...
+ }
+}
+```
+
+### Batch Return
+
+Similarly, `batch_return_user_funds` returns funds to multiple users in one transaction:
+
+```rust
+/// Batch return funds to multiple users
+public entry fun batch_return_user_funds(
+ caller: &signer,
+ lockup_obj: Object,
+ fa_metadata: Object,
+ users: vector,
+) acquires Lockup {
+ let caller_address = signer::address_of(caller);
+ let lockup = get_lockup_mut(&lockup_obj);
+ assert!(caller_address == lockup.creator, E_NOT_ORIGINAL_OR_LOCKUP_OWNER);
+
+ let len = vector::length(&users);
+ assert!(len > 0, E_EMPTY_BATCH);
+
+ for (i in 0..len) {
+ let user = *vector::borrow(&users, i);
+ // Return funds to each user
+ // ...
+ }
+}
+```
+
+:::tip **Gas Savings**
+Batch operations can save up to 90% on gas compared to individual transactions when processing 10+ users.
+:::
+
+## Step 5: Emergency Controls
+
+The escrow contract includes pause functionality for emergency situations. Only the lockup creator can pause and unpause the contract.
+
+### Pausing the Contract
+
+```rust
+/// Pause the lockup contract (creator only)
+public entry fun pause_lockup(
+ caller: &signer,
+ lockup_obj: Object,
+) acquires Lockup {
+ let caller_address = signer::address_of(caller);
+ let lockup = get_lockup_mut(&lockup_obj);
+ assert!(caller_address == lockup.creator, E_NOT_ORIGINAL_OR_LOCKUP_OWNER);
+ lockup.paused = true;
+}
+```
+
+### Unpausing the Contract
+
+```rust
+/// Unpause the lockup contract (creator only)
+public entry fun unpause_lockup(
+ caller: &signer,
+ lockup_obj: Object,
+) acquires Lockup {
+ let caller_address = signer::address_of(caller);
+ let lockup = get_lockup_mut(&lockup_obj);
+ assert!(caller_address == lockup.creator, E_NOT_ORIGINAL_OR_LOCKUP_OWNER);
+ lockup.paused = false;
+}
+```
+
+:::warning **Pause Behavior**
+When paused, new escrows are blocked. However, users can still withdraw their existing funds - this prevents lock-in during emergencies.
+:::
+
+## Step 6: Lockup Cleanup
+
+When all escrows have been cleared, the creator can delete the lockup contract to reclaim storage deposits.
+
+```rust
+/// Delete an empty lockup contract
+public entry fun delete_lockup(
+ caller: &signer,
+) acquires LockupRef, Lockup {
+ let caller_address = signer::address_of(caller);
+
+ assert!(exists(caller_address), E_LOCKUP_NOT_FOUND);
+ let LockupRef { lockup_address } = move_from(caller_address);
+
+ let Lockup::ST {
+ creator,
+ extend_ref: _,
+ delete_ref,
+ escrows,
+ paused: _
+ } = move_from(lockup_address);
+
+ assert!(creator == caller_address, E_NOT_ORIGINAL_OR_LOCKUP_OWNER);
+ assert!(big_ordered_map::is_empty(&escrows), E_LOCKUP_HAS_ESCROWS);
+
+ big_ordered_map::destroy_empty(escrows);
+ object::delete(delete_ref);
+}
+```
+
+You can check if a lockup exists before attempting operations:
+
+```rust
+#[view]
+/// Check if a creator has an active lockup contract
+public fun has_lockup(creator: address): bool {
+ exists(creator)
+}
+```
+
+## Step 7: Checking Status
As you interact with escrow contracts - whether you're locking, claiming, or returning tokens - it’s important to have visibility into the state of your escrows. The module provides several view-only functions that help you do exactly that.
@@ -492,21 +668,102 @@ public fun remaining_escrow_time(
If the escrow is of type `Simple`, the function will return 0. If it's a `TimeUnlock` escrow, it subtracts the current blockchain timestamp from the unlock timestamp.
+Use `is_paused` to check if a lockup contract is currently paused:
+
+```rust
+#[view]
+/// Check if lockup contract is paused
+public fun is_paused(lockup_obj: Object): bool acquires Lockup {
+ let lockup = get_lockup(&lockup_obj);
+ lockup.paused
+}
+```
+
+Use `get_creator` to get the creator/owner address of a lockup:
+
+```rust
+#[view]
+/// Get the creator address of a lockup
+public fun get_creator(lockup_obj: Object): address acquires Lockup {
+ let lockup = get_lockup(&lockup_obj);
+ lockup.creator
+}
+```
+
+Use `escrow_exists` to check if an escrow exists for a specific user-asset pair:
+
+```rust
+#[view]
+/// Check if an escrow exists for a user-asset pair
+public fun escrow_exists(
+ lockup_obj: Object,
+ fa_metadata: Object,
+ user: address
+): bool acquires Lockup {
+ let lockup = get_lockup(&lockup_obj);
+ let escrow_key = EscrowKey::FAPerUser {
+ fa_metadata,
+ user
+ };
+ lockup.escrows.contains(&escrow_key)
+}
+```
+
:::info **Frontend Integration**
These view functions are perfect for building dashboards and user interfaces. They don't consume gas and provide real-time escrow status without any side effects.
:::
-You now understand escrow as a user flow - not just code.
+## Step 8: Events
+
+All escrow operations emit events for monitoring and indexing. These events can be used to build real-time dashboards or track contract activity.
+
+| Event | Description |
+|-------|-------------|
+| `EscrowCreatedEvent` | Emitted when a new escrow is created |
+| `FundsAddedEvent` | Emitted when funds are added to an existing escrow |
+| `FundsReturnedEvent` | Emitted when funds are returned to the original owner |
+| `FundsClaimedEvent` | Emitted when the creator claims escrowed funds |
+| `PartialWithdrawalEvent` | Emitted when a partial withdrawal occurs |
+| `LockupPausedEvent` | Emitted when the contract is paused |
+| `LockupUnpausedEvent` | Emitted when the contract is unpaused |
+
+## Error Codes
+
+The contract defines the following error codes:
+
+| Code | Name | Description |
+|------|------|-------------|
+| 1 | `E_LOCKUP_ALREADY_EXISTS` | Lockup already initialized for this account |
+| 2 | `E_LOCKUP_NOT_FOUND` | No lockup exists for this account |
+| 3 | `E_NO_USER_LOCKUP` | No escrow found for this user-asset pair |
+| 4 | `E_UNLOCK_TIME_NOT_YET` | Time lock hasn't expired yet |
+| 5 | `E_NOT_ORIGINAL_OR_LOCKUP_OWNER` | Caller is not authorized for this operation |
+| 6 | `E_NOT_TIME_LOCKUP` | Expected a time-locked escrow |
+| 7 | `E_NOT_SIMPLE_LOCKUP` | Expected a simple escrow |
+| 8 | `E_CANNOT_SHORTEN_LOCKUP_TIME` | Cannot reduce existing lock time |
+| 9 | `E_INVALID_AMOUNT` | Amount must be greater than 0 |
+| 10 | `E_CONTRACT_PAUSED` | Contract is currently paused |
+| 11 | `E_INSUFFICIENT_BALANCE` | Not enough funds for withdrawal |
+| 12 | `E_LENGTH_MISMATCH` | Batch vectors have different lengths |
+| 13 | `E_EMPTY_BATCH` | Batch operation cannot be empty |
+| 14 | `E_LOCKUP_HAS_ESCROWS` | Cannot delete lockup with active escrows |
## Conclusion
-At the heart of the system are three key components: `LockupRef`, which links a user account to their lockup manager; `Lockup`, an on-chain object that holds and manages escrow entries; and `Escrow`, which stores actual tokens and controls how they can be claimed or returned. These abstractions separate ownership, logic, and control - giving developers and users a clean, auditable lifecycle for escrowed funds.
+At the heart of the system are three key components: `LockupRef`, which links a user account to their lockup manager; `Lockup`, an on-chain object that holds and manages escrow entries; and `Escrow`, which stores tokens and controls how they can be claimed or returned.
+
+The escrow contract provides:
-Escrow flows begin with a one-time lockup initialization, which sets up a new `Lockup` object and stores a reference to it in the user's account. From there, funds can be deposited into escrow via two entry points: a simple version with no time restrictions and a time-locked version that prevents access until a specified timestamp. Each deposit creates or reuses an `Escrow` object tied to a unique (token, user) pair.
+* **Two escrow types**: Simple (manual release) and time-locked (automatic after timestamp)
+* **Flexible withdrawals**: Full or partial withdrawal support
+* **Batch operations**: Gas-efficient multi-user transactions
+* **Emergency controls**: Pause mechanism for security incidents
+* **Storage management**: Cleanup functions to reclaim deposits
+* **Full observability**: Events for all operations
### What's Next?
Now that you understand how escrow works, you can:
-* **Build your own tokens**: Create fungible assets using our [First FA Guide](/guides/first-fa)
+* **Build your own tokens**: Create fungible assets using our [First FA Guide](/guides/first-fa)
* **Explore more contracts**: Check out other [Move examples](https://github.com/cedra-labs/move-contract-examples) for inspiration
diff --git a/sidebars.ts b/sidebars.ts
index 9d7834b..8b7541a 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -351,6 +351,51 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
id: 'cli/usage',
label: 'CLI Reference'
},
+ {
+ type: 'category',
+ label: 'API Reference',
+ link: {
+ type: 'doc',
+ id: 'dev-tools/api/index',
+ },
+ items: [
+ {
+ type: 'category',
+ label: 'REST API',
+ link: {
+ type: 'doc',
+ id: 'dev-tools/api/rest/index',
+ },
+ items: [
+ {
+ type: 'doc',
+ id: 'dev-tools/api/rest/examples',
+ label: 'Examples'
+ },
+ {
+ type: 'doc',
+ id: 'dev-tools/api/rest/errors',
+ label: 'Errors'
+ }
+ ]
+ },
+ {
+ type: 'category',
+ label: 'GraphQL API',
+ link: {
+ type: 'doc',
+ id: 'dev-tools/api/graphql/index',
+ },
+ items: [
+ {
+ type: 'doc',
+ id: 'dev-tools/api/graphql/queries',
+ label: 'Query Examples'
+ }
+ ]
+ }
+ ]
+ },
{
type: 'category',
label: 'SDKs',