multiversx-cross-contract-calls

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MultiversX Cross-Contract Calls — Tx Builder API Reference

MultiversX跨合约调用 — Tx Builder API参考文档

Complete reference for cross-contract calls, callbacks, back-transfers, and proxies in MultiversX smart contracts (SDK v0.64+).
本文档是MultiversX智能合约(SDK v0.64+)中跨合约调用、回调、回传转账及代理的完整参考指南。

Tx Builder Chain

Tx Builder链式调用

Every cross-contract interaction starts with
self.tx()
and chains builder methods:
self.tx()
    .to(address)                        // recipient (ManagedAddress or &ManagedAddress)
    .typed(ProxyType)                   // type-safe proxy (recommended)
    // OR .raw_call("endpoint_name")    // raw call without proxy
    .egld(&amount)                      // payment: EGLD
    // OR .single_esdt(&id, nonce, &amount) // payment: single ESDT
    // OR .payment(payment)             // payment: Payment / EgldOrEsdtTokenPayment
    .gas(gas_limit)                     // explicit gas (required for promises)
    .returns(ReturnsResult)             // result handler(s)
    .sync_call()                        // execution method
所有跨合约交互都以
self.tx()
开头,并链式调用构建器方法:
self.tx()
    .to(address)                        // 接收方(ManagedAddress或&ManagedAddress类型)
    .typed(ProxyType)                   // 类型安全代理(推荐使用)
    // 或 .raw_call("endpoint_name")    // 不使用代理的原始调用
    .egld(&amount)                      // 支付方式:EGLD
    // 或 .single_esdt(&id, nonce, &amount) // 支付方式:单个ESDT代币
    // 或 .payment(payment)             // 支付方式:Payment / EgldOrEsdtTokenPayment
    .gas(gas_limit)                     // 显式设置Gas(异步Promise调用必填)
    .returns(ReturnsResult)             // 结果处理器
    .sync_call()                        // 执行方法

Builder Methods

构建器方法

CategoryMethodDescription
Target
.to(address)
Set recipient address
Proxy
.typed(ProxyType)
Use generated proxy for type-safe calls
Raw
.raw_call("endpoint")
Manual endpoint call (no proxy)
Payment
.egld(&amount)
Attach EGLD
.single_esdt(&token_id, nonce, &amount)
Attach single ESDT
.esdt(esdt_payment)
Attach
EsdtTokenPayment
.payment(payment)
Attach any payment type
.egld_or_single_esdt(&id, nonce, &amount)
Attach EGLD or ESDT
Gas
.gas(amount)
Set gas limit
.gas_for_callback(amount)
Reserve gas for callback
Args
.argument(&arg)
Add single argument (raw calls)
.arguments_raw(buffer)
Add pre-encoded arguments
Results
.returns(handler)
Add result handler (can chain multiple)
Callback
.callback(closure)
Set callback for async calls
分类方法描述
目标地址
.to(address)
设置接收方地址
代理
.typed(ProxyType)
使用生成的代理进行类型安全调用
原始调用
.raw_call("endpoint")
手动调用合约端点(不使用代理)
支付设置
.egld(&amount)
附加EGLD作为支付
.single_esdt(&token_id, nonce, &amount)
附加单个ESDT代币作为支付
.esdt(esdt_payment)
附加
EsdtTokenPayment
类型的支付
.payment(payment)
附加任意类型的支付
.egld_or_single_esdt(&id, nonce, &amount)
附加EGLD或ESDT代币作为支付
Gas设置
.gas(amount)
设置Gas上限
.gas_for_callback(amount)
为回调函数预留Gas
参数
.argument(&arg)
添加单个参数(仅适用于原始调用)
.arguments_raw(buffer)
添加预编码的参数
结果处理
.returns(handler)
添加结果处理器(可链式添加多个)
回调设置
.callback(closure)
为异步调用设置回调函数

Execution Methods

执行方法

