multiversx-cross-contract-calls
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMultiversX 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 and chains builder methods:
self.tx()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
构建器方法
| Category | Method | Description |
|---|---|---|
| Target | | Set recipient address |
| Proxy | | Use generated proxy for type-safe calls |
| Raw | | Manual endpoint call (no proxy) |
| Payment | | Attach EGLD |
| Attach single ESDT | |
| Attach | |
| Attach any payment type | |
| Attach EGLD or ESDT | |
| Gas | | Set gas limit |
| Reserve gas for callback | |
| Args | | Add single argument (raw calls) |
| Add pre-encoded arguments | |
| Results | | Add result handler (can chain multiple) |
| Callback | | Set callback for async calls |
| 分类 | 方法 | 描述 |
|---|---|---|
| 目标地址 | | 设置接收方地址 |
| 代理 | | 使用生成的代理进行类型安全调用 |
| 原始调用 | | 手动调用合约端点(不使用代理) |
| 支付设置 | | 附加EGLD作为支付 |
| 附加单个ESDT代币作为支付 | |
| 附加 | |
| 附加任意类型的支付 | |
| 附加EGLD或ESDT代币作为支付 | |
| Gas设置 | | 设置Gas上限 |
| 为回调函数预留Gas | |
| 参数 | | 添加单个参数(仅适用于原始调用) |
| 添加预编码的参数 | |
| 结果处理 | | 添加结果处理器(可链式添加多个) |
| 回调设置 | | 为异步调用设置回调函数 |
Execution Methods
执行方法
| Method | Type | Description |
|---|---|---|
| Synchronous | Same-shard atomic. Panics if callee fails. |
| Synchronous | Same-shard. Returns |
| Synchronous | Read-only call. Cannot modify state. |
| Async (v2) | Cross-shard. Requires |
| Async (v1) | Legacy. Only 1 per tx. Exits immediately. Prefer |
| Transfer | Send tokens without calling an endpoint. |
| 方法 | 类型 | 描述 |
|---|---|---|
| 同步 | 同分片原子操作。若被调用合约执行失败则触发panic。 |
| 同步 | 同分片操作。被调用合约出错时返回 |
| 同步 | 只读调用,无法修改合约状态。 |
| 异步(v2) | 跨分片操作。必须设置 |
| 异步(v1) | 旧版实现。单个交易中仅能使用1次,调用后立即退出。推荐使用 |
| 转账 | 仅发送代币,不调用合约端点。 |
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
结果处理器
| Handler | Returns | Description |
|---|---|---|
| Decoded return type | Decodes endpoint return value |
| | Recommended. Resets back-transfers before call, returns them after. |
| | Returns back-transfers without resetting (may include leftovers from prior calls). |
| | Only the EGLD portion of back-transfers |
| | Single ESDT back-transfer (panics if != 1) |
| | Address of newly deployed contract |
| | Wraps other handlers; returns error code on failure |
Chain multiple handlers:
rust
.returns(ReturnsResult)
.returns(ReturnsBackTransfersReset)
.sync_call()
// Returns tuple: (result, back_transfers)| 处理器 | 返回值 | 描述 |
|---|---|---|
| 解码后的返回类型 | 解码合约端点的返回值 |
| | 推荐使用。调用前重置回传转账记录,调用后返回最新的回传转账数据。 |
| | 返回回传转账数据但不重置(可能包含之前调用的残留数据)。 |
| | 仅返回回传转账中的EGLD部分 |
| | 返回单个ESDT代币的回传转账数据(若数量不等于1则触发panic) |
| | 返回新部署合约的地址 |
| | 包装其他处理器;调用失败时返回错误码 |
链式调用多个处理器:
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-readrust
// 手动方式(不推荐 — 优先使用结果处理器)
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 annotation (not
#[promises_callback]which is legacy)#[callback] - parameter receives
#[call_result]whereManagedAsyncCallResult<T>matches callee returnT - Callback arguments are passed via — they're serialized into a
self.callbacks().my_callback(arg1, arg2)CallbackClosure - In callbacks, gets tokens sent back
self.call_value().all() - Multiple calls allowed in a single transaction
register_promise()
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 directory or a file.
proxy/proxy_*.rsrust
// 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_*.rsrust
// 在合约中使用代理:
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) { }