diff --git a/docs/smart-contracts/global-contracts.md b/docs/smart-contracts/global-contracts.md index 353637bd52..744d727025 100644 --- a/docs/smart-contracts/global-contracts.md +++ b/docs/smart-contracts/global-contracts.md @@ -1,64 +1,99 @@ --- id: global-contracts title: Global Contracts -sidebar_label: Global Contracts +sidebar_label: Introduction description: "Global contracts allow smart contracts to be deployed once and reused by any account without incurring high storage costs." --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; Global contracts allow smart contracts to be deployed once and reused by any account without incurring high storage costs. +Rather than deploying duplicate contracts or routing messages inefficiently across shards, NEAR developers can now think modularly and universally: write once, use everywhere. ---- +## Overview -## Deploying a Global Contract +If you've ever deployed the same contract code to multiple accounts, you’ve likely noticed that each deployment requires you to pay the full storage cost again — since the size of the WASM file determines how much `NEAR` is locked on the account. +With [NEP-0591](https://github.com/near/NEPs/blob/master/neps/nep-0591.md), which introduces Global Contracts, NEAR provides a highly strategic alternative that [solves this elegantly](#solution). -Global contracts can be deployed in 2 ways: either by their hash or by the owner account ID. -Contracts deployed by hash are effectively immutable and cannot be updated. -When deployed by account id the owner can redeploy the contract updating it for all its users. +## Key Features + +These are the features that make Global Contracts special: + +- **Global Addressing**: These contracts are not tied to a specific account but instead use a unique global identifier. This enables any contract, user, or application on NEAR to call the contract from any shard instantly. + +- **Immutable Logic**: The contract code is fixed once deployed, making it a trusted point of reference. This ensures consistency and security—ideal for system-critical protocols. + +- **Shared Infrastructure**: Global contracts can act as canonical libraries, utility hubs, or standards for other contracts to rely on, simplifying development and reducing duplication. -Global contracts can be deployed using `NEAR CLI`. -The process is similar to [deploying a regular contract](./release/deploy.md#deploying-the-contract) but `deploy-as-global` command should be used instead of `deploy`. +- **Cross-Shard Superpowers**: Developers can build truly modular apps where parts of their stack reside on different shards but communicate via shared global logic with minimal latency or duplication. - - +### Use Cases - ```bash - near contract deploy-as-global use-file as-global-hash network-config testnet sign-with-keychain send - ``` - +- **Standard Libraries**: Reusable components for math, string operations, or token interfaces. +- **DeFi Protocols**: Global contracts can anchor DEXs, lending markets, oracles—shared across all applications. +- **DAO Frameworks**: Shared governance modules that any DAO can plug into, ensuring consistency and reliability. +- **Identity & Credentials**: One global contract can manage decentralized identity verification and access management for the entire chain. +- **Multi-part dApps**: Complex applications can split responsibilities across shards while accessing a common logic core. - +## Solution + +Global Contracts solve the inefficiency of duplicate deployments by allowing the same contract code to be shared across multiple accounts, so storage cost is paid only once. + +There are two ways to reference a global contract: +- **[By Account](#reference-by-account)**: an upgradable contract is published globally under a specific account ID. +- **[By Hash](#reference-by-hash)**: an immutable contract is deployed globally and identified by its code hash. - ```bash - near contract deploy-as-global use-file as-global-account-id network-config testnet sign-with-keychain send - ``` - - :::info -Note that deploying a global contract incurs high storage costs. Tokens are burned to compensate for storing the contract on-chain, unlike regular contracts where tokens are locked based on contract size. +- The contract code is distributed across all shards in the Near Protocol network, not stored inside any specific account’s storage. +- The account is charged 10x more for deploying a Global Contract, at the rate 10 NEAR per 100KB. +- This amount is entirely burnt and cannot be recovered later, unlike regular deployments where Near is simply locked. +- The total fee is typically under 0.001 NEAR for a user to use a Global Contract, since only a few bytes are needed for the reference that is stored in the account's storage. ::: -## Using a Global Contract +### Reference by Account + +When using a reference **by account**, the contract code is tied to another account. If that account later deploys a new version of the contract, your account will automatically start using the updated code, with no need for redeployment. + +### Reference by Hash + +When using a reference **by hash**, you reference the global contract by its immutable code hash. This ensures you're always using the exact same version, and it will never change unless you explicitly redeploy with a different hash. + +## When to use Global Contracts -A previously deployed global contract can be attached to any NEAR account using `NEAR CLI` `deploy` command. Such a contract behaves exactly like a regular contract. +Ask yourself the following questions before deciding how to deploy your contracts: - - +- **_Are you working in a local environment?_** + + If you're just testing or building a prototype, [regular deployments](release/deploy.md) are simpler and more flexible. There's no need to burn tokens or register global references — just deploy and iterate. - ```bash - # Using global contract deployed by hash - near contract deploy use-global-hash without-init-call network-config testnet - ``` - +- **_Is the contract supposed to be deployed on many accounts?_** - + If the same contract will be reused across many independent accounts — say, 10 or more — Global Contracts can significantly reduce overall cost and complexity. But if only a few accounts are involved, regular deployment remains the more economical choice. - ```bash - # Using global contract deployed by account id - near contract deploy use-global-account-id without-init-call network-config testnet - ``` - - +- **_Are these accounts managed by your team?_** + If all target accounts are under your infrastructure, you may prefer regular deployments for flexibility and cost recovery. +- **_Are there more than 10 accounts?_** + + Global Contracts become financially efficient when reused at scale. If you're deploying the same contract to more than 10 accounts, it's likely worth considering. + +- **_Do you need to upgrade the contract across many accounts in one step, even if it requires burning tokens?_** + + If you want to be able to push updates to all deployed instances at once, then go with Global Contracts by Account ID, but keep in mind that the deployment cost is non-refundable. + +- **_Does your use case require the contract to be permanently immutable?_** + + If the contract must never change, for example, due to security, compliance, or user trust, then using a Global Contract by Code Hash ensures immutability at the protocol level. + +## Deploying a Global Contract + +Global contracts can be deployed in 2 ways: either by their [hash](#reference-by-hash) or by the owner [account ID](#reference-by-account). +Contracts deployed by hash are effectively immutable and cannot be updated. +When deployed by account ID the owner can redeploy the contract updating it for all its users. + +Global contracts can be deployed using [`NEAR CLI`](#) or by code using [NEAR APIs](#). + +:::info +Note that deploying a global contract incurs high storage costs. Tokens are burned to compensate for storing the contract on-chain, unlike regular contracts where tokens are locked based on contract size. +::: diff --git a/docs/tools/near-api.md b/docs/tools/near-api.md index 2e6919022c..b9b43ef8e0 100644 --- a/docs/tools/near-api.md +++ b/docs/tools/near-api.md @@ -1167,13 +1167,11 @@ Unlike many other blockchains, contracts on NEAR are mutable, meaning you have t ### Deploy a Global Contract {#deploy-a-global-contract} -If you've ever deployed the same contract code to multiple accounts, you’ve likely noticed that each deployment requires you to pay the full storage cost again — since the size of the WASM file determines how much `NEAR` is locked on the account. - -Global Contracts solve this inefficiency by allowing the same contract code to be shared across multiple accounts, so storage cost is paid only once. +[Global contracts](../smart-contracts/global-contracts.md) allow smart contracts to be deployed once and reused by any account without incurring high storage costs. There are two ways to reference a global contract: -- **By account:** The contract code is tied to another account. If that account later deploys a new version of the contract, your account will automatically start using the updated code, with no need for redeployment. -- **By hash:** You reference the contract by its immutable code hash. This ensures you're always using the exact same version, and it will never change unless you explicitly redeploy with a different hash. +- **[By account](../smart-contracts/global-contracts.md#reference-by-account):** The contract code is tied to another account. +- **[By hash](../smart-contracts/global-contracts.md#reference-by-hash):** You reference the contract by its immutable code hash. @@ -1196,14 +1194,6 @@ There are two ways to reference a global contract: await account.deployGlobalContract(wasm, "accountId"); ``` - Once the global contract is deployed, let’s see how an end user can reference and use it in their own account. To do this, they need to call the `useGlobalContract` function and pass the source `accountId` where the contract was originally deployed. - - ```js - const account = new Account("another_user.testnet", provider, signer); - - await account.useGlobalContract({ accountId: "user.testnet" }); - ``` - See full example on GitHub @@ -1224,7 +1214,96 @@ There are two ways to reference a global contract: await account.deployGlobalContract(wasm, "codeHash"); ``` - Once the global contract is deployed, let’s see how an end user can reference and use it in their own account. To do this, they need to call the `useGlobalContract` function and pass the source `codeHash` of the original contract. + + See full example on GitHub + + + + + + + + + Once you've created an Account instance, you can deploy your regular contract as a global contract. + + + + + Let’s look at an example of deploying a global contract by account. + + To do this, use the `deploy_global_contract_code` function and use the method `as_account_id`, along with the contract’s code bytes. + + ```rust + let global_account_id: AccountId = "nft-contract.testnet".parse().unwrap(); + let code = std::fs::read("path/to/your/contract.wasm").unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy_global_contract_code(code) + .as_account_id(global_account_id) + .with_signer(signer) + .send_to_testnet() + .await.unwrap(); + ``` + + + See full example on GitHub + + + + + + Let’s look at an example of deploying a global contract by hash. + + To do this, use the `deploy_global_contract_code` function and use the method `as_hash`, along with the contract’s code bytes. + + ```rust + let account_id: AccountId = "my-account.testnet".parse().unwrap(); + let code = std::fs::read("path/to/your/contract.wasm").unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy_global_contract_code(code) + .as_hash() + .with_signer(account_id, signer) + .send_to_testnet() + .await.unwrap(); + ``` + + + See full example on GitHub + + + + + + + + +### Use a Global Contract + +Once a [global contract](../smart-contracts/global-contracts.md) has been [deployed](#deploy-a-global-contract), let’s see how you can reference and use it from another account. + + + + + + + + To reference a global contract by account, you need to call the `useGlobalContract` function and pass the source `accountId` where the contract was originally deployed. + + ```js + const account = new Account("another_user.testnet", provider, signer); + + await account.useGlobalContract({ accountId: "user.testnet" }); + ``` + + + See full example on GitHub + + + + + + To reference a global contract by hash, you need to call the `useGlobalContract` function and pass the source `codeHash` of the original contract. ```js const account = new Account("another_user.testnet", provider, signer); @@ -1240,9 +1319,58 @@ There are two ways to reference a global contract: + + + + + + To reference a global contract by account, you need to call the `use_global_account_id` function and pass the source `accountId` where the contract was originally deployed. + + ```rust + let global_account_id: AccountId = "nft-contract.testnet".parse().unwrap(); + let my_account_id: AccountId = "my-contract.testnet".parse().unwrap(); + let my_signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy(my_account_id) + .use_global_account_id(global_account_id) + .without_init_call() + .with_signer(my_signer) + .send_to_testnet() + .await.unwrap(); + ``` + + + See full example on GitHub + + + + + To reference a global contract by hash, you need to call the `use_global_hash` function and pass the source `hash` of the original contract. + + ```rust + let global_hash: types::CryptoHash = "DxfRbrjT3QPmoANMDYTR6iXPGJr7xRUyDnQhcAWjcoFF".parse().unwrap(); + let account_id: AccountId = "my-contract.testnet".parse().unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy(account_id) + .use_global_hash(global_hash) + .without_init_call() + .with_signer(signer) + .send_to_testnet() + .await.unwrap(); + ``` + + + See full example on GitHub + + + + + + --- diff --git a/docs/tutorials/examples/global-contracts.md b/docs/tutorials/examples/global-contracts.md new file mode 100644 index 0000000000..86e1b888b8 --- /dev/null +++ b/docs/tutorials/examples/global-contracts.md @@ -0,0 +1,330 @@ +--- +id: global-contracts +title: Global Contracts +sidebar_label: Global Contracts +description: "Learn how to deploy a Global contract and use it from another account." +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +If you've ever deployed the same contract code to multiple accounts, you’ve likely noticed that each deployment requires you to pay the full storage cost again. + +Imagine that you want to deploy the same 500 KB contract to three accounts: each deployment pays 5 NEAR for the storage — a total of 15 NEAR is locked just to hold identical code. +[Global Contracts](../../smart-contracts/global-contracts.md) eliminates this redundancy by allowing the same contract code to be shared across multiple accounts, so storage cost is paid only once. + +In this tutorial you'll learn how to deploy and use a [global contract by Account ID](#global-contract-by-account-id) or [by Hash](#global-contract-by-hash), depending on your needs. + +:::info Global Contract types +- [By Account](../../smart-contracts/global-contracts.md#reference-by-account): an upgradable contract is published globally under a specific account ID. +- [By Hash](../../smart-contracts/global-contracts.md#reference-by-hash): an immutable contract is deployed globally and identified by its code hash. +::: + +## Examples + +Let's define two example smart contracts that we can deploy using Global Contracts, and evaluate which alternative (by Account ID or by Hash) is the best for each case. + +- **DAO Factory**: A tool that allows any user to deploy their own DAO governance instance. +- **NFT Collection Factory**: A service that lets users create their own NFT contracts with fixed metadata and royalty values. + +:::tip Do you need a Global Contract? +Not sure if you need a Global Contract? Check these questions to decide [when to use a global contract](../../smart-contracts/global-contracts.md#when-to-use-global-contracts). +::: + +### Global Contract by Account ID + +For example, let's consider the **DAO Factory** case. + +Since the same contract is deployed many times by end users across different accounts, this clearly meets the threshold where [Global Contracts](../../smart-contracts/global-contracts.md) become financially efficient. Also, since upgradeability may be useful down the line (e.g., to patch bugs or extend functionality), it's a great case to use a [Global Contract by Account ID](../../smart-contracts/global-contracts.md#reference-by-account). + +### Global Contract by Hash + +Now let's take for example the **NFT Collection Factory** case. + +Since each user deploys the same contract, but once deployed, it should never change (security and immutability are critical), this makes a perfect case to use a [Global Contract by Hash](../../smart-contracts/global-contracts.md#reference-by-hash). + +## Deploying a Global Contract + +Next, let's see how you can deploy a global contract by Account ID (for the DAO Factory), or by Hash (for the NFT Collection Factory). +As stated, Global contracts can be deployed in 2 ways: +- by their [hash](../../smart-contracts/global-contracts.md#reference-by-hash). +- by the owner [account ID](../../smart-contracts/global-contracts.md#reference-by-account). + +Contracts deployed by hash are effectively immutable and cannot be updated. +When deployed by account ID the owner can redeploy the contract updating it for all its users. + +:::info +Note that deploying a global contract incurs high storage costs. Tokens are burned to compensate for storing the contract on-chain, unlike regular contracts where tokens are locked based on contract size. +::: + + +### Deployment + +Global contracts can be deployed using [`NEAR CLI`](../../tools/cli.md) or by code using [NEAR APIs](../../tools/near-api.md#deploy-a-global-contract) (JavaScript and Rust). + +Since there are two ways to reference a global contract ([by account](../../smart-contracts/global-contracts.md#reference-by-account) or [by hash](../../smart-contracts/global-contracts.md#reference-by-hash)), the deployment step depends on the type of global contract that you want to store on the blockchain. + + + + + The process is similar to [deploying a regular contract](../../smart-contracts/release/deploy.md#deploying-the-contract) but `deploy-as-global` command should be used instead of `deploy`. + + + + + To deploy a global contract by account, you need to use `as-global-account-id` and pass the source `` where the contract will be deployed. + + ```bash + near contract deploy-as-global use-file as-global-account-id network-config testnet sign-with-keychain send + ``` + + + + + + To deploy a global contract by hash, you need to use `as-global-hash` function and pass the source `` to fund the transaction. + + ```bash + near contract deploy-as-global use-file as-global-hash network-config testnet sign-with-keychain send + ``` + + + + + + + + Once you've created an Account instance, you can deploy your regular contract as a global contract. + + + + + Let’s look at an example of deploying a global contract by account. + + To do this, use the `deployGlobalContract` function and set the mode to `accountId`, along with the contract’s code bytes. + + ```js + import { readFileSync } from "fs"; + + const account = new Account("user.testnet", provider, signer); + + const wasm = readFileSync("../contracts/contract.wasm"); + await account.deployGlobalContract(wasm, "accountId"); + ``` + + + See full example on GitHub + + + + + + Let’s look at an example of deploying a global contract by hash. + + To do this, use the `deployGlobalContract` function and set the mode to `codeHash`, along with the contract’s code bytes. + + ```js + import { readFileSync } from "fs"; + + const account = new Account("user.testnet", provider, signer); + + const wasm = readFileSync("../contracts/contract.wasm"); + await account.deployGlobalContract(wasm, "codeHash"); + ``` + + + See full example on GitHub + + + + + + + + + Once you've created an Account instance, you can deploy your regular contract as a global contract. + + + + + Let’s look at an example of deploying a global contract by account. + + To do this, use the `deploy_global_contract_code` function and use the method `as_account_id`, along with the contract’s code bytes. + + ```rust + let global_account_id: AccountId = "nft-contract.testnet".parse().unwrap(); + let code = std::fs::read("path/to/your/contract.wasm").unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy_global_contract_code(code) + .as_account_id(global_account_id) + .with_signer(signer) + .send_to_testnet() + .await.unwrap(); + ``` + + + See full example on GitHub + + + + + + Let’s look at an example of deploying a global contract by hash. + + To do this, use the `deploy_global_contract_code` function and use the method `as_hash`, along with the contract’s code bytes. + + ```rust + let account_id: AccountId = "my-account.testnet".parse().unwrap(); + let code = std::fs::read("path/to/your/contract.wasm").unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy_global_contract_code(code) + .as_hash() + .with_signer(account_id, signer) + .send_to_testnet() + .await.unwrap(); + ``` + + + See full example on GitHub + + + + + + + + +:::tip +Check the [NEAR API reference documentation](../../tools/near-api.md#deploy-a-global-contract) for complete code examples. +::: + +### Usage + +Once a [global contract](../../smart-contracts/global-contracts.md) has been [deployed](#deployment), it can be attached to any NEAR account using [`NEAR CLI`](../../tools/cli.md) or by code using [NEAR APIs](../../tools/near-api.md#use-a-global-contract) (JavaScript and Rust). + +Let’s see how you can reference and use your global contract from another account. + + + + + Use `near deploy` command. Such a contract behaves exactly like a regular contract. + + + + + To reference a global contract by account, you need to call the `useGlobalContract` function and pass the source `accountId` where the contract was originally deployed. + + ```bash + # Using global contract deployed by account id + near contract deploy use-global-account-id without-init-call network-config testnet + ``` + + + + + + To reference a global contract by hash, you need to call the `useGlobalContract` function and pass the source `codeHash` of the original contract. + + ```bash + # Using global contract deployed by hash + near contract deploy use-global-hash without-init-call network-config testnet + ``` + + + + + + + + + + + To reference a global contract by account, you need to call the `useGlobalContract` function and pass the source `accountId` where the contract was originally deployed. + + ```js + const account = new Account("another_user.testnet", provider, signer); + + await account.useGlobalContract({ accountId: "user.testnet" }); + ``` + + + See full example on GitHub + + + + + + To reference a global contract by hash, you need to call the `useGlobalContract` function and pass the source `codeHash` of the original contract. + + ```js + const account = new Account("another_user.testnet", provider, signer); + + await account.useGlobalContract({ + codeHash: "36b15ea09f737220583f63ad120d91b7e233d2039bebea43be527f8fd85450c9", + }); + ``` + + + See full example on GitHub + + + + + + + + + + + + To reference a global contract by account, you need to call the `use_global_account_id` function and pass the source `accountId` where the contract was originally deployed. + + ```rust + let global_account_id: AccountId = "nft-contract.testnet".parse().unwrap(); + let my_account_id: AccountId = "my-contract.testnet".parse().unwrap(); + let my_signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy(my_account_id) + .use_global_account_id(global_account_id) + .without_init_call() + .with_signer(my_signer) + .send_to_testnet() + .await.unwrap(); + ``` + + + See full example on GitHub + + + + + + To reference a global contract by hash, you need to call the `use_global_hash` function and pass the source `hash` of the original contract. + + ```rust + let global_hash: types::CryptoHash = "DxfRbrjT3QPmoANMDYTR6iXPGJr7xRUyDnQhcAWjcoFF".parse().unwrap(); + let account_id: AccountId = "my-contract.testnet".parse().unwrap(); + let signer = Signer::new(Signer::from_secret_key(private_key)).unwrap(); + + let result: FinalExecutionOutcomeView = Contract::deploy(account_id) + .use_global_hash(global_hash) + .without_init_call() + .with_signer(signer) + .send_to_testnet() + .await.unwrap(); + ``` + + + See full example on GitHub + + + + + + + + +:::tip +Check the [NEAR API reference documentation](../../tools/near-api.md#use-a-global-contract) for complete code examples. +::: diff --git a/website/sidebars.js b/website/sidebars.js index 875ac44db0..728857fe7b 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -205,7 +205,9 @@ const sidebar = { ], }, { - Advanced: ['smart-contracts/global-contracts'], + 'Global Contracts': [ + 'smart-contracts/global-contracts' + ], }, 'resources/contracts-list' ], @@ -375,6 +377,7 @@ const sidebar = { 'tutorials/examples/xcc', 'tutorials/examples/advanced-xcc', 'tutorials/examples/update-contract-migrate-state', + 'tutorials/examples/global-contracts', { "Build a FT Contract from Scratch": [ 'tutorials/fts/introduction',