MultiversX跨合约调用开发技能Skill multiversx-cross-contract-calls

该技能涉及使用MultiversX区块链的Tx Builder API进行智能合约之间的跨合约调用,包括同步调用、异步调用、回调处理、代币支付和回传代币管理。它用于开发去中心化应用(DApp)、DeFi项目、NFT平台等区块链应用,提升智能合约的互操作性和功能扩展。关键词:MultiversX、跨合约调用、智能合约、区块链开发、DeFi、DApp、异步调用、回调处理。

智能合约 0 次安装 0 次浏览 更新于 3/21/2026

name: multiversx-cross-contract-calls description: 在 MultiversX 智能合约中进行跨合约调用。用于调用其他合约、处理回调、管理回传代币、使用类型化代理或通过 Tx builder API (.tx().to()) 发送代币。

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

MultiversX 智能合约(SDK v0.64+)中跨合约调用、回调、回传代币和代理的完整参考。

Tx Builder 链

每个跨合约交互都以 self.tx() 开始,并链式调用构建器方法:

self.tx()
    .to(address)                        // 接收方地址(ManagedAddress 或 &ManagedAddress)
    .typed(ProxyType)                   // 类型安全代理(推荐)
    // OR .raw_call("endpoint_name")    // 无代理的原始调用
    .egld(&amount)                      // 支付:EGLD
    // OR .single_esdt(&id, nonce, &amount) // 支付:单个 ESDT
    // OR .payment(payment)             // 支付:Payment / EgldOrEsdtTokenPayment
    .gas(gas_limit)                     // 显式设置 gas(对承诺是必需的)
    .returns(ReturnsResult)             // 结果处理器
    .sync_call()                        // 执行方法

构建器方法

类别 方法 描述
目标 .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) 设置异步调用的回调

执行方法

方法 类型 描述
.sync_call() 同步 同分片原子调用。如果被调用方失败则恐慌。
.sync_call_fallible() 同步 同分片调用。被调用方错误时返回 Result(与 ReturnsHandledOrError 使用)。
.sync_call_readonly() 同步 只读调用。不能修改状态。
.register_promise() 异步(v2) 跨分片调用。需要 .gas()。每个交易允许多个承诺。
.async_call_and_exit() 异步(v1) 旧版。每个交易仅一个。立即退出。推荐使用 register_promise()
.transfer() 转账 发送代币而不调用端点。

简单转账(无端点调用)

// 发送 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();

同步调用(同分片)

类型化代理调用

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

带支付的同步调用

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

多返回值

// 链式多个 .returns() 来处理多值返回
let (result, back_transfers) = self.tx()
    .to(&pool_address)
    .typed(proxy_pool::PoolProxy)
    .withdraw(amount)
    .returns(ReturnsResult)
    .returns(ReturnsBackTransfersReset)
    .sync_call();

可失败同步调用

// 与 ReturnsHandledOrError 使用,在被调用方错误时不恐慌
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) => { /* 被调用方返回错误 */ },
}

原始调用(无代理)

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

结果处理器

处理器 返回 描述
ReturnsResult 解码后的返回类型 解码端点返回值
ReturnsBackTransfersReset BackTransfers 推荐。 在调用前重置回传代币,调用后返回它们。
ReturnsBackTransfers BackTransfers 返回回传代币而不重置(可能包含之前调用的遗留)。
ReturnsBackTransfersEgld BigUint 仅返回回传代币的 EGLD 部分
ReturnsBackTransfersSingleEsdt EsdtTokenPayment 单个 ESDT 回传代币(如果不是 1 个则恐慌)
ReturnsNewManagedAddress ManagedAddress 新部署合约的地址
ReturnsHandledOrError Result<T, u32> 包装其他处理器;失败时返回错误代码

链式多个处理器:

.returns(ReturnsResult)
.returns(ReturnsBackTransfersReset)
.sync_call()
// 返回元组:(result, back_transfers)

回传代币

在跨合约调用期间发送回调用方的代币。

BackTransfers 结构

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

impl BackTransfers {
    fn egld_sum(&self) -> BigUint              // 所有 EGLD 回传代币的总和
    fn to_single_esdt(self) -> EsdtTokenPayment // 恰好 1 个 ESDT(否则恐慌)
    fn into_payment_vec(self) -> PaymentVec     // 转换为 ManagedVec<Payment>
    fn into_multi_value(self) -> MultiValueEncoded<EgldOrEsdtTokenPaymentMultiValue>
}

使用回传代币与结果处理器(推荐)

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);
}

手动回传代币检索

// 手动方法(不太推荐 — 使用结果处理器代替)
self.tx().to(&addr).typed(Proxy).some_call().sync_call();
let bt = self.blockchain().get_back_transfers();
self.blockchain().reset_back_transfers(); // 必须重置以防止双重读取

异步调用(跨分片)— 承诺

注册承诺带回调

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();

回调实现

#[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]
  • #[call_result] 参数接收 ManagedAsyncCallResult<T>,其中 T 匹配被调用方返回类型
  • 回调参数通过 self.callbacks().my_callback(arg1, arg2) 传递 — 它们被序列化为 CallbackClosure
  • 在回调中,self.call_value().all() 获取发送回的代币
  • 单个交易中允许多个 register_promise() 调用

不带回调的承诺

// 发射后不管(无需回调)
self.tx()
    .to(&address)
    .typed(Proxy)
    .some_endpoint()
    .gas(5_000_000u64)
    .register_promise();

类型化代理模式

代理从合约接口生成,提供类型安全的端点调用。

代理生成

代理由框架从合约特征定义自动生成。生成的代理模块通常在 proxy/ 目录或 proxy_*.rs 文件中。

// 在您的合约中,使用代理:
use crate::proxy_pool;

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

通过代理部署

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();

常见模式

交换并转发结果

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();
}

多步带回传代币跟踪

// 步骤 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();

反模式

// 错误:使用 ReturnsBackTransfers 而不重置 — 可能包含陈旧数据
.returns(ReturnsBackTransfers).sync_call()

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

// 错误:在 register_promise 上缺少 gas — 将恐慌
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) { }