Skip to content

Commit 9f5fab4

Browse files
Revise advanced cross-contract call tutorial (#2749)
Updated the advanced cross-contract call tutorial to clarify patterns and enhance explanations. Added sections on asynchronous architecture, error handling, and best practices for complex interactions.
1 parent b7716c0 commit 9f5fab4

File tree

1 file changed

+212
-47
lines changed

1 file changed

+212
-47
lines changed

docs/tutorials/examples/advanced-xcc.md

Lines changed: 212 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,40 @@ import Tabs from '@theme/Tabs';
77
import TabItem from '@theme/TabItem';
88
import {CodeTabs, Language, Github} from "@site/src/components/codetabs"
99

10-
This example presents 3 instances of complex cross-contract calls on the NEAR blockchain, showcasing how to batch multiple function calls to a same contract, call multiple contracts in parallel, and handle responses in the callback. It includes both the smart contract and the frontend components.
11-
10+
This guide explores advanced cross-contract call patterns in NEAR, demonstrating how to orchestrate complex multi-contract interactions. You'll learn to batch function calls, execute contracts in parallel, and handle sophisticated callback scenarios.
1211

1312
:::info Simple Cross-Contract Calls
1413

1514
Check the tutorial on how to use [simple cross-contract calls](xcc.md)
1615

1716
:::
1817

18+
## Understanding NEAR's Asynchronous Architecture
19+
20+
Before diving into complex patterns, it's crucial to understand why NEAR handles cross-contract calls differently from other blockchains.
21+
22+
NEAR's sharded architecture makes all cross-contract interactions asynchronous and independent. This design choice enables massive scalability but requires a different mental model:
23+
24+
- **Independent execution**: Each contract runs in its own environment
25+
- **Asynchronous results**: You cannot get immediate responses from external calls
26+
- **Promise-based coordination**: Use Promises to schedule and chain operations
27+
28+
Think of it like coordinating multiple teams across different time zones—you send instructions, continue with other work, and process responses as they arrive.
29+
30+
### Why This Design is Powerful
31+
32+
NEAR's asynchronous approach provides significant advantages:
33+
34+
- **Scalability**: Sharded execution means calls don't compete for resources
35+
- **Flexibility**: Design sophisticated workflows impossible in synchronous systems
36+
- **Reliability**: Failed calls in one contract don't cascade to others
37+
- **Performance**: Parallel execution enables faster overall processing
38+
1939
---
2040

2141
## Obtaining the Cross Contract Call Example
2242

23-
You have two options to start the Donation Example:
43+
You have two options to start the Cross Contract Call Example:
2444

2545
1. You can use the app through `Github Codespaces`, which will open a web-based interactive environment.
2646
2. Clone the repository locally and use it from your computer.
@@ -86,13 +106,13 @@ The smart contract is available in two flavors: Rust and JavaScript
86106

87107
---
88108

89-
## Smart Contract
109+
## Smart Contract Patterns
90110

91-
### Batch Actions
111+
### Pattern 1: Batch Actions
92112

93-
You can aggregate multiple actions directed towards one same contract into a batched transaction.
94-
Methods called this way are executed sequentially, with the added benefit that, if one fails then
95-
they **all get reverted**.
113+
Batch actions let you aggregate multiple function calls to the same contract into a single atomic transaction. This is perfect when you need sequential operations that must all succeed or all fail together.
114+
115+
**Use case**: Multi-step operations that require consistency, such as complex DeFi transactions or multi-stage data updates.
96116

97117
<CodeTabs>
98118
<Language value="js" language="js">
@@ -110,10 +130,9 @@ they **all get reverted**.
110130
</Language>
111131
</CodeTabs>
112132

113-
#### Getting the Last Response
133+
#### Handling Batch Responses
114134

115-
In this case, the callback has access to the value returned by the **last
116-
action** from the chain.
135+
With batch actions, your callback receives the result from the **last action** in the sequence. This design makes sense because if any earlier action failed, the entire batch would have reverted.
117136

118137
<CodeTabs>
119138
<Language value="js" language="js">
@@ -136,10 +155,11 @@ action** from the chain.
136155

137156
---
138157

139-
### Calling Multiple Contracts
158+
### Pattern 2: Calling Multiple Contracts in Parallel
159+
160+
When you need to interact with multiple contracts simultaneously, NEAR's parallel execution shines. Each call executes independently—if one fails, the others continue unaffected.
140161

141-
A contract can call multiple other contracts. This creates multiple transactions that execute
142-
all in parallel. If one of them fails the rest **ARE NOT REVERTED**.
162+
**Use case**: Gathering data from multiple sources, executing independent operations, or building resilient multi-protocol interactions.
143163

144164
<CodeTabs>
145165
<Language value="js" language="js">
@@ -157,10 +177,9 @@ all in parallel. If one of them fails the rest **ARE NOT REVERTED**.
157177
</Language>
158178
</CodeTabs>
159179

160-
#### Getting All Responses
180+
#### Processing Multiple Responses
161181

162-
In this case, the callback has access to an **array of responses**, which have either the
163-
value returned by each call, or an error message.
182+
With parallel calls, your callback receives an **array of responses**. Each response either contains the returned value or an error message, allowing you to handle partial failures gracefully.
164183

165184
<CodeTabs>
166185
<Language value="js" language="js">
@@ -183,12 +202,11 @@ value returned by each call, or an error message.
183202

184203
---
185204

186-
### Multiple Calls - Same Result Type
205+
### Pattern 3: Multiple Calls with Uniform Response Types
187206

188-
This example is a particular case of the previous one ([Calling Multiple Contracts](#calling-multiple-contracts)).
189-
It simply showcases a different way to check the results by directly accessing the `promise_result` array.
207+
This pattern is particularly useful when calling multiple instances of similar contracts or the same method across different contracts. It demonstrates a clean way to handle uniform response types.
190208

191-
In this case, we call multiple contracts that will return the same type:
209+
**Use case**: Polling multiple data sources, aggregating results from similar contracts, or implementing multi-oracle patterns.
192210

193211
<CodeTabs>
194212
<Language value="js" language="js">
@@ -206,10 +224,9 @@ In this case, we call multiple contracts that will return the same type:
206224
</Language>
207225
</CodeTabs>
208226

209-
#### Getting All Responses
227+
#### Iterating Through Uniform Responses
210228

211-
In this case, the callback again has access to an **array of responses**, which we can iterate checking the
212-
results.
229+
When all external contracts return the same data type, you can process responses more elegantly:
213230

214231
<CodeTabs>
215232
<Language value="js" language="js">
@@ -232,9 +249,55 @@ results.
232249

233250
---
234251

235-
### Testing the Contract
252+
## Production Considerations
253+
254+
### Critical Callback Behavior
255+
256+
Understanding callback execution is essential for building reliable applications:
257+
258+
- **Callbacks always execute**: Whether external calls succeed or fail, your callback will run
259+
- **Manual rollbacks required**: Failed external calls don't automatically revert your contract's state changes
260+
- **Token handling**: Failed calls return attached NEAR tokens to your contract, not the original caller
261+
262+
### Error Handling Strategy
263+
264+
Implement comprehensive error handling in your callbacks:
265+
266+
```javascript
267+
@call({ privateFunction: true })
268+
robust_callback({ user_data, transaction_id }) {
269+
const result = near.promiseResult(0);
270+
271+
if (result.length === 0) {
272+
// External call failed - implement cleanup
273+
this.revert_user_changes(user_data);
274+
this.refund_if_needed(user_data.amount);
275+
this.log_failure(transaction_id);
276+
return { success: false, error: "External operation failed" };
277+
}
278+
279+
try {
280+
const response = JSON.parse(result);
281+
return this.process_success(response, user_data);
282+
} catch (error) {
283+
// Invalid response format
284+
this.handle_parse_error(transaction_id);
285+
return { success: false, error: "Invalid response format" };
286+
}
287+
}
288+
```
289+
290+
### Gas Management Tips
291+
292+
- **Allocate sufficient gas**: Cross-contract calls consume more gas than single-contract operations
293+
- **Account for callback execution**: Reserve gas for your callback function
294+
- **Handle gas estimation failures**: Implement fallbacks when gas estimates are insufficient
295+
296+
---
297+
298+
## Testing the Contract
236299

237-
The contract readily includes a set of unit and sandbox testing to validate its functionality. To execute the tests, run the following commands:
300+
The contract includes comprehensive testing to validate complex interaction patterns. Run the following commands to execute tests:
238301

239302
<Tabs groupId="code-tabs">
240303
<TabItem value="js" label="🌐 JavaScript">
@@ -257,8 +320,8 @@ The contract readily includes a set of unit and sandbox testing to validate its
257320

258321
</Tabs>
259322

260-
:::tip
261-
The `integration tests` use a sandbox to create NEAR users and simulate interactions with the contract.
323+
:::tip Testing Cross-Contract Logic
324+
The integration tests use a sandbox environment to simulate multi-contract interactions. This is essential for validating that your callback logic handles both success and failure scenarios correctly.
262325
:::
263326

264327
<hr class="subsection" />
@@ -311,55 +374,157 @@ Go into the directory containing the smart contract (`cd contract-advanced-ts` o
311374

312375
### CLI: Interacting with the Contract
313376

314-
To interact with the contract through the console, you can use the following commands:
377+
Test the different cross-contract patterns using these commands:
315378

316379
<Tabs groupId="cli-tabs">
317380
<TabItem value="short" label="Short">
318381

319382
```bash
320-
# Execute contracts sequentially
321-
# Replace <accountId> with your account ID
383+
# Execute contracts sequentially (batch pattern)
322384
near call <accountId> batch_actions --accountId <accountId> --gas 300000000000000
323385

324-
# Execute contracts in parallel
325-
# Replace <accountId> with your account ID
326-
near call <accountId> multiple_contracts --accountId <accountId> --gas 300000000000000
386+
# Execute contracts in parallel (multiple contracts pattern)
387+
near call <accountId> multiple_contracts --accountId <accountId> --gas 300000000000000
327388

328-
# Execute multiple instances of the same contract in parallel
329-
# Replace <accountId> with your account ID
389+
# Execute multiple instances with same return type
330390
near call <accountId> similar_contracts --accountId <accountId> --gas 300000000000000
331391
```
332392
</TabItem>
333393

334394
<TabItem value="full" label="Full">
335395

336396
```bash
337-
# Execute contracts sequentially
338-
# Replace <accountId> with your account ID
397+
# Execute contracts sequentially (batch pattern)
339398
near contract call-function as-transaction <accountId> batch_actions json-args '{}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as <accountId> network-config testnet sign-with-keychain send
340399

341-
# Execute contracts in parallel
342-
# Replace <accountId> with your account ID
400+
# Execute contracts in parallel (multiple contracts pattern)
343401
near contract call-function as-transaction <accountId> multiple_contracts json-args '{}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as <accountId> network-config testnet sign-with-keychain send
344402

345-
# Execute multiple instances of the same contract in parallel
346-
# Replace <accountId> with your account ID
403+
# Execute multiple instances with same return type
347404
near contract call-function as-transaction <accountId> similar_contracts json-args '{}' prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as <accountId> network-config testnet sign-with-keychain send
348405
```
349406
</TabItem>
350407
</Tabs>
351408

409+
---
410+
411+
## Advanced Implementation Patterns
412+
413+
### Coordinating Complex Multi-Contract Workflows
414+
415+
For sophisticated applications, you might need to coordinate between multiple contracts with interdependent operations:
416+
417+
```javascript
418+
// Example: Coordinated DeFi operation across multiple protocols
419+
contract_a = CrossContract(this.dex_contract);
420+
promise_a = contract_a.call("get_price", { token: "USDC" });
421+
422+
contract_b = CrossContract(this.lending_contract);
423+
promise_b = contract_b.call("get_collateral_ratio", { user: user_id });
424+
425+
combined_promise = promise_a.join(
426+
[promise_b],
427+
"execute_leveraged_trade",
428+
contract_ids=[this.dex_contract, this.lending_contract]
429+
);
430+
431+
return combined_promise.value();
432+
```
433+
434+
### Error Recovery Strategies
435+
436+
Implement robust error handling for production applications:
437+
438+
```javascript
439+
@call({ privateFunction: true })
440+
complex_operation_callback({ user_id, operation_data, original_state }) {
441+
const results = this.get_all_promise_results();
442+
443+
// Check if any critical operations failed
444+
const critical_failures = results.filter((result, index) =>
445+
!result.success && operation_data.critical_operations.includes(index)
446+
);
447+
448+
if (critical_failures.length > 0) {
449+
// Rollback strategy for critical failures
450+
this.restore_user_state(user_id, original_state);
451+
this.refund_user_funds(user_id, operation_data.total_amount);
452+
return {
453+
success: false,
454+
error: "Critical operations failed",
455+
failed_operations: critical_failures.length
456+
};
457+
}
458+
459+
// Process partial success scenarios
460+
return this.handle_partial_success(results, user_id);
461+
}
462+
```
463+
464+
---
465+
466+
## Best Practices for Complex Cross-Contract Calls
467+
468+
### 1. Design for Partial Failures
469+
470+
Always assume some external calls might fail and design your application to handle partial success gracefully.
471+
472+
### 2. Implement Comprehensive Logging
473+
474+
Add detailed logging to track cross-contract call outcomes:
475+
476+
```javascript
477+
near.log(`Cross-contract call initiated: ${contract_id}.${method_name}`);
478+
near.log(`Callback executed with status: ${result.success ? 'SUCCESS' : 'FAILED'}`);
479+
```
480+
481+
### 3. Optimize Gas Usage
352482

353-
:::info
354-
If at some point you get an "Exceeded the prepaid gas" error, try to increase the gas amount used within the functions when calling other contracts
483+
Cross-contract calls consume significant gas. Profile your operations and optimize:
484+
485+
- Use appropriate gas allocations for each external call
486+
- Consider the gas cost of your callback processing
487+
- Implement gas estimation for complex workflows
488+
489+
### 4. State Management Strategy
490+
491+
Plan your state changes carefully:
492+
493+
- Save original state before making external calls
494+
- Implement clear rollback procedures
495+
- Use consistent patterns across your application
496+
497+
---
498+
499+
## Troubleshooting Common Issues
500+
501+
:::warning Gas Limitations
502+
If you encounter "Exceeded the prepaid gas" errors, increase the gas amount in your external contract calls. Complex multi-contract operations require substantial gas allocation.
355503
:::
356504

357-
:::note Versioning for this article
505+
:::info Callback Debugging
506+
Use NEAR's sandbox testing environment to debug callback logic. The sandbox lets you simulate various failure scenarios and validate your error handling.
507+
:::
358508

509+
:::note Version Compatibility
359510
At the time of this writing, this example works with the following versions:
360511

361512
- near-cli: `4.0.13`
362-
- node: `18.19.1`
513+
- node: `18.19.1`
363514
- rustc: `1.77.0`
364-
365515
:::
516+
517+
---
518+
519+
## Taking Your Skills Further
520+
521+
Mastering these complex cross-contract patterns opens up possibilities for building sophisticated applications:
522+
523+
- **DeFi protocols** that coordinate across multiple markets
524+
- **Multi-step workflows** that span several specialized contracts
525+
- **Resilient systems** that gracefully handle partial failures
526+
- **High-performance applications** that leverage parallel execution
527+
528+
The key is understanding that NEAR's asynchronous nature isn't a constraint—it's a powerful feature that enables building applications that would be impossible on synchronous blockchains.
529+
530+
Start with these patterns, experiment with combining them, and you'll discover new ways to architect complex blockchain applications that take full advantage of NEAR's unique capabilities.

0 commit comments

Comments
 (0)