MethodTypeDescription
.sync_call()
SynchronousSame-shard atomic. Panics if callee fails.
.sync_call_fallible()
SynchronousSame-shard. Returns
Result
on callee error (use with
ReturnsHandledOrError
).
.sync_call_readonly()
SynchronousRead-only call. Cannot modify state.
.register_promise()
Async (v2)Cross-shard. Requires
.gas()
. Multiple promises per tx OK.
.async_call_and_exit()
Async (v1)Legacy. Only 1 per tx. Exits immediately. Prefer
register_promise()
.
.transfer()
TransferSend tokens without calling an endpoint.
方法类型描述
.sync_call()
同步同分片原子操作。若被调用合约执行失败则触发panic。
.sync_call_fallible()
同步同分片操作。被调用合约出错时返回
Result
类型(需配合
ReturnsHandledOrError
使用)。
.sync_call_readonly()
同步只读调用,无法修改合约状态。
.register_promise()
异步(v2)跨分片操作。必须设置
.gas()
。单个交易中可注册多个Promise。
.async_call_and_exit()
异步(v1)旧版实现。单个交易中仅能使用1次,调用后立即退出。推荐使用
register_promise()
.transfer()
转账仅发送代币,不调用合约端点。

Simple Transfers (No Endpoint Call)

简单转账(不调用合约端点)

rust
// Send EGLD
self.tx().to(&recipient).egld(&amount).transfer();

// Send ESDT
self.tx().to(&recipient).payment(payment.clone()).transfer();

// Send specific ESDT
self.tx().to(&recipient).single_esdt(&token_id, nonce, &amount).transfer();
rust
// 发送EGLD
self.tx().to(&recipient).egld(&amount).transfer();

// 发送ESDT代币
self.tx().to(&recipient).payment(payment.clone()).transfer();

// 发送指定的ESDT代币
self.tx().to(&recipient).single_esdt(&token_id, nonce, &amount).transfer();

Synchronous Calls (Same-Shard)

同步调用(同分片)

Typed Proxy Call

类型化代理调用

rust
// Basic sync call with typed result
let result: BigUint = self.tx()
    .to(&pool_address)
    .typed(proxy_pool::LiquidityPoolProxy)
    .get_reserve()
    .returns(ReturnsResult)
    .sync_call();
rust
// 带类型化结果的基础同步调用
let result: BigUint = self.tx()
    .to(&pool_address)
    .typed(proxy_pool::LiquidityPoolProxy)
    .get_reserve()
    .returns(ReturnsResult)
    .sync_call();

Sync Call with Payment

带支付的同步调用

rust
self.tx()
    .to(&accumulator_address)
    .typed(proxy_accumulator::AccumulatorProxy)
    .deposit()
    .payment(revenue)
    .returns(ReturnsResult)
    .sync_call();
rust
self.tx()
    .to(&accumulator_address)
    .typed(proxy_accumulator::AccumulatorProxy)
    .deposit()
    .payment(revenue)
    .returns(ReturnsResult)
    .sync_call();

Multiple Return Values

多返回值处理

rust
// Chain multiple .returns() for multi-value returns
let (result, back_transfers) = self.tx()
    .to(&pool_address)
    .typed(proxy_pool::PoolProxy)
    .withdraw(amount)
    .returns(ReturnsResult)
    .returns(ReturnsBackTransfersReset)
    .sync_call();
rust
// 链式调用多个.returns()来处理多返回值
let (result, back_transfers) = self.tx()
    .to(&pool_address)
    .typed(proxy_pool::PoolProxy)
    .withdraw(amount)
    .returns(ReturnsResult)
    .returns(ReturnsBackTransfersReset)
    .sync_call();

Fallible Sync Call

可容错的同步调用

rust
// Use with ReturnsHandledOrError to not panic on callee error
let outcome: Result<BigUint, u32> = self.tx()
    .to(&address)
    .typed(SomeProxy)
    .some_endpoint()
    .returns(
        ReturnsHandledOrError::new()
            .returns(ReturnsResult)
    )
    .sync_call_fallible();

