Overview
A Transaction in NEAR represents a single unit of work that modifies the blockchain state. Every transaction consists of metadata (signer, receiver, nonce, block hash) and one or more actions to execute.
Transaction Structure
Transaction Type
type Transaction = {
signerAccountId : AccountId ;
signerPublicKey : PublicKey ;
receiverAccountId : AccountId ;
nonce : Nonce ;
blockHash : BlockHash ;
} & (
| { action : Action ; actions ?: never } // Single action
| { action ?: never ; actions : Action [] } // Multiple actions
);
Transaction Fields
The account that signs and pays for the transaction. signerAccountId : 'alice.near'
The public key of the access key used to sign the transaction. Must correspond to an access key on the signer account. signerPublicKey : 'ed25519:5FNQFvXMECbW...'
The account that receives and processes the transaction. For transfers, this is the recipient. For contract calls, this is the contract. receiverAccountId : 'bob.near'
A unique number that must be greater than the current nonce for the access key. Prevents replay attacks.
Hash of a recent block (within ~2 minutes). Ensures transaction is processed within a time window. blockHash : '4hfJMGGE8NQ9xvK3kD3MFqF7...'
Transaction Intent
For simpler transaction construction, use TransactionIntent:
type TransactionIntent = {
receiverAccountId : AccountId ;
} & (
| { action : Action ; actions ?: never }
| { action ?: never ; actions : Action [] }
);
The Signer automatically fills in signerAccountId, signerPublicKey, nonce, and blockHash when you provide a TransactionIntent.
const intent : TransactionIntent = {
receiverAccountId: 'bob.near' ,
action: transfer ({ amount: near ( '1' ) })
};
// Signer converts intent → Transaction → SignedTransaction
await signer . executeTransaction ({ intent });
Actions
Actions are the operations performed by a transaction. NEAR supports nine action types:
Action Type Definition
type Action =
| CreateAccountAction
| TransferAction
| AddFullAccessKeyAction
| AddFunctionCallKeyAction
| DeployContractAction
| FunctionCallAction
| StakeAction
| DeleteKeyAction
| DeleteAccountAction ;
Transfer Action
Transfer NEAR tokens from signer to receiver.
import { transfer , near , yoctoNear } from '@near-api/client' ;
// Transfer 1 NEAR
const action1 = transfer ({ amount: near ( '1' ) });
// Transfer 500 milliNEAR (0.5 NEAR)
const action2 = transfer ({ amount: near ( '0.5' ) });
// Transfer exact yoctoNEAR (1 NEAR = 10^24 yoctoNEAR)
const action3 = transfer ({ amount: yoctoNear ( '1000000000000000000000000' ) });
Type definition:
type TransferAction = {
actionType : 'Transfer' ;
amount : NearTokenArgs ; // String or bigint in yoctoNEAR
};
Function Call Action
Call a contract method, optionally with arguments, gas, and attached deposit.
import { functionCall , teraGas , gas , near } from '@near-api/client' ;
// Simple call without arguments
const action1 = functionCall ({
functionName: 'my_method' ,
gasLimit: teraGas ( '30' )
});
// With JSON arguments (auto-serialized)
const action2 = functionCall ({
functionName: 'transfer' ,
functionArgs: {
receiver_id: 'bob.near' ,
amount: '1000000'
},
gasLimit: teraGas ( '30' ),
attachedDeposit: near ( '0' )
});
// Custom serialization
const action3 = functionCall ({
functionName: 'custom_method' ,
functionArgs: myCustomData ,
gasLimit: gas ( '100000000000000' ),
options: {
serializeArgs : ({ functionArgs }) => {
return myCustomSerializer ( functionArgs );
}
}
});
Type definition:
type FunctionCallAction = {
actionType : 'FunctionCall' ;
functionName : string ;
functionArgs : Uint8Array ; // Serialized arguments
gasLimit : NearGasArgs ; // String or bigint in gas units
attachedDeposit ?: NearTokenArgs ; // Optional NEAR deposit
};
Function arguments are automatically JSON-serialized unless you provide a custom serializeArgs function.
Add Full Access Key
Add a full access key that can perform any action on the account.
import { addFullAccessKey } from '@near-api/client' ;
const action = addFullAccessKey ({
publicKey: 'ed25519:5FNQFvXMECbW...'
});
Type definition:
type AddFullAccessKeyAction = {
actionType : 'AddKey' ;
accessType : 'FullAccess' ;
publicKey : PublicKey ;
};
Security Warning Full access keys can perform ANY action on the account, including transferring all funds and deleting the account. Use with extreme caution.
Add Function Call Key
Add a limited access key that can only call specific contract methods.
import { addFunctionCallKey , near } from '@near-api/client' ;
// Limited to specific contract
const action1 = addFunctionCallKey ({
publicKey: 'ed25519:5FNQFvXMECbW...' ,
contractAccountId: 'contract.near'
});
// Limited to specific methods
const action2 = addFunctionCallKey ({
publicKey: 'ed25519:5FNQFvXMECbW...' ,
contractAccountId: 'contract.near' ,
allowedFunctions: [ 'method1' , 'method2' ]
});
// With gas budget (allowance)
const action3 = addFunctionCallKey ({
publicKey: 'ed25519:5FNQFvXMECbW...' ,
contractAccountId: 'contract.near' ,
gasBudget: near ( '0.25' ) // 0.25 NEAR allowance
});
Type definition:
type AddFunctionCallKeyAction = {
actionType : 'AddKey' ;
accessType : 'FunctionCall' ;
publicKey : PublicKey ;
contractAccountId : AccountId ;
gasBudget ?: NearTokenArgs ;
allowedFunctions ?: string [];
};
Deploy Contract
Deploy WebAssembly contract code to an account.
import { deployContract } from '@near-api/client' ;
import { readFile } from 'fs/promises' ;
const wasmCode = await readFile ( './contract.wasm' );
const action = deployContract ({
code: wasmCode
});
Type definition:
type DeployContractAction = {
actionType : 'DeployContract' ;
code : Uint8Array ; // WebAssembly bytecode
};
Stake
Stake NEAR tokens to become a validator.
import { stake , near } from '@near-api/client' ;
const action = stake ({
amount: near ( '1000' ), // Minimum varies by network
publicKey: 'ed25519:5FNQFvXMECbW...'
});
Type definition:
type StakeAction = {
actionType : 'Stake' ;
amount : NearTokenArgs ;
publicKey : PublicKey ;
};
Delete Key
Remove an access key from the account.
import { deleteKey } from '@near-api/client' ;
const action = deleteKey ({
publicKey: 'ed25519:5FNQFvXMECbW...'
});
Type definition:
type DeleteKeyAction = {
actionType : 'DeleteKey' ;
publicKey : PublicKey ;
};
Delete Account
Delete the account and transfer remaining balance to a beneficiary.
import { deleteAccount } from '@near-api/client' ;
const action = deleteAccount ({
beneficiaryAccountId: 'alice.near'
});
Type definition:
type DeleteAccountAction = {
actionType : 'DeleteAccount' ;
beneficiaryAccountId : AccountId ;
};
Create Account
Create a new account (typically used with subaccounts).
import { createAccount } from '@near-api/client' ;
const action = createAccount ();
Type definition:
type CreateAccountAction = {
actionType : 'CreateAccount' ;
};
Creating an account requires the receiver to be a subaccount of the signer. For example, alice.near can create bob.alice.near.
Signed Transactions
After signing, a transaction becomes a SignedTransaction:
type SignedTransaction = {
transaction : Transaction ;
transactionHash : CryptoHash ;
signature : Signature ;
};
Signing Process
// 1. Create transaction intent
const intent = {
receiverAccountId: 'bob.near' ,
action: transfer ({ amount: near ( '1' ) })
};
// 2. Signer builds full transaction
// - Fetches current nonce
// - Gets recent block hash
// - Adds signer account and public key
// 3. KeyService signs the transaction
const signedTx = await signer . signTransaction ({ intent });
// 4. Result contains transaction, hash, and signature
console . log ( signedTx . transaction ); // Full transaction
console . log ( signedTx . transactionHash ); // Base58 hash
console . log ( signedTx . signature ); // Base58 signature
Multiple Actions
A transaction can include multiple actions that execute atomically:
import { transfer , functionCall , near , teraGas } from '@near-api/client' ;
await signer . executeTransaction ({
intent: {
receiverAccountId: 'contract.near' ,
actions: [
// First: transfer 0.1 NEAR
transfer ({ amount: near ( '0.1' ) }),
// Then: call deposit function
functionCall ({
functionName: 'deposit' ,
gasLimit: teraGas ( '30' )
})
]
}
});
Atomic Execution All actions in a transaction execute atomically. If any action fails, the entire transaction is reverted.
Action Order Matters
Actions execute in the order specified:
// ✅ Correct: Create account, then add key
actions : [
createAccount (),
addFullAccessKey ({ publicKey: '...' }),
transfer ({ amount: near ( '1' ) })
]
// ❌ Wrong: Can't add key to non-existent account
actions : [
addFullAccessKey ({ publicKey: '...' }),
createAccount ()
]
Complete Transaction Flow
Here’s the complete flow from intent to execution:
import {
createTestnetClient ,
createMemoryKeyService ,
createMemorySigner ,
transfer ,
near
} from '@near-api/client' ;
// 1. Setup
const client = createTestnetClient ();
const keyService = createMemoryKeyService ({
keySource: { privateKey: 'ed25519:...' }
});
const signer = createMemorySigner ({
signerAccountId: 'alice.testnet' ,
client ,
keyService
});
// 2. Define intent
const intent = {
receiverAccountId: 'bob.testnet' ,
action: transfer ({ amount: near ( '1' ) })
};
// 3. Execute (internally performs all steps)
const result = await signer . safeExecuteTransaction ({ intent });
if ( result . ok ) {
const txResult = result . value ;
// Transaction outcome
console . log ( txResult . transaction_outcome . outcome . status );
// Receipt outcomes
for ( const receipt of txResult . receipts_outcome ) {
console . log ( receipt . outcome . status );
}
// Transaction hash
console . log ( txResult . transaction . hash );
}
Internal Steps
When you call executeTransaction, the signer:
Validates intent : Checks schema and arguments
Fetches access keys : Gets account access keys from RPC
Selects signing key : Chooses appropriate key from key pool
Gets nonce : Fetches and increments nonce for the key
Gets block hash : Retrieves recent block hash (cached)
Builds transaction : Constructs full transaction object
Signs transaction : Uses KeyService to create signature
Submits transaction : Sends to network via Client
Returns result : Transaction outcome or error
Internally, transactions are encoded in a binary format for the blockchain:
type NativeTransaction = {
signerId : AccountId ;
publicKey : NativePublicKey ;
actions : NativeAction [];
receiverId : AccountId ;
nonce : bigint ;
blockHash : Uint8Array ;
};
The library handles conversion between the developer-friendly types and the binary format using BORSH serialization.
Token and Gas Helpers
The library provides convenient helpers for working with NEAR tokens and gas:
NEAR Tokens
import { near , yoctoNear , nearToken } from '@near-api/client' ;
// Human-readable NEAR amounts
near ( '1' ) // 1 NEAR
near ( '0.5' ) // 0.5 NEAR
near ( '10.25' ) // 10.25 NEAR
// Exact yoctoNEAR (1 NEAR = 10^24 yoctoNEAR)
yoctoNear ( '1000000000000000000000000' ) // 1 NEAR
// Generic token (accepts both formats)
nearToken ( '1' )
nearToken ({ near: '1' })
nearToken ({ yoctoNear: '1000000000000000000000000' })
Gas
import { gas , teraGas , nearGas } from '@near-api/client' ;
// TGas (1 TGas = 10^12 gas)
teraGas ( '30' ) // 30 TGas (common for function calls)
teraGas ( '300' ) // 300 TGas (maximum per transaction)
// Exact gas units
gas ( '100000000000000' )
// Generic gas
nearGas ( '30' )
nearGas ({ teraGas: '30' })
nearGas ({ gas: '30000000000000' })
Common gas amounts:
Simple transfers: 0 TGas (no execution)
View calls: 1-5 TGas
Function calls: 30-100 TGas
Complex operations: 100-200 TGas
Maximum per transaction: 300 TGas
Transaction Results
A successful transaction returns comprehensive result data:
type SendSignedTransactionOutput = {
transaction : {
hash : CryptoHash ;
signer_id : AccountId ;
receiver_id : AccountId ;
// ... other fields
};
transaction_outcome : {
outcome : {
status : ExecutionStatus ;
receipt_ids : string [];
gas_burnt : number ;
tokens_burnt : string ;
// ... other fields
};
};
receipts_outcome : Array <{
outcome : {
status : ExecutionStatus ;
receipt_ids : string [];
gas_burnt : number ;
tokens_burnt : string ;
// ... other fields
};
}>;
};
Checking Transaction Status
const result = await signer . executeTransaction ({ intent });
if ( result . ok ) {
const { transaction_outcome } = result . value ;
if ( 'SuccessValue' in transaction_outcome . outcome . status ) {
console . log ( 'Success!' );
} else if ( 'Failure' in transaction_outcome . outcome . status ) {
console . log ( 'Failed:' , transaction_outcome . outcome . status . Failure );
}
}
Best Practices
Use Safe Action Creators Always use the safe variants of action creators (safeTransfer, safeFunctionCall) in production to catch validation errors early.
Check Transaction Results Always verify the transaction status in the result. A successful RPC response doesn’t guarantee the transaction executed successfully.
Handle Nonce Conflicts Use a Signer instead of manually managing nonces. The Signer’s TaskQueue prevents nonce conflicts in concurrent scenarios.
Set Appropriate Gas Limits Set gas limits based on actual function complexity. Too low causes failure; too high wastes tokens (only burnt gas is charged).
Use Recent Block Hashes Block hashes expire after ~2 minutes. The Client’s cache handles this automatically, but manual transaction construction should fetch fresh hashes.