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