match outcome {
    Ok(value) => { /* success */ },
    Err(error_code) => { /* callee returned error */ },
}
rust
// 配合ReturnsHandledOrError使用,避免被调用合约出错时触发panic
let outcome: Result<BigUint, u32> = self.tx()
    .to(&address)
    .typed(SomeProxy)
    .some_endpoint()
    .returns(
        ReturnsHandledOrError::new()
            .returns(ReturnsResult)
    )
    .sync_call_fallible();

match outcome {
    Ok(value) => { /* 处理成功逻辑 */ },
    Err(error_code) => { /* 处理被调用合约返回的错误 */ },
}

Raw Call (No Proxy)

原始调用(不使用代理)

rust
let result: ManagedBuffer = self.tx()
    .to(&address)
    .raw_call("getStatus")
    .argument(&token_id)
    .returns(ReturnsResult)
    .sync_call();
rust
let result: ManagedBuffer = self.tx()
    .to(&address)
    .raw_call("getStatus")
    .argument(&token_id)
    .returns(ReturnsResult)
    .sync_call();

Result Handlers

结果处理器

HandlerReturnsDescription
ReturnsResult
Decoded return typeDecodes endpoint return value
ReturnsBackTransfersReset
BackTransfers
Recommended. Resets back-transfers before call, returns them after.
ReturnsBackTransfers
BackTransfers
Returns back-transfers without resetting (may include leftovers from prior calls).
ReturnsBackTransfersEgld
BigUint
Only the EGLD portion of back-transfers
ReturnsBackTransfersSingleEsdt
EsdtTokenPayment
Single ESDT back-transfer (panics if != 1)
ReturnsNewManagedAddress
ManagedAddress
Address of newly deployed contract
ReturnsHandledOrError
Result<T, u32>
Wraps other handlers; returns error code on failure
Chain multiple handlers:
rust
.returns(ReturnsResult)
.returns(ReturnsBackTransfersReset)
.sync_call()
// Returns tuple: (result, back_transfers)
处理器返回值描述
ReturnsResult
解码后的返回类型解码合约端点的返回值
ReturnsBackTransfersReset
BackTransfers
推荐使用。调用前重置回传转账记录,调用后返回最新的回传转账数据。
ReturnsBackTransfers
BackTransfers
返回回传转账数据但不重置(可能包含之前调用的残留数据)。
ReturnsBackTransfersEgld
BigUint
仅返回回传转账中的EGLD部分
ReturnsBackTransfersSingleEsdt
EsdtTokenPayment
返回单个ESDT代币的回传转账数据(若数量不等于1则触发panic)
ReturnsNewManagedAddress
ManagedAddress
返回新部署合约的地址
ReturnsHandledOrError
Result<T, u32>
包装其他处理器;调用失败时返回错误码
链式调用多个处理器:
rust
.returns(ReturnsResult)
.returns(ReturnsBackTransfersReset)
.sync_call()
// 返回元组:(结果, 回传转账数据)

Back-Transfers

回传转账

Tokens sent back to the caller during a cross-contract call.
跨合约调用过程中,被调用合约返回给调用方的代币。

BackTransfers Struct

BackTransfers结构体

rust
pub struct BackTransfers<A: ManagedTypeApi> {
    pub payments: MultiEgldOrEsdtPayment<A>,
}

impl BackTransfers {
    fn egld_sum(&self) -> BigUint              // Sum of all EGLD back-transfers
    fn to_single_esdt(self) -> EsdtTokenPayment // Exactly 1 ESDT (panics otherwise)
    fn into_payment_vec(self) -> PaymentVec     // Convert to ManagedVec<Payment>
    fn into_multi_value(self) -> MultiValueEncoded<EgldOrEsdtTokenPaymentMultiValue>
}
rust
pub struct BackTransfers<A: ManagedTypeApi> {
    pub payments: MultiEgldOrEsdtPayment<A>,
}

impl BackTransfers {
    fn egld_sum(&self) -> BigUint              // 计算所有EGLD回传转账的总和
    fn to_single_esdt(self) -> EsdtTokenPayment // 提取单个ESDT代币(数量不等于1则触发panic)
    fn into_payment_vec(self) -> PaymentVec     // 转换为ManagedVec<Payment>类型
    fn into_multi_value(self) -> MultiValueEncoded<EgldOrEsdtTokenPaymentMultiValue>
}

