Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/tutorials/factory/0-introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ Before we begin, it's important to understand NEAR's account creation rules:

This means your factory at `factory.testnet` can create `dao1.factory.testnet` but cannot create `dao1.alice.testnet`.

Ready to get started? Let's [build your first factory contract](1-factory-contract.md)!
Ready to get started? Let's [build your first factory contract](1-factory-contract.md)!
133 changes: 133 additions & 0 deletions docs/tutorials/factory/1-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
id: setup
title: Project Setup and Structure
sidebar_label: Project Setup
---

import {Github} from "@site/src/components/codetabs"

Let's set up a factory contract project that can deploy contracts using NEAR's global contracts feature.

## Create the Project

Initialize a new Rust project:

```bash
cargo new factory-contract --lib
cd factory-contract
```

## Configure Cargo.toml

Set up the dependencies and build configuration:

<Github fname="Cargo.toml"
url="https://github.com/near-examples/factory-rust/blob/main/Cargo.toml"
start="1" end="28" />

Key dependencies:
- `near-sdk` with `global-contracts` feature enables global contract functionality
- `bs58` for decoding base58-encoded code hashes
- `near-workspaces` for integration testing

## Rust Toolchain

Configure the Rust toolchain for WASM compilation:

<Github fname="rust-toolchain.toml"
url="https://github.com/near-examples/factory-rust/blob/main/rust-toolchain.toml"
start="1" end="4" />

## Project Structure

Create the following file structure:

```
factory-contract/
├── Cargo.toml
├── rust-toolchain.toml
├── src/
│ ├── lib.rs # Main contract logic
│ └── manager.rs # Configuration management
└── tests/
└── workspaces.rs # Integration tests
```

## Contract State

Define the contract state that stores the global contract reference:

<Github fname="lib.rs"
url="https://github.com/near-examples/factory-rust/blob/main/src/lib.rs"
start="7" end="13" />

The `GlobalContractId` enum allows referencing a global contract in two ways:
- **AccountId**: Reference the account that deployed the global contract
- **CodeHash**: Reference the global contract by its code hash directly

## Initialize the Factory

Set up the default state:

<Github fname="lib.rs"
url="https://github.com/near-examples/factory-rust/blob/main/src/lib.rs"
start="15" end="29" />

By default:
- References the global Fungible Token contract on testnet
- Requires minimum 0.2 NEAR deposit for sub-account creation

## Build the Contract

Compile the contract to WASM:

```bash
cargo near build
```

This creates an optimized WASM file in `target/near/`.

## Deploy to Testnet

Create a testnet account and deploy:

```bash
# Create a dev account
cargo near create-dev-account

# Deploy the contract
cargo near deploy <your-dev-account>
```

Your factory is now deployed and ready to create sub-accounts!

## Understanding Global Contract References

### By Account ID

When you reference a global contract by account ID:

```rust
GlobalContractId::AccountId("ft.globals.primitives.testnet".parse().unwrap())
```

The factory will look up the most recent global contract deployed by that account.

### By Code Hash

When you reference a global contract by hash:

```rust
GlobalContractId::CodeHash("3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX".to_string())
```

The factory directly uses that specific contract code, regardless of who deployed it.

:::tip Which to Use?

- **AccountId**: Use when you trust the deployer and want automatic updates
- **CodeHash**: Use when you need a specific, immutable version of the contract

:::

Next, we'll implement the core deployment functionality that creates sub-accounts and deploys contracts.
171 changes: 171 additions & 0 deletions docs/tutorials/factory/2-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
id: deploy
title: Implementing the Deploy Method
sidebar_label: Deploy Contracts
---

import {Github} from "@site/src/components/codetabs"

The `deploy` method is the core of the factory pattern - it creates a sub-account and deploys a contract onto it using global contracts.

## The Complete Deploy Method

<Github fname="lib.rs"
url="https://github.com/near-examples/factory-rust/blob/main/src/lib.rs"
start="35" end="75" />

Let's break down each part of this method.

## Validating the Deposit

