Skip to content

Instantly share code, notes, and snippets.

@bh2smith
Created June 3, 2024 14:44
Show Gist options
  • Save bh2smith/e1ede8eb9ae6a9005af700fb209291ce to your computer and use it in GitHub Desktop.
Save bh2smith/e1ede8eb9ae6a9005af700fb209291ce to your computer and use it in GitHub Desktop.
Near Contract Monorepo Archetecture

The following guide was coppied from ChatGPT 4o:

When managing multiple NEAR smart contracts within a single repository, it's common to structure the repository to facilitate organization, compilation, and deployment. Here’s a pattern you can follow for a multi-contract NEAR project in Rust:

Directory Structure

  1. root directory: Contains common configuration files.
  2. contracts: A directory containing individual subdirectories for each contract.
  3. scripts: Contains scripts for building, testing, and deploying contracts.
  4. tests: Contains integration tests that might involve multiple contracts.

Here's an example directory structure:

my-near-project/
├── contracts/
│   ├── contract_a/
│   │   ├── src/
│   │   │   └── lib.rs
│   │   ├── Cargo.toml
│   │   └── README.md
│   ├── contract_b/
│   │   ├── src/
│   │   │   └── lib.rs
│   │   ├── Cargo.toml
│   │   └── README.md
├── scripts/
│   ├── build.sh
│   ├── deploy.sh
│   └── test.sh
├── tests/
│   ├── integration_test.rs
├── Cargo.toml
├── Cargo.lock
├── README.md
└── near-config.json

Root Cargo.toml

The root Cargo.toml can be used to manage dependencies common to all contracts, and workspace configuration:

[workspace]
members = [
    "contracts/contract_a",
    "contracts/contract_b",
    # Add other contracts here
]

[dependencies]
# Add common dependencies here, if any

[profile.release]
codegen-units = 1
incremental = false
opt-level = "s" # optimize for size

Individual Contract Cargo.toml

Each contract should have its own Cargo.toml file. Here is an example for contract_a:

[package]
name = "contract_a"
version = "0.1.0"
edition = "2018"

[dependencies]
near-sdk = "4.0.0" # Ensure this is the correct version for your project

[lib]
crate-type = ["cdylib", "rlib"]

[profile.release]
opt-level = "s" # optimize for size

Building the Contracts

In the scripts directory, create a build.sh script to compile all the contracts:

#!/bin/bash
set -e

echo "Building contract_a"
cd contracts/contract_a
cargo build --target wasm32-unknown-unknown --release

echo "Building contract_b"
cd ../contract_b
cargo build --target wasm32-unknown-unknown --release

echo "Build completed"

Deploying the Contracts

Create a deploy.sh script for deploying the contracts:

#!/bin/bash
set -e

CONTRACT_A_WASM="contracts/contract_a/target/wasm32-unknown-unknown/release/contract_a.wasm"
CONTRACT_B_WASM="contracts/contract_b/target/wasm32-unknown-unknown/release/contract_b.wasm"

echo "Deploying contract_a"
near deploy --wasmFile $CONTRACT_A_WASM --accountId <your-contract-a-account>

echo "Deploying contract_b"
near deploy --wasmFile $CONTRACT_B_WASM --accountId <your-contract-b-account>

echo "Deployment completed"

Testing

For integration tests, you can use the tests directory:

// tests/integration_test.rs
use near_sdk::MockedBlockchain;
use near_sdk::testing_env;
use contract_a::ContractA;
use contract_b::ContractB;

#[test]
fn test_contract_interaction() {
    let context = get_context(vec![], false);
    testing_env!(context);
    
    let contract_a = ContractA::default();
    let contract_b = ContractB::default();

    // Write your test logic here
}

fn get_context(input: Vec<u8>, is_view: bool) -> VMContext {
    VMContext {
        current_account_id: "contract_a".to_string(),
        signer_account_id: "signer".to_string(),
        signer_account_pk: vec![0, 1, 2],
        predecessor_account_id: "predecessor".to_string(),
        input,
        block_index: 0,
        block_timestamp: 0,
        account_balance: 0,
        account_locked_balance: 0,
        storage_usage: 0,
        attached_deposit: 0,
        prepaid_gas: 10u64.pow(18),
        random_seed: vec![0, 1, 2],
        is_view,
        output_data_receivers: vec![],
        epoch_height: 0,
    }
}

Configuration

Create a near-config.json file in the root directory to manage NEAR-specific configuration:

{
  "network": "testnet",
  "nodeUrl": "https://rpc.testnet.near.org",
  "walletUrl": "https://wallet.testnet.near.org",
  "helperUrl": "https://helper.testnet.near.org",
  "explorerUrl": "https://explorer.testnet.near.org",
  "keyPath": "~/.near-credentials/testnet/<your-account>.json"
}

README

Update your README.md to provide an overview of the project, how to build, deploy, and test the contracts.

# My NEAR Project

## Contracts

This project contains the following smart contracts:

- `contract_a`: Description of contract A.
- `contract_b`: Description of contract B.

## Building

To build all contracts, run:

```sh
./scripts/build.sh

Deploying

To deploy all contracts, run:

./scripts/deploy.sh

Testing

To run the integration tests, use:

cargo test

This structure and pattern help in managing multiple contracts effectively, ensuring clear separation of concerns and ease of building, deploying, and testing the contracts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment