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.
Overview
A Signer is a high-level abstraction that combines a Client, KeyService, and account context to provide a seamless transaction execution experience. It handles the complexity of transaction construction, signing, and submission to the network.
MemorySigner
The MemorySigner is the default implementation that manages transaction signing with automatic nonce management, access key selection, and concurrent transaction handling.
Type Definition
type MemorySigner = {
signerAccountId : AccountId ;
client : Client ;
keyService : MemoryKeyService ;
// Throwable variants
signTransaction : SignTransactionIntent ;
executeTransaction : ExecuteTransaction ;
// Safe variants
safeSignTransaction : SafeSignTransactionIntent ;
safeExecuteTransaction : SafeExecuteTransaction ;
};
Internal Architecture
The MemorySigner uses several internal components:
type MemorySignerContext = {
signerAccountId : AccountId ;
client : Client ;
keyService : MemoryKeyService ;
taskQueue : TaskQueue ; // Manages concurrent transactions
keyPool : KeyPool ; // Manages access keys
tasker : Tasker ; // Coordinates tasks
};
The internal components handle complex scenarios like:
Automatic nonce increment for sequential transactions
Access key selection from multiple available keys
Concurrent transaction queuing to prevent nonce conflicts
Creating a MemorySigner
Basic Creation
import {
createClient ,
createMemoryKeyService ,
createMemorySigner
} from '@near-api/client' ;
const client = createClient ({
transport: {
rpcEndpoints: {
archival: [{ url: 'https://rpc.testnet.near.org' }]
}
}
});
const keyService = createMemoryKeyService ({
keySources: [
{ privateKey: 'ed25519:...' }
]
});
const signer = createMemorySigner ({
signerAccountId: 'alice.near' ,
client ,
keyService
});
With Configuration
const signer = createMemorySigner ({
signerAccountId: 'alice.near' ,
client ,
keyService ,
keyPool: {
// Only use specific access keys
allowedAccessKeys: [
'ed25519:PublicKey1...' ,
'ed25519:PublicKey2...'
]
},
taskQueue: {
// Timeout for queued transactions
timeoutMs: 30000 // 30 seconds
}
});
Safe Creation
import { safeCreateMemorySigner } from '@near-api/client' ;
const result = safeCreateMemorySigner ({
signerAccountId: 'alice.near' ,
client ,
keyService
});
if ( result . ok ) {
const signer = result . value ;
} else {
// 'CreateMemorySigner.Args.InvalidSchema'
// 'CreateMemorySigner.Internal'
}
Signer Methods
executeTransaction
The primary method for executing transactions. It handles the full lifecycle:
Fetches account access keys
Selects an appropriate key from the key pool
Gets the current nonce and recent block hash
Constructs the transaction
Signs it using the KeyService
Submits it to the network via Client
Returns the transaction result
import { transfer , near } from '@near-api/client' ;
const result = await signer . executeTransaction ({
intent: {
receiverAccountId: 'bob.near' ,
action: transfer ({ amount: near ( '1' ) })
}
});
console . log ( result . transaction_outcome . outcome . status );
Multiple Actions
import { transfer , functionCall , near , teraGas } from '@near-api/client' ;
const result = await signer . executeTransaction ({
intent: {
receiverAccountId: 'contract.near' ,
actions: [
transfer ({ amount: near ( '0.1' ) }),
functionCall ({
functionName: 'my_method' ,
functionArgs: { key: 'value' },
gasLimit: teraGas ( '30' ),
attachedDeposit: near ( '0' )
})
]
}
});
try {
const result = await signer . executeTransaction ({
intent: {
receiverAccountId: 'bob.near' ,
action: myAction
}
});
console . log ( 'Success!' , result );
} catch ( error ) {
console . error ( 'Transaction failed:' , error );
}
const result = await signer . safeExecuteTransaction ({
intent: {
receiverAccountId: 'bob.near' ,
action: myAction
}
});
if ( ! result . ok ) {
switch ( result . error . kind ) {
case 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Receiver.NotFound' :
console . log ( 'Receiver account does not exist' );
break ;
case 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Signer.Balance.TooLow' :
console . log ( 'Insufficient balance' );
break ;
case 'MemorySigner.ExecuteTransaction.KeyPool.SigningKey.NotFound' :
console . log ( 'No valid signing key found' );
break ;
}
}
signTransaction
Sign a transaction without submitting it to the network:
const signedTx = await signer . signTransaction ({
intent: {
receiverAccountId: 'bob.near' ,
action: transfer ({ amount: near ( '1' ) })
}
});
console . log ( signedTx . transaction );
console . log ( signedTx . signature );
console . log ( signedTx . transactionHash );
This is useful when:
You want to inspect the signed transaction before sending
You’re implementing a transaction approval flow
You need to submit the transaction through a different channel
Key Pool Management
The KeyPool automatically manages access keys for the signer account:
Allowed Access Keys
Restrict which access keys the signer can use:
const signer = createMemorySigner ({
signerAccountId: 'alice.near' ,
client ,
keyService ,
keyPool: {
allowedAccessKeys: [
'ed25519:SpecificKey1...' ,
'ed25519:SpecificKey2...'
]
}
});
Without this configuration, the signer will attempt to use any access key that:
Exists on the account (fetched via RPC)
Has a corresponding private key in the KeyService
Access Key Selection
The KeyPool selects keys based on:
Full Access Keys : Preferred for general transactions
Function Call Keys : Used only if they match the transaction’s receiver and functions
Nonce availability : Keys with available nonces are prioritized
Task Queue
The TaskQueue ensures transaction ordering and prevents nonce conflicts:
Sequential Execution
When you call executeTransaction multiple times rapidly:
// All three will execute sequentially with proper nonces
const tx1 = signer . executeTransaction ({ intent: intent1 });
const tx2 = signer . executeTransaction ({ intent: intent2 });
const tx3 = signer . executeTransaction ({ intent: intent3 });
await Promise . all ([ tx1 , tx2 , tx3 ]);
The TaskQueue ensures:
Nonces are properly incremented
Transactions don’t conflict
Parallel execution when possible (using different keys)
Timeout Configuration
const signer = createMemorySigner ({
signerAccountId: 'alice.near' ,
client ,
keyService ,
taskQueue: {
timeoutMs: 60000 // Wait up to 60 seconds in queue
}
});
Signer Factory Pattern
For applications that need multiple signers, use the factory pattern:
import { createMemorySignerFactory } from '@near-api/client' ;
// Create factory once
const signerFactory = createMemorySignerFactory ({
client ,
keyService
});
// Create signers for different accounts
const aliceSigner = signerFactory ( 'alice.near' );
const bobSigner = signerFactory ( 'bob.near' );
const contractSigner = signerFactory ( 'contract.near' );
// All signers share the same client and keyService
Safe factory:
import { createSafeMemorySignerFactory } from '@near-api/client' ;
const safeSignerFactory = createSafeMemorySignerFactory ({
client ,
keyService
});
const result = safeSignerFactory ( 'alice.near' );
if ( result . ok ) {
const signer = result . value ;
}
Error Handling
The MemorySigner has comprehensive error types for all failure scenarios:
interface MemorySignerPublicErrorRegistry {
// Creation errors
'CreateMemorySigner.Args.InvalidSchema' : InvalidSchemaErrorContext ;
'CreateMemorySigner.Internal' : InternalErrorContext ;
// Execution errors - Key Pool
'MemorySigner.ExecuteTransaction.KeyPool.AccessKeys.NotLoaded' : {};
'MemorySigner.ExecuteTransaction.KeyPool.Empty' : {};
'MemorySigner.ExecuteTransaction.KeyPool.SigningKey.NotFound' : {};
// Execution errors - Task Queue
'MemorySigner.ExecuteTransaction.TaskQueue.Timeout' : { timeoutMs : number };
// Execution errors - Network
'MemorySigner.ExecuteTransaction.PreferredRpc.NotFound' : {};
'MemorySigner.ExecuteTransaction.Timeout' : { timeoutMs : number };
'MemorySigner.ExecuteTransaction.Aborted' : { reason : string };
'MemorySigner.ExecuteTransaction.Exhausted' : {};
// Execution errors - Transaction
'MemorySigner.ExecuteTransaction.Rpc.Transaction.Signer.Balance.TooLow' : {};
'MemorySigner.ExecuteTransaction.Rpc.Transaction.Receiver.NotFound' : { accountId : string };
'MemorySigner.ExecuteTransaction.Rpc.Transaction.Timeout' : {};
// Execution errors - Actions
'MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.CreateAccount.AlreadyExist' : {};
'MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.BelowThreshold' : {};
'MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.Balance.TooLow' : {};
'MemorySigner.ExecuteTransaction.Rpc.Transaction.Action.Stake.NotFound' : {};
// Sign errors
'MemorySigner.SignTransaction.Args.InvalidSchema' : InvalidSchemaErrorContext ;
// ... and more
}
Common Error Scenarios
No Valid Keys
Receiver Not Found
Insufficient Balance
Queue Timeout
const result = await signer . safeExecuteTransaction ({
intent: myIntent
});
if ( ! result . ok &&
result . error . kind === 'MemorySigner.ExecuteTransaction.KeyPool.SigningKey.NotFound' ) {
console . log ( 'The signer has no valid access keys' );
console . log ( 'Possible reasons:' );
console . log ( '1. KeyService does not have the private key' );
console . log ( '2. Account does not have matching access keys' );
console . log ( '3. allowedAccessKeys filter is too restrictive' );
}
const result = await signer . safeExecuteTransaction ({
intent: {
receiverAccountId: 'nonexistent.near' ,
action: transfer ({ amount: near ( '1' ) })
}
});
if ( ! result . ok &&
result . error . kind === 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Receiver.NotFound' ) {
console . log ( 'Receiver account does not exist:' ,
result . error . context . accountId );
}
const result = await signer . safeExecuteTransaction ({
intent: {
receiverAccountId: 'bob.near' ,
action: transfer ({ amount: near ( '1000000' ) })
}
});
if ( ! result . ok &&
result . error . kind === 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Signer.Balance.TooLow' ) {
console . log ( 'Signer account has insufficient balance' );
}
const result = await signer . safeExecuteTransaction ({
intent: myIntent
});
if ( ! result . ok &&
result . error . kind === 'MemorySigner.ExecuteTransaction.TaskQueue.Timeout' ) {
console . log ( 'Transaction timed out in queue after' ,
result . error . context . timeoutMs , 'ms' );
console . log ( 'Too many concurrent transactions or slow network' );
}
Complete Example
Here’s a complete example using all the concepts:
import {
createTestnetClient ,
createMemoryKeyService ,
createMemorySigner ,
randomEd25519KeyPair ,
transfer ,
functionCall ,
near ,
teraGas
} from '@near-api/client' ;
// 1. Setup
const client = createTestnetClient ();
const keyPair = randomEd25519KeyPair ();
const keyService = createMemoryKeyService ({
keySource: { privateKey: keyPair . privateKey }
});
const signer = createMemorySigner ({
signerAccountId: 'alice.testnet' ,
client ,
keyService
});
// 2. Simple transfer
const result1 = await signer . safeExecuteTransaction ({
intent: {
receiverAccountId: 'bob.testnet' ,
action: transfer ({ amount: near ( '1' ) })
}
});
if ( result1 . ok ) {
console . log ( 'Transfer successful!' );
}
// 3. Contract call with deposit
const result2 = await signer . safeExecuteTransaction ({
intent: {
receiverAccountId: 'contract.testnet' ,
action: functionCall ({
functionName: 'my_method' ,
functionArgs: { key: 'value' },
gasLimit: teraGas ( '50' ),
attachedDeposit: near ( '0.1' )
})
}
});
// 4. Multiple actions in one transaction
const result3 = await signer . safeExecuteTransaction ({
intent: {
receiverAccountId: 'contract.testnet' ,
actions: [
transfer ({ amount: near ( '0.1' ) }),
functionCall ({
functionName: 'deposit' ,
gasLimit: teraGas ( '30' )
})
]
}
});
// 5. Handle all errors
if ( ! result3 . ok ) {
console . error ( 'Transaction failed:' , result3 . error . kind );
console . error ( 'Context:' , result3 . error . context );
}
Best Practices
Reuse Signer Instances Create signer instances once and reuse them. The internal TaskQueue and KeyPool maintain state for optimal performance.
Use Safe Variants in Production Always use safe variants (safeExecuteTransaction, safeSignTransaction) in production to handle errors explicitly.
Configure Task Queue Timeout Set appropriate timeout values based on your use case:
High-frequency transactions: 10-30 seconds
User-initiated transactions: 60-120 seconds
Batch operations: Consider sequential processing
Monitor Key Pool Ensure your KeyService has private keys for all access keys on the signer account, or use allowedAccessKeys to restrict usage.