The Wallet API is in Beta. Endpoints and response formats may change.
Overview
The Token Transfers endpoint retrieves all token transfer activity for a Solana wallet, including detailed sender and recipient information. Unlike the full transaction history , this endpoint focuses specifically on transfers, making it ideal for payment tracking and transfer monitoring.
The endpoint returns up to 100 transfers per request (default 50). Use the cursor parameter with pagination.nextCursor to fetch the next page, and read pagination.hasMore to know when more results are available.
When to use this
Use the Token Transfers API when you need to:
Track payments : monitor incoming payments for payment processors.
Build a transfer feed : display a simple “sent/received” activity feed.
Monitor specific tokens : track transfers of a specific token (e.g., USDC payments).
Identify counterparties : see who sent or received tokens.
Generate receipts : create payment receipts with sender/recipient details.
Detect suspicious activity : monitor unusual transfer patterns.
Quickstart
Basic transfers query
Get recent incoming and outgoing transfers:
const getWalletTransfers = async ( address ) => {
const url = `https://api.helius.xyz/v1/wallet/ ${ address } /transfers?api-key=YOUR_API_KEY` ;
const response = await fetch ( url );
if ( ! response . ok ) {
throw new Error ( `HTTP error! status: ${ response . status } ` );
}
const data = await response . json ();
console . log ( `Found ${ data . data . length } transfers` );
// Display recent transfers
data . data . forEach ( transfer => {
const date = new Date ( transfer . timestamp * 1000 ). toLocaleString ();
const direction = transfer . direction === 'in' ? 'Received' : 'Sent' ;
const counterparty = transfer . counterparty . slice ( 0 , 8 ) + '...' ;
console . log ( ` \n ${ direction } - ${ date } ` );
console . log ( `Amount: ${ transfer . amount } ${ transfer . symbol || transfer . mint . slice ( 0 , 8 ) + '...' } ` );
console . log ( ` ${ transfer . direction === 'in' ? 'From' : 'To' } : ${ counterparty } ` );
console . log ( `Signature: ${ transfer . signature . slice ( 0 , 20 ) } ...` );
});
return data ;
};
getWalletTransfers ( "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY" );
import requests
from datetime import datetime
def get_wallet_transfers ( address : str ):
url = f "https://api.helius.xyz/v1/wallet/ { address } /transfers"
headers = { "X-Api-Key" : "YOUR_API_KEY" }
response = requests.get(url, headers = headers)
response.raise_for_status()
data = response.json()
print ( f "Found { len (data[ 'data' ]) } transfers" )
# Display recent transfers
for transfer in data[ 'data' ]:
date = datetime.fromtimestamp(transfer[ 'timestamp' ]).strftime( '%Y-%m- %d %H:%M:%S' )
direction = 'Received' if transfer[ 'direction' ] == 'in' else 'Sent'
counterparty = transfer[ 'counterparty' ][: 8 ] + '...'
symbol = transfer.get( 'symbol' ) or transfer[ 'mint' ][: 8 ] + '...'
print ( f " \n { direction } - { date } " )
print ( f "Amount: { transfer[ 'amount' ] } { symbol } " )
print ( f " { 'From' if transfer[ 'direction' ] == 'in' else 'To' } : { counterparty } " )
print ( f "Signature: { transfer[ 'signature' ][: 20 ] } ..." )
return data
get_wallet_transfers( "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY" )
curl "https://api.helius.xyz/v1/wallet/86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY/transfers?api-key=YOUR_API_KEY"
Filter by direction
Filter the results client-side to get only incoming or outgoing transfers:
Incoming Only
Outgoing Only
const getIncomingTransfers = async ( address ) => {
const data = await getWalletTransfers ( address );
const incoming = data . data . filter ( t => t . direction === 'in' );
console . log ( `Received ${ incoming . length } incoming transfers` );
incoming . forEach ( transfer => {
console . log ( `Received ${ transfer . amount } ${ transfer . symbol } from ${ transfer . counterparty . slice ( 0 , 8 ) } ...` );
});
return incoming ;
};
const getOutgoingTransfers = async ( address ) => {
const data = await getWalletTransfers ( address );
const outgoing = data . data . filter ( t => t . direction === 'out' );
console . log ( `Made ${ outgoing . length } outgoing transfers` );
outgoing . forEach ( transfer => {
console . log ( `Sent ${ transfer . amount } ${ transfer . symbol } to ${ transfer . counterparty . slice ( 0 , 8 ) } ...` );
});
return outgoing ;
};
Query parameters
Parameter Type Default Description limitinteger 50 Maximum number of transfers to return (1-100) cursorstring - Pagination cursor from previous response
{
"data" : [
{
"signature" : "5wHu1qwD7Jsj3xqWjdSEJmYr3Q5f5RjXqjqQJ7jqEj7jqEj7jqEj7jqEj7jqEj7jqE" ,
"timestamp" : 1704067200 ,
"direction" : "in" ,
"counterparty" : "HXsKP7wrBWaQ8T2Vtjry3Nj3oUgwYcqq9vrHDM12G664" ,
"mint" : "So11111111111111111111111111111111111111111" ,
"symbol" : "SOL" ,
"amount" : 1.5 ,
"amountRaw" : "1500000000" ,
"decimals" : 9
},
{
"signature" : "4aHu2qwD8Jtj4xqWjdSEJmYr3Q5f5RjXqjqQJ7jqEj7jqEj7jqEj7jqEj7jqEj7jqE" ,
"timestamp" : 1704067100 ,
"direction" : "out" ,
"counterparty" : "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" ,
"mint" : "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ,
"symbol" : "USDC" ,
"amount" : 100.0 ,
"amountRaw" : "100000000" ,
"decimals" : 6
}
],
"pagination" : {
"hasMore" : true ,
"nextCursor" : "5wHu1qwD7Jsj3xqWjdSEJmYr3Q5f5RjXqjqQJ7jqEj7jqEj7jqEj7jqEj7jqEj7jqE"
}
}
Field notes
direction : relative to the wallet you’re querying. in is tokens received (incoming payment); out is tokens sent (outgoing payment).
counterparty : for in transfers, the sender; for out transfers, the recipient.
amount : human-readable transfer amount, already divided by decimals. Use this for display (e.g., 1.5 SOL, 100.0 USDC).
amountRaw : the same amount as a raw integer string, before decimal adjustment (e.g., "1500000000" for 1.5 SOL). Serialized as a string to avoid floating-point precision loss. Use this for on-chain instructions or precise arithmetic: amount = parseInt(amountRaw) / 10**decimals.
mint : token mint address (So11111111111111111111111111111111111111111 for native SOL).
symbol : token symbol. Not all tokens have one; fall back to the mint address when symbol is null.
Use cases
Track payment history for a merchant
Monitor incoming USDC payments:
const trackMerchantPayments = async ( merchantWallet ) => {
const data = await getWalletTransfers ( merchantWallet );
// Filter for incoming USDC transfers
const usdcPayments = data . data . filter ( t =>
t . direction === 'in' &&
t . mint === 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC
);
console . log ( `Received ${ usdcPayments . length } USDC payments` );
const totalReceived = usdcPayments . reduce (( sum , t ) => sum + t . amount , 0 );
console . log ( `Total USDC Received: $ ${ totalReceived . toFixed ( 2 ) } ` );
// Display each payment
usdcPayments . forEach ( payment => {
const date = new Date ( payment . timestamp * 1000 ). toLocaleString ();
console . log ( ` ${ date } : $ ${ payment . amount } from ${ payment . counterparty } ` );
});
return {
count: usdcPayments . length ,
total: totalReceived ,
payments: usdcPayments
};
};
Generate a payment receipt
Create a detailed receipt for a specific transfer:
const generatePaymentReceipt = async ( address , signature ) => {
const data = await getWalletTransfers ( address );
const transfer = data . data . find ( t => t . signature === signature );
if ( ! transfer ) {
console . log ( 'Transfer not found' );
return null ;
}
const receipt = {
receiptId: transfer . signature . slice ( 0 , 16 ),
date: new Date ( transfer . timestamp * 1000 ). toISOString (),
type: transfer . direction === 'in' ? 'Payment Received' : 'Payment Sent' ,
amount: ` ${ transfer . amount } ${ transfer . symbol || 'tokens' } ` ,
from: transfer . direction === 'in' ? transfer . counterparty : address ,
to: transfer . direction === 'out' ? transfer . counterparty : address ,
transactionUrl: `https://orbmarkets.io/tx/ ${ transfer . signature } `
};
console . log ( '--- PAYMENT RECEIPT ---' );
Object . entries ( receipt ). forEach (([ key , value ]) => {
console . log ( ` ${ key } : ${ value } ` );
});
return receipt ;
};
Monitor suspicious transfer patterns
Detect unusual transfer activity:
const detectSuspiciousActivity = async ( address ) => {
const data = await getWalletTransfers ( address );
const recentTransfers = data . data . filter ( t => {
const hourAgo = Date . now () / 1000 - 3600 ;
return t . timestamp > hourAgo ;
});
// Check for high frequency
if ( recentTransfers . length > 100 ) {
console . log ( `Warning: ${ recentTransfers . length } transfers in the last hour` );
}
// Check for large amounts
const largeTransfers = recentTransfers . filter ( t => {
// Assuming USDC/stablecoins
return t . amount > 10000 && t . decimals === 6 ;
});
if ( largeTransfers . length > 0 ) {
console . log ( `Warning: ${ largeTransfers . length } large transfers (>$10k) in the last hour` );
}
// Check for transfers to same address
const counterparties = recentTransfers . map ( t => t . counterparty );
const duplicates = counterparties . filter (( item , index ) => counterparties . indexOf ( item ) !== index );
if ( duplicates . length > 5 ) {
console . log ( `Warning: Multiple transfers to the same address` );
}
return {
recentCount: recentTransfers . length ,
largeTransfers: largeTransfers . length ,
suspiciousPatterns: duplicates . length > 5
};
};
Build a transfer activity feed
Create a user-friendly activity feed:
const buildTransferFeed = async ( address ) => {
const data = await getWalletTransfers ( address );
const feed = data . data . map ( transfer => {
const date = new Date ( transfer . timestamp * 1000 );
const timeAgo = getTimeAgo ( date );
return {
id: transfer . signature ,
direction: transfer . direction ,
title: transfer . direction === 'in' ? 'Received' : 'Sent' ,
subtitle: ` ${ transfer . amount } ${ transfer . symbol || 'tokens' } ` ,
description: transfer . direction === 'in'
? `from ${ transfer . counterparty . slice ( 0 , 8 ) } ...`
: `to ${ transfer . counterparty . slice ( 0 , 8 ) } ...` ,
timeAgo ,
explorerUrl: `https://orbmarkets.io/tx/ ${ transfer . signature } `
};
});
return feed ;
};
function getTimeAgo ( date ) {
const seconds = Math . floor (( new Date () - date ) / 1000 );
if ( seconds < 60 ) return 'Just now' ;
if ( seconds < 3600 ) return ` ${ Math . floor ( seconds / 60 ) } m ago` ;
if ( seconds < 86400 ) return ` ${ Math . floor ( seconds / 3600 ) } h ago` ;
return ` ${ Math . floor ( seconds / 86400 ) } d ago` ;
}
Reconcile payments
Match transfers against expected payments:
const reconcilePayments = async ( address , expectedPayments ) => {
const data = await getWalletTransfers ( address );
const recentTransfers = data . data . filter ( t =>
t . direction === 'in' &&
t . mint === 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC
);
const reconciliation = expectedPayments . map ( expected => {
const match = recentTransfers . find ( t =>
Math . abs ( t . amount - expected . amount ) < 0.01 &&
t . counterparty === expected . from
);
return {
orderId: expected . orderId ,
expectedAmount: expected . amount ,
status: match ? 'Received' : 'Pending' ,
receivedAmount: match ?. amount ,
signature: match ?. signature ,
timestamp: match ?. timestamp
};
});
console . log ( 'Payment Reconciliation:' );
reconciliation . forEach ( r => {
console . log ( `Order ${ r . orderId } : ${ r . status } ` );
});
return reconciliation ;
};
// Example usage
const expected = [
{ orderId: 'ORDER-001' , amount: 100.00 , from: 'ABC...' },
{ orderId: 'ORDER-002' , amount: 250.50 , from: 'XYZ...' }
];
reconcilePayments ( "86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY" , expected );
For wallets with many transfers, page through results with the cursor parameter and pagination.hasMore:
const getAllTransfers = async ( address ) => {
let allTransfers = [];
let cursor = null ;
do {
const url = cursor
? `https://api.helius.xyz/v1/wallet/ ${ address } /transfers?api-key=YOUR_API_KEY&cursor= ${ cursor } `
: `https://api.helius.xyz/v1/wallet/ ${ address } /transfers?api-key=YOUR_API_KEY` ;
const response = await fetch ( url );
const data = await response . json ();
allTransfers = allTransfers . concat ( data . data );
cursor = data . pagination . hasMore ? data . pagination . nextCursor : null ;
console . log ( `Fetched ${ allTransfers . length } transfers so far...` );
} while ( cursor );
console . log ( ` \n Total transfers: ${ allTransfers . length } ` );
return allTransfers ;
};
Best practices
Filter client-side for specific tokens. The API returns all token transfers. Filter by mint address to track specific tokens like USDC or SOL.
Combine with the Identity API. Use the Identity endpoint to show human-readable names for known counterparties (exchanges, protocols, and others).
Cache recent transfers. Transfer data doesn’t change. Cache results and only fetch new transfers since your last query.
Paginate for complete history. Implement pagination to handle wallets with thousands of transfers efficiently.
Handle a missing symbol. Not all tokens have a symbol field. Fall back to the mint address when symbol is null.
Transfers vs transaction history
Feature Transfers Transaction History Focus Only token transfers All transaction types Data Sender/recipient info Balance changes for all tokens Use case Payment tracking Complete activity log Performance Faster, simpler More comprehensive
Use Transfers when you only care about payments. Use Transaction History when you need complete balance change data.
Common errors
Error Code Description Solution 400 Invalid wallet address format Verify the address is a valid base58 Solana address 401 Missing or invalid API key Check your API key is included in the request 429 Rate limit exceeded Reduce request frequency or upgrade your plan
Next steps
Wallet History Complete transaction history with per-transaction balance changes.
Wallet API Overview All Wallet API endpoints and shared conventions.
API Reference Request and response schemas for token transfers.