Documentation Index
Fetch the complete documentation index at: https://mintlify.com/near/near-api-ts/llms.txt
Use this file to discover all available pages before exploring further.
This guide covers everything you need to know about sending transactions on NEAR, from simple transfers to complex multi-action transactions.
Prerequisites
Install the NEAR API TypeScript library:
Overview
Sending transactions on NEAR involves three main steps:
- Create a signer - Manages your keys and signs transactions
- Define actions - Specify what the transaction should do
- Execute the transaction - Send it to the network
Setting Up a Signer
A signer handles transaction signing using your private keys. The library provides MemorySigner for most use cases.
Create a client
import { createTestnetClient } from 'near-api-ts';
const client = createTestnetClient();
Create a key service
import { createMemoryKeyService } from 'near-api-ts';
const keyService = createMemoryKeyService({
keySource: {
privateKey: 'ed25519:your-private-key-here'
}
});
Create the signer
import { createMemorySigner } from 'near-api-ts';
const signer = createMemorySigner({
signerAccountId: 'your-account.testnet',
client,
keyService
});
Sending NEAR Tokens
Simple Transfer
The most common transaction is transferring NEAR tokens:
import { createTestnetClient, createMemoryKeyService, createMemorySigner, transfer, near } from 'near-api-ts';
const client = createTestnetClient();
const keyService = createMemoryKeyService({
keySource: { privateKey: 'ed25519:your-private-key' }
});
const signer = createMemorySigner({
signerAccountId: 'sender.testnet',
client,
keyService
});
// Transfer 1 NEAR
const result = await signer.executeTransaction({
intent: {
action: transfer({ amount: near('1') }),
receiverAccountId: 'receiver.testnet'
}
});
console.log('Transaction hash:', result.transactionOutcome.id);
console.log('Status:', result.transactionOutcome.outcome.status);
Transfer with YoctoNEAR
You can also specify amounts in yoctoNEAR (1 NEAR = 10²⁴ yoctoNEAR):
import { transfer, yoctoNear } from 'near-api-ts';
// Transfer exactly 1,000,000 yoctoNEAR
await signer.executeTransaction({
intent: {
action: transfer({ amount: yoctoNear('1000000') }),
receiverAccountId: 'receiver.testnet'
}
});
// Or use the object syntax
await signer.executeTransaction({
intent: {
action: transfer({ amount: { yoctoNear: '1000000' } }),
receiverAccountId: 'receiver.testnet'
}
});
Working with NEAR Tokens
The NearToken type provides methods for token arithmetic:
import { near, transfer } from 'near-api-ts';
// Create token amounts
const amount1 = near('5');
const amount2 = near('2.5');
// Perform arithmetic
const total = amount1.add(amount2); // 7.5 NEAR
const remaining = amount1.sub(amount2); // 2.5 NEAR
// Comparisons
if (amount1.gt(amount2)) {
console.log('amount1 is greater');
}
// Access values
console.log('NEAR:', total.near); // 7.5
console.log('YoctoNEAR:', total.yoctoNear); // 7500000000000000000000000
// Use in transfer
await signer.executeTransaction({
intent: {
action: transfer({ amount: total }),
receiverAccountId: 'receiver.testnet'
}
});
Transaction Error Handling
Using Try-Catch
Handle errors with standard try-catch:
import { isNatError, transfer, near } from 'near-api-ts';
try {
await signer.executeTransaction({
intent: {
action: transfer({ amount: near('1000') }),
receiverAccountId: 'receiver.testnet'
}
});
console.log('Transfer successful');
} catch (error) {
if (isNatError(error, 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Signer.Balance.TooLow')) {
console.log('Insufficient balance');
console.log('Required:', error.context.transactionCost);
} else if (isNatError(error, 'MemorySigner.ExecuteTransaction.Rpc.Account.NotFound')) {
console.log('Receiver account does not exist');
} else {
console.error('Transaction failed:', error.kind);
}
}
Using Safe Variants
For more explicit error handling without exceptions:
const result = await signer.safeExecuteTransaction({
intent: {
action: transfer({ amount: near('1') }),
receiverAccountId: 'receiver.testnet'
}
});
if (result.ok) {
console.log('Success:', result.value.transactionOutcome.id);
} else {
console.error('Failed:', result.error.kind);
// Handle the error
}
Multi-Action Transactions
NEAR allows multiple actions in a single transaction:
import { createAccount, transfer, addFullAccessKey, near, randomEd25519KeyPair } from 'near-api-ts';
// Generate a new key pair for the new account
const newKeyPair = randomEd25519KeyPair();
// Create account with multiple actions
await signer.executeTransaction({
intent: {
actions: [
createAccount(),
transfer({ amount: near('5') }),
addFullAccessKey({ publicKey: newKeyPair.publicKey })
],
receiverAccountId: 'newaccount.sender.testnet'
}
});
console.log('New account created with 5 NEAR');
console.log('New account private key:', newKeyPair.privateKey);
Function Call Transactions
Calling Contract Methods
Call smart contract methods with function call actions:
import { functionCall, teraGas, near } from 'near-api-ts';
// Call a contract method
await signer.executeTransaction({
intent: {
action: functionCall({
functionName: 'set_status',
functionArgs: {
message: 'Hello, NEAR!'
},
gasLimit: teraGas('30'),
attachedDeposit: near('0') // No deposit
}),
receiverAccountId: 'contract.testnet'
}
});
With Attached Deposit
Many contract methods require NEAR tokens to be attached:
import { functionCall, teraGas, near } from 'near-api-ts';
// Mint an NFT with 0.1 NEAR attached
await signer.executeTransaction({
intent: {
action: functionCall({
functionName: 'nft_mint',
functionArgs: {
token_id: '123',
metadata: {
title: 'My NFT',
description: 'An example NFT'
},
receiver_id: 'owner.testnet'
},
gasLimit: teraGas('50'),
attachedDeposit: near('0.1')
}),
receiverAccountId: 'nft-contract.testnet'
}
});
Custom Argument Serialization
For non-JSON contracts, provide a custom serializer:
import { functionCall, teraGas, toJsonBytes } from 'near-api-ts';
await signer.executeTransaction({
intent: {
action: functionCall({
functionName: 'add_item',
functionArgs: { itemId: 42, itemName: 'Widget' },
gasLimit: teraGas('30'),
options: {
serializeArgs: ({ functionArgs }) => {
// Convert camelCase to snake_case for contract
return toJsonBytes({
item_id: functionArgs.itemId,
item_name: functionArgs.itemName
});
}
}
}),
receiverAccountId: 'contract.testnet'
}
});
Working with Gas
Gas Amounts
Gas can be specified in different units:
import { gas, teraGas } from 'near-api-ts';
// Using teraGas (most common)
const gasLimit1 = teraGas('30'); // 30 TGas
// Using raw gas units
const gasLimit2 = gas('30000000000000'); // 30 TGas in gas units
// Gas arithmetic
const total = teraGas('20').add(teraGas('10')); // 30 TGas
const remaining = teraGas('50').sub(teraGas('20')); // 30 TGas
// Comparisons
if (teraGas('50').gt(teraGas('30'))) {
console.log('50 TGas is greater than 30 TGas');
}
Gas Estimation
Different operations require different amounts of gas:
- Simple transfers: ~0.3 TGas
- Function calls: 30-50 TGas (depends on contract)
- Complex operations: 100-200 TGas
- Cross-contract calls: 100-300 TGas
import { functionCall, teraGas } from 'near-api-ts';
// Conservative gas limit for contract calls
await signer.executeTransaction({
intent: {
action: functionCall({
functionName: 'complex_operation',
functionArgs: {},
gasLimit: teraGas('100') // Generous limit
}),
receiverAccountId: 'contract.testnet'
}
});
Advanced Patterns
Transaction with Multiple Function Calls
import { functionCall, teraGas, near } from 'near-api-ts';
// Batch operations in one transaction
await signer.executeTransaction({
intent: {
actions: [
functionCall({
functionName: 'operation_1',
functionArgs: { data: 'first' },
gasLimit: teraGas('30')
}),
functionCall({
functionName: 'operation_2',
functionArgs: { data: 'second' },
gasLimit: teraGas('30')
}),
functionCall({
functionName: 'operation_3',
functionArgs: { data: 'third' },
gasLimit: teraGas('30')
})
],
receiverAccountId: 'contract.testnet'
}
});
Signing Without Executing
Sign a transaction without sending it to the network:
import { transfer, near } from 'near-api-ts';
const signedTransaction = await signer.signTransaction({
intent: {
action: transfer({ amount: near('1') }),
receiverAccountId: 'receiver.testnet'
}
});
// Later, send the signed transaction
const result = await client.sendSignedTransaction({
signedTransaction
});
Transaction Retry Logic
import { transfer, near } from 'near-api-ts';
async function sendWithRetry(maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const result = await signer.executeTransaction({
intent: {
action: transfer({ amount: near('1') }),
receiverAccountId: 'receiver.testnet'
}
});
return result;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
console.log(`Attempt ${attempt + 1} failed, retrying...`);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
const result = await sendWithRetry();
console.log('Transaction successful:', result.transactionOutcome.id);
Complete Example: Payment Flow
Here’s a complete example of a payment system with error handling:
import {
createTestnetClient,
createMemoryKeyService,
createMemorySigner,
transfer,
near,
isNatError
} from 'near-api-ts';
interface PaymentResult {
success: boolean;
transactionId?: string;
error?: string;
}
async function sendPayment(
fromAccount: string,
privateKey: string,
toAccount: string,
amount: string
): Promise<PaymentResult> {
try {
// Setup
const client = createTestnetClient();
const keyService = createMemoryKeyService({
keySource: { privateKey }
});
const signer = createMemorySigner({
signerAccountId: fromAccount,
client,
keyService
});
// Validate amount
const nearAmount = near(amount);
if (nearAmount.near <= 0) {
return {
success: false,
error: 'Amount must be positive'
};
}
// Execute transaction
const result = await signer.executeTransaction({
intent: {
action: transfer({ amount: nearAmount }),
receiverAccountId: toAccount
}
});
return {
success: true,
transactionId: result.transactionOutcome.id
};
} catch (error) {
// Handle specific errors
if (isNatError(error, 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Signer.Balance.TooLow')) {
return {
success: false,
error: 'Insufficient balance'
};
} else if (isNatError(error, 'MemorySigner.ExecuteTransaction.Rpc.Account.NotFound')) {
return {
success: false,
error: 'Receiver account not found'
};
} else {
return {
success: false,
error: 'Transaction failed'
};
}
}
}
// Usage
const result = await sendPayment(
'sender.testnet',
'ed25519:your-private-key',
'receiver.testnet',
'5.5'
);
if (result.success) {
console.log('Payment successful!');
console.log('Transaction ID:', result.transactionId);
} else {
console.error('Payment failed:', result.error);
}
Best Practices
// Validate account IDs
if (!receiverAccountId.includes('.')) {
throw new Error('Invalid account ID format');
}
// Validate amounts
const amount = near(userInput);
if (amount.near <= 0) {
throw new Error('Amount must be positive');
}
Use Appropriate Gas Limits
// Don't use excessive gas
const gasLimit = teraGas('30'); // Good for most operations
// Avoid
const gasLimit = teraGas('300'); // Unnecessarily high
Handle All Error Cases
try {
await signer.executeTransaction({ intent });
} catch (error) {
if (isNatError(error, 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Signer.Balance.TooLow')) {
// Handle insufficient balance
} else if (isNatError(error, 'MemorySigner.ExecuteTransaction.Rpc.Account.NotFound')) {
// Handle account not found
} else if (isNatError(error, 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Expired')) {
// Handle expired transaction
} else {
// Handle unknown errors
}
}
Store Transaction Receipts
const result = await signer.executeTransaction({ intent });
// Store important transaction data
const receipt = {
transactionId: result.transactionOutcome.id,
blockHash: result.transactionOutcome.blockHash,
status: result.transactionOutcome.outcome.status,
gasUsed: result.transactionOutcome.outcome.gasBurnt,
timestamp: new Date().toISOString()
};
console.log('Transaction receipt:', receipt);
Next Steps