Using Back-Transfers with Result Handlers (Recommended)

配合结果处理器使用回传转账(推荐方式)

rust
let back_transfers = self.tx()
    .to(&dex_address)
    .typed(DexProxy)
    .swap(token_out, min_amount)
    .payment(input_payment)
    .returns(ReturnsBackTransfersReset)  // ← always use Reset variant
    .sync_call();

// Process returned tokens
let result_payments = back_transfers.into_payment_vec();
for payment in result_payments.iter() {
    vault.deposit(&payment.token_identifier, &payment.amount);
}
rust
let back_transfers = self.tx()
    .to(&dex_address)
    .typed(DexProxy)
    .swap(token_out, min_amount)
    .payment(input_payment)
    .returns(ReturnsBackTransfersReset)  // ← 始终使用Reset变体
    .sync_call();

// 处理返回的代币
let result_payments = back_transfers.into_payment_vec();
for payment in result_payments.iter() {
    vault.deposit(&payment.token_identifier, &payment.amount);
}

Manual Back-Transfer Retrieval

手动获取回传转账

rust
// Manual approach (less preferred — use result handlers instead)
self.tx().to(&addr).typed(Proxy).some_call().sync_call();
let bt = self.blockchain().get_back_transfers();
self.blockchain().reset_back_transfers(); // MUST reset to prevent double-read
rust
// 手动方式(不推荐 — 优先使用结果处理器)
self.tx().to(&addr).typed(Proxy).some_call().sync_call();
let bt = self.blockchain().get_back_transfers();
self.blockchain().reset_back_transfers(); // 必须重置,避免重复读取旧数据

Async Calls (Cross-Shard) — Promises

异步调用(跨分片)— Promise

Register Promise with Callback

注册带回调的Promise

rust
self.tx()
    .to(&provider)
    .typed(proxy_delegation::DelegationProxy)
    .delegate()
    .egld(&payment)
    .gas(12_000_000u64)
    .callback(
        self.callbacks().delegation_callback(
            provider.clone(),
            &payment,
            &caller,
        ),
    )
    .gas_for_callback(10_000_000u64)
    .register_promise();
rust
self.tx()
    .to(&provider)
    .typed(proxy_delegation::DelegationProxy)
    .delegate()
    .egld(&payment)
    .gas(12_000_000u64)
    .callback(
        self.callbacks().delegation_callback(
            provider.clone(),
            &payment,
            &caller,
        ),
    )
    .gas_for_callback(10_000_000u64)
    .register_promise();

Callback Implementation

回调函数实现

rust
#[promises_callback]
fn delegation_callback(
    &self,
    contract_address: ManagedAddress,
    staked_amount: &BigUint,
    caller: &ManagedAddress,
    #[call_result] result: ManagedAsyncCallResult<()>,
) {
    match result {
        ManagedAsyncCallResult::Ok(()) => {
            // Success — process result
            let ls_amount = self.calculate_ls(staked_amount);
            let user_payment = self.mint_ls_token(ls_amount);
            self.tx().to(caller).esdt(user_payment).transfer();
        },
        ManagedAsyncCallResult::Err(_) => {
            // Failure — refund
            self.tx().to(caller).egld(staked_amount).transfer();
        },
    }
}
Key callback rules:
  • Use
    #[promises_callback]
    annotation (not
    #[callback]
    which is legacy)
  • #[call_result]
    parameter receives
    ManagedAsyncCallResult<T>
    where
    T
    matches callee return
  • Callback arguments are passed via
    self.callbacks().my_callback(arg1, arg2)
    — they're serialized into a
    CallbackClosure
  • In callbacks,
    self.call_value().all()
    gets tokens sent back
  • Multiple
    register_promise()
    calls allowed in a single transaction
