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',