Skip to main content
Cross-contract calls allow your smart contract to interact with other contracts on the NEAR blockchain. This enables powerful composability and integration patterns.

Overview

This tutorial teaches you how to:
  • Make simple cross-contract calls
  • Handle callbacks from other contracts
  • Work with promises
  • Handle errors in cross-contract interactions

Simple cross-contract call

The simplest example calls the Hello NEAR contract to set and retrieve a greeting.

GitHub Repository

View the complete example on GitHub

How it works

1

Make the cross-contract call

Your contract creates a promise to call another contract’s method:
use near_sdk::{ext_contract, Promise};

// Define the interface of the external contract
#[ext_contract(ext_hello)]
trait HelloContract {
    fn set_greeting(&mut self, greeting: String);
    fn get_greeting(&self) -> String;
}

#[near]
impl Contract {
    pub fn call_set_greeting(
        &self,
        account_id: AccountId,
        greeting: String,
    ) -> Promise {
        ext_hello::ext(account_id)
            .set_greeting(greeting)
    }
}
2

Handle the callback

Add a callback method to process the result:
#[near]
impl Contract {
    #[private]
    pub fn callback_greeting(&self) -> String {
        match env::promise_result(0) {
            PromiseResult::Successful(value) => {
                let greeting = near_sdk::serde_json::from_slice::<String>(&value)
                    .unwrap();
                log!("Received greeting: {}", greeting);
                greeting
            }
            _ => {
                log!("Promise failed");
                "Failed to get greeting".to_string()
            }
        }
    }
    
    pub fn get_greeting_with_callback(
        &self,
        account_id: AccountId,
    ) -> Promise {
        ext_hello::ext(account_id)
            .get_greeting()
            .then(
                Self::ext(env::current_account_id())
                    .callback_greeting()
            )
    }
}
3

Deploy and test

# Deploy your contract
near contract deploy <your-contract>.testnet use-file ./contract.wasm

# Call the cross-contract method
near contract call-function as-transaction <your-contract>.testnet \
  call_set_greeting \
  json-args '{"account_id":"hello.near-examples.testnet","greeting":"Hi!"}' \
  prepaid-gas '100 TeraGas' \
  attached-deposit '0 NEAR' \
  network-config testnet

Key concepts

Cross-contract calls use promises to handle asynchronous operations:
  • Promise creation: Initiates a call to another contract
  • Promise chaining: Sequences multiple calls with .then()
  • Promise results: Access results in callback functions
Callbacks handle the results of cross-contract calls:
  • Must be marked as #[private] (Rust) or privateFunction: true (JS)
  • Only callable by the contract itself
  • Access promise results via env::promise_result() or near.promiseResult()
Cross-contract calls require careful gas management:
  • Allocate sufficient gas for the external call
  • Reserve gas for the callback
  • Total gas = call gas + callback gas
  • Failed calls due to insufficient gas cannot be recovered
Handle errors gracefully:
  • Check promise results in callbacks
  • Provide fallback behavior for failed calls
  • Log errors for debugging
  • Consider refund logic for failed operations

Common patterns

Sequential calls

Make multiple calls in sequence:
ext_contract_a::ext(account_a)
    .method_a()
    .then(
        ext_contract_b::ext(account_b)
            .method_b()
    )
    .then(
        Self::ext(env::current_account_id())
            .callback_final()
    )

Batch calls

Make multiple independent calls:
let promise_a = ext_contract_a::ext(account_a).method_a();
let promise_b = ext_contract_b::ext(account_b).method_b();

Promise::and(vec![promise_a, promise_b])
    .then(
        Self::ext(env::current_account_id())
            .callback_both()
    )

Testing cross-contract calls

Use NEAR Workspaces to test cross-contract interactions:
#[tokio::test]
async fn test_cross_contract_call() -> Result<()> {
    let worker = near_workspaces::sandbox().await?;
    
    // Deploy both contracts
    let hello_contract = worker.dev_deploy(HELLO_WASM).await?;
    let caller_contract = worker.dev_deploy(CALLER_WASM).await?;
    
    // Make cross-contract call
    let outcome = caller_contract
        .call("call_set_greeting")
        .args_json(serde_json::json!({
            "account_id": hello_contract.id(),
            "greeting": "Testing!"
        }))
        .gas(100_000_000_000_000)
        .transact()
        .await?;
    
    assert!(outcome.is_success());
    
    Ok(())
}

Next steps

Advanced cross-contract calls

Learn about parallel and batch cross-contract calls

Factory contracts

Deploy contracts from contracts

NFT marketplace

Build a marketplace using cross-contract calls

Contract security

Secure your cross-contract interactions

Build docs developers (and LLMs) love