<Github fname="lib.rs"
url="https://github.com/near-examples/factory-rust/blob/main/src/lib.rs"
start="38" end="44" />

The attached deposit must cover:
- Account creation and storage
- Initial balance for the new sub-account to initialize the contract

:::info Why Require Deposit?

While deploying via global contracts doesn't cost storage, the new contract needs tokens to:
- Cover storage when initialized with state
- Pay for future transactions
- Maintain minimum account balance

:::

## Creating the Sub-Account

<Github fname="lib.rs"
url="https://github.com/near-examples/factory-rust/blob/main/src/lib.rs"
start="46" end="52" />

Key points:
- Sub-accounts must follow the pattern `name.factory-account.testnet`
- The factory can **only** create its own sub-accounts
- Account ID validation ensures the name is valid

### Account Creation Limitations

The factory:
- ✅ **Can** create `sub.factory.testnet` if it is `factory.testnet`
- ❌ **Cannot** create sub-accounts for other accounts
- ❌ **Cannot** create top-level accounts like `newaccount.testnet`

## Building the Promise Chain

<Github fname="lib.rs"
url="https://github.com/near-examples/factory-rust/blob/main/src/lib.rs"
start="54" end="58" />

The promise chain:
1. **create_account**: Creates the new sub-account
2. **transfer**: Moves attached deposit to the new account
3. **add_full_access_key**: Adds the signer's public key so they control the account

:::tip Full Access Key

Adding the signer's public key as a full access key ensures the caller can manage their new sub-account immediately after creation.

:::

## Deploying the Global Contract

The final step differs based on how the global contract is referenced:

### Deploy by Account ID

<Github fname="lib.rs"
url="https://github.com/near-examples/factory-rust/blob/main/src/lib.rs"
start="60" end="67" />

Uses `use_global_contract_by_account_id()` to deploy the most recent global contract from that account.

### Deploy by Code Hash

<Github fname="lib.rs"
url="https://github.com/near-examples/factory-rust/blob/main/src/lib.rs"
start="68" end="74" />

Uses `use_global_contract()` with the decoded base58 hash to deploy that specific contract version.

## Using the Deploy Method

Deploy a new contract instance:

```bash
near contract call-function as-transaction <factory-account> deploy \
json-args '{"name": "my-token"}' \
prepaid-gas '100.0 Tgas' \
attached-deposit '0.2 NEAR' \
sign-as <your-account> \
network-config testnet \
sign-with-keychain send
```

This creates `my-token.<factory-account>` with the global contract deployed on it.

## Initialize the Deployed Contract

After deployment, initialize the contract. For the default FT contract:

```bash
near contract call-function as-transaction my-token.<factory-account> \
new_default_meta \
json-args '{
"owner_id": "<your-account>",
"total_supply": "100000000000000000000000000"
}' \
prepaid-gas '100.0 Tgas' \
attached-deposit '0 NEAR' \
sign-as <your-account> \
network-config testnet \
sign-with-keychain send
```

## Verify the Deployment

Check that the contract is working:

```bash
near contract call-function as-read-only \
my-token.<factory-account> ft_metadata \
json-args {} \
network-config testnet now
```

Expected response:

```json
{
"decimals": 24,
"name": "Example NEAR fungible token",
"symbol": "EXAMPLE",
"spec": "ft-1.0.0"
}
```

## Promise Chain Execution

The entire operation executes atomically:

1. If account creation fails → entire operation reverts
2. If transfer fails → entire operation reverts
3. If key addition fails → entire operation reverts
4. If global contract deployment fails → entire operation reverts

This ensures you never end up with a partially created account.

## Cost Savings

Using global contracts dramatically reduces costs:

| Deployment Method | Storage Cost per Instance |
|------------------|---------------------------|
| Traditional | ~3 NEAR (full bytecode) |
| Global Contracts | ~0.2 NEAR (just state) |

For 100 instances, this saves **~280 NEAR**!

Next, we'll implement management functions to update the global contract reference and configuration.
Loading