Редакционная доработка ещё намеренно не завершена
Этот раздел скрыт из основной навигации. Перевод и редакторская полировка намеренно не считаются обязательными, пока раздел не станет публичным.
Финальность
Every execution in NEAR produces an outcome that records what happened. Understanding outcomes is crucial for knowing when your транзакции truly succeeded.
ExecutionOutcome Structure
Source: core/primitives/src/transaction.rs
pub struct ExecutionOutcome {
/// Logs emitted during execution
pub logs: Vec<LogEntry>,
/// Receipt IDs generated by this execution
pub receipt_ids: Vec<CryptoHash>,
/// Gas consumed
pub gas_burnt: Gas,
/// Compute time (internal metric, not persisted)
pub compute_usage: Option<Compute>,
/// NEAR burned (gas_burnt × gas_price)
pub tokens_burnt: Balance,
/// Who executed (signer for tx, receiver for receipt)
pub executor_id: AccountId,
/// Final status
pub status: ExecutionStatus,
/// Detailed profiling (V1, V2, V3 versions)
pub metadata: ExecutionMetadata,
}
ExecutionStatus
pub enum ExecutionStatus {
/// Execution hasn't started or status unknown
Unknown,
/// Execution failed with error
Failure(TxExecutionError),
/// Success with return value
SuccessValue(Vec<u8>),
/// Success, spawned a receipt (for async calls)
SuccessReceiptId(CryptoHash),
}
Key distinctions:
SuccessValue: Execution completed and returned dataSuccessReceiptId: Execution succeeded but created a promise - final result is in that квитанцияFailure: Execution failed - check error for details
Транзакция Outcome vs Квитанция Outcome
A single транзакции can produce many outcomes:
Example outcome tree:
Transaction Hash: TX1
├── TransactionOutcome: Initial execution
│ ├── status: SuccessReceiptId(R1)
│ └── receipt_ids: [R1, R2]
│
├── ReceiptOutcome R1: First receipt execution
│ ├── status: SuccessValue(...)
│ └── receipt_ids: [R3]
│
├── ReceiptOutcome R2: Second receipt (callback)
│ ├── status: SuccessValue(...)
│ └── receipt_ids: []
│
└── ReceiptOutcome R3: Refund
├── status: SuccessValue(...)
└── receipt_ids: []
FinalExecutionOutcome
The aggregated просмотр of all outcomes:
pub struct FinalExecutionOutcomeView {
/// Status of the whole transaction
pub status: FinalExecutionStatus,
/// The original signed transaction
pub transaction: SignedTransactionView,
/// Transaction outcome
pub transaction_outcome: ExecutionOutcomeWithIdView,
/// All receipt outcomes
pub receipts_outcome: Vec<ExecutionOutcomeWithIdView>,
}
pub enum FinalExecutionStatus {
/// Still processing
NotStarted = 0,
Started = 1,
/// All done but the final receipt failed
Failure(TxExecutionError) = 2,
/// All done with final value
SuccessValue(Vec<u8>) = 3,
}
Querying Outcomes
The tx RPC Method
curl -X POST https://rpc.mainnet.fastnear.com \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "tx",
"params": {
"tx_hash": "6zgh2u9DqHHiXzdy9ouTP7oGky2T4nugqzqt9wJZwNFm",
"sender_account_id": "sender.near",
"wait_until": "FINAL"
}
}'
wait_until Options
| Value | Description | Use Case |
|---|---|---|
NONE | Return immediately with whatever статус exists | Polling pattern |
INCLUDED | Wait until транзакции is in a блока | Quick confirmation |
INCLUDED_FINAL | Wait until блока is final | Fork protection |
EXECUTED | Wait until initial квитанция executed | Basic execution |
EXECUTED_OPTIMISTIC | Wait for execution with optimistic финальность | Faster feedback |
FINAL | Wait until ALL квитанции executed and finalized | Recommended |
Understanding Финальность
Блок Финальность
NEAR uses Doomslug consensus with ~2 second финальность:
- Optimistic: Блок produced but not yet endorsed
- Near-final: Блок has endorsements but could theoretically revert
- Final: Блок has BFT финальность - cannot revert
Транзакция Финальность
A транзакции is truly final when:
- The блока containing it is final
- All квитанции have executed
- All квитанция блоки are final
Checking only INCLUDED means the транзакции is in a блока, but:
- The блока might not be final
- The квитанции haven't executed yet
- Cross-shard квитанции take additional блоки
Practical Examples
Simple Transfer (Fast)
Block N: Transaction included → immediate execution
Block N+1: Block N is final
Total: ~2 seconds to finality
Cross-Контракт Вызов (Slower)
Block N: Transaction creates receipt R1
Block N+1: R1 executes, creates R2 (callback)
Block N+2: R2 executes
Block N+3: Block N+2 is final
Total: ~4-6 seconds to finality
Multi-Shard DeFi (Slowest)
Complex operations spanning multiple shards:
Block N through N+5: 6 blocks of receipts
Block N+6: Final block is final
Total: ~12+ seconds to finality
Error Handling
Check All Outcomes
const result = await near.connection.provider.txStatus(txHash, accountId);
// Check transaction outcome
if (result.status.Failure) {
console.error('Transaction failed:', result.status.Failure);
return;
}
// Check ALL receipt outcomes
for (const receipt of result.receipts_outcome) {
if (receipt.outcome.status.Failure) {
console.error('Receipt failed:', receipt.id, receipt.outcome.status.Failure);
return;
}
}
// Success!
const finalValue = result.status.SuccessValue;
Common Errors
| Error | Description |
|---|---|
ActionError::FunctionCallError | Контракт execution failed |
ActionError::AccountDoesNotExist | Target аккаунта missing |
ActionError::InsufficientStake | Not enough staked |
ActionError::ActorNoPermission | доступ ключ lacks permission |
Parsing Return Values
When a контракта function returns data:
// The SuccessValue is base64-encoded
const result = await provider.txStatus(txHash, accountId);
if (result.status.SuccessValue) {
// Decode base64 to bytes
const bytes = Buffer.from(result.status.SuccessValue, 'base64');
// If the contract returns JSON (common pattern)
const value = JSON.parse(bytes.toString());
console.log('Return value:', value);
}
Best Practices
1. Always Use wait_until: FINAL
Unless you have specific requirements for faster feedback, use FINAL to ensure complete execution.
2. Check All Квитанция Outcomes
A транзакции can succeed but a квитанция can fail. Always iterate through all outcomes.
3. Handle Partial Success
In cross-контракта calls, some квитанции may succeed while others fail. Your application should handle this gracefully.
4. Implement Retry Logic
For TIMEOUT errors, the транзакции may still execute. Запрос the статус before retrying.
async function submitWithRetry(signedTx, maxAttempts = 3) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await provider.sendTransaction(signedTx);
} catch (error) {
if (error.type === 'TimeoutError') {
// Check if tx was actually processed
const status = await provider.txStatus(
signedTx.transaction.hash,
signedTx.transaction.signer_id
);
if (status) return status;
}
if (attempt === maxAttempts - 1) throw error;
}
}
}