rust
#[promises_callback]
fn delegation_callback(
    &self,
    contract_address: ManagedAddress,
    staked_amount: &BigUint,
    caller: &ManagedAddress,
    #[call_result] result: ManagedAsyncCallResult<()>,
) {
    match result {
        ManagedAsyncCallResult::Ok(()) => {
            // 成功 — 处理结果
            let ls_amount = self.calculate_ls(staked_amount);
            let user_payment = self.mint_ls_token(ls_amount);
            self.tx().to(caller).esdt(user_payment).transfer();
        },
        ManagedAsyncCallResult::Err(_) => {
            // 失败 — 执行退款
            self.tx().to(caller).egld(staked_amount).transfer();
        },
    }
}
回调函数关键规则:
  • 使用
    #[promises_callback]
    注解(不要使用旧版的
    #[callback]
    ,仅适用于
    async_call_and_exit()
  • #[call_result]
    参数接收
    ManagedAsyncCallResult<T>
    类型,其中
    T
    与被调用合约的返回值类型匹配
  • 回调参数通过
    self.callbacks().my_callback(arg1, arg2)
    传递 — 会被序列化为
    CallbackClosure
    类型
  • 在回调函数中,
    self.call_value().all()
    可获取返回的代币
  • 单个交易中允许注册多个
    register_promise()

Promise without Callback

不带回调的Promise

rust
// Fire-and-forget (no callback needed)
self.tx()
    .to(&address)
    .typed(Proxy)
    .some_endpoint()
    .gas(5_000_000u64)
    .register_promise();
rust
// 即发即弃(无需处理回调)
self.tx()
    .to(&address)
    .typed(Proxy)
    .some_endpoint()
    .gas(5_000_000u64)
    .register_promise();

Typed Proxy Pattern

类型化代理模式

Proxies are generated from contract interfaces. They provide type-safe endpoint calls.
代理是从合约接口自动生成的,可提供类型安全的合约端点调用。

Proxy Generation

代理生成

Proxies are auto-generated by the framework from contract trait definitions. The generated proxy module is typically in a
proxy/
directory or a
proxy_*.rs
file.
rust
// In your contract, use the proxy:
use crate::proxy_pool;

// Call via typed proxy
self.tx()
    .to(&pool_address)
    .typed(proxy_pool::LiquidityPoolProxy)
    .deposit(token_id, amount)
    .payment(payment)
    .returns(ReturnsResult)
    .sync_call();
代理由框架从合约trait定义自动生成,生成的代理模块通常位于
proxy/
目录下或
proxy_*.rs
文件中。
rust
// 在合约中使用代理:
use crate::proxy_pool;

// 通过类型化代理调用合约
self.tx()
    .to(&pool_address)
    .typed(proxy_pool::LiquidityPoolProxy)
    .deposit(token_id, amount)
    .payment(payment)
    .returns(ReturnsResult)
    .sync_call();

Deploy via Proxy

通过代理部署合约

rust
let new_address: ManagedAddress = self.tx()
    .typed(proxy_pool::LiquidityPoolProxy)
    .init(base_asset, max_rate, threshold)
    .from_source(self.template_address().get())
    .code_metadata(CodeMetadata::UPGRADEABLE | CodeMetadata::READABLE)
    .returns(ReturnsNewManagedAddress)
    .sync_call();
rust
let new_address: ManagedAddress = self.tx()
    .typed(proxy_pool::LiquidityPoolProxy)
    .init(base_asset, max_rate, threshold)
    .from_source(self.template_address().get())
    .code_metadata(CodeMetadata::UPGRADEABLE | CodeMetadata::READABLE)
    .returns(ReturnsNewManagedAddress)
    .sync_call();

Common Patterns

常见模式

Swap and Forward Results

代币兑换并转发结果

rust
let back_transfers = self.tx()
    .to(&dex)
    .typed(DexProxy)
    .swap(token_out, min_out)
    .payment(input)
    .returns(ReturnsBackTransfersReset)
    .sync_call();

let output = back_transfers.into_payment_vec();
let caller = self.blockchain().get_caller();
for payment in output.iter() {
    self.tx().to(&caller).payment(payment.clone()).transfer();
}
rust
let back_transfers = self.tx()
    .to(&dex)
    .typed(DexProxy)
    .swap(token_out, min_out)
    .payment(input)
    .returns(ReturnsBackTransfersReset)
    .sync_call();

let output = back_transfers.into_payment_vec();
let caller = self.blockchain().get_caller();
for payment in output.iter() {
    self.tx().to(&caller).payment(payment.clone()).transfer();
}

Multi-Step with Back-Transfer Tracking

多步骤调用并跟踪回传转账

rust
// Step 1: withdraw from pool
let (withdrawn_amount, bt1) = self.tx()
    .to(&pool)
    .typed(PoolProxy)
    .withdraw(shares)
    .returns(ReturnsResult)
    .returns(ReturnsBackTransfersReset)
    .sync_call();

// Step 2: deposit into another pool
self.tx()
    .to(&other_pool)
    .typed(OtherPoolProxy)
    .deposit()
    .payment(bt1.into_payment_vec().get(0).clone())
    .returns(ReturnsResult)
    .sync_call();
rust
// 步骤1:从资金池提取代币
let (withdrawn_amount, bt1) = self.tx()
    .to(&pool)
    .typed(PoolProxy)
    .withdraw(shares)
    .returns(ReturnsResult)
    .returns(ReturnsBackTransfersReset)
    .sync_call();

// 步骤2:将代币存入另一个资金池
self.tx()
    .to(&other_pool)
    .typed(OtherPoolProxy)
    .deposit()
    .payment(bt1.into_payment_vec().get(0).clone())
    .returns(ReturnsResult)
    .sync_call();

Anti-Patterns

反模式

rust
// BAD: Using ReturnsBackTransfers without reset — may include stale data
.returns(ReturnsBackTransfers).sync_call()

// GOOD: Always use Reset variant
.returns(ReturnsBackTransfersReset).sync_call()

// BAD: Missing gas on register_promise — will panic
self.tx().to(&addr).typed(Proxy).call().register_promise();

// GOOD: Always set gas for async calls
self.tx().to(&addr).typed(Proxy).call().gas(10_000_000u64).register_promise();

// BAD: Using legacy send() API
self.send().direct_egld(&to, &amount);

// GOOD: Use Tx builder API
self.tx().to(&to).egld(&amount).transfer();

// BAD: Manual back-transfer without reset
let bt = self.blockchain().get_back_transfers();
// Forgot reset — next call will see same transfers again!

// GOOD: Always reset after manual retrieval
let bt = self.blockchain().get_back_transfers();
self.blockchain().reset_back_transfers();
// Or better: use ReturnsBackTransfersReset in the result handler

// BAD: Using #[callback] with register_promise
#[callback]  // ← This is for legacy async_call_and_exit only
fn my_callback(&self) { }

// GOOD: Use #[promises_callback] for register_promise
#[promises_callback]
fn my_callback(&self) { }
rust
// 错误:使用ReturnsBackTransfers而不重置 — 可能包含过期数据
.returns(ReturnsBackTransfers).sync_call()

// 正确:始终使用Reset变体
.returns(ReturnsBackTransfersReset).sync_call()

// 错误:注册Promise时未设置Gas — 会触发panic
self.tx().to(&addr).typed(Proxy).call().register_promise();

// 正确:异步调用必须设置Gas
self.tx().to(&addr).typed(Proxy).call().gas(10_000_000u64).register_promise();

// 错误:使用旧版send() API
self.send().direct_egld(&to, &amount);

// 正确:使用Tx Builder API
self.tx().to(&to).egld(&amount).transfer();

// 错误:手动获取回传转账后未重置
let bt = self.blockchain().get_back_transfers();
// 忘记重置 — 下一次调用会读取到相同的旧数据!

// 正确:手动获取后必须重置
let bt = self.blockchain().get_back_transfers();
self.blockchain().reset_back_transfers();
// 更优方案:在结果处理器中使用ReturnsBackTransfersReset

// 错误:将#[callback]与register_promise配合使用
#[callback]  // ← 此注解仅适用于旧版async_call_and_exit
fn my_callback(&self) { }

// 正确:register_promise需配合#[promises_callback]
#[promises_callback]
fn my_callback(&self) { }