Overview
The GetIntentTransactionHistory endpoint provides a paginated view of intent transaction history with summary information optimized for display. Unlike SearchIntents, this endpoint returns lightweight IntentSummary objects and supports cursor-based pagination for efficient browsing of large histories.
Use Cases
- Build transaction history pages with pagination
- Display user transaction history in UI
- Create activity feeds
- Export transaction data
- Analytics and reporting
- Mobile apps with infinite scroll
Request Parameters
All parameters are optional:
- page (Page): Pagination configuration
- column (string): Column to paginate by (typically “id” or “createdAt”)
- before (object): Cursor for previous page
- after (object): Cursor for next page
- sort (SortBy[]): Sorting configuration
- column (string): Column to sort by
- order (SortOrder):
DESC or ASC
- pageSize (number): Number of results per page (default: 20)
- more (boolean): Indicates if more pages are available
Response
The response includes:
- intents (IntentSummary[]): Array of intent summary objects
- nextPage (Page): Pagination cursor for the next page
IntentSummary Structure
Lightweight summary object containing:
- id (number): Internal database ID
- intentId (string): Unique intent identifier
- status (IntentStatus): Current status
- ownerAddress (string): Wallet address
- originChainId (number): Source chain ID
- destinationChainId (number): Destination chain ID
- originIntentAddress (string): Origin intent contract address
- destinationIntentAddress (string): Destination intent contract address
Transaction Hashes
- depositTransactionHash (string): Deposit transaction hash
- depositTransactionStatus (TransactionStatus): Deposit status
- originTransactionHash (string): Origin transaction hash
- originTransactionStatus (TransactionStatus): Origin status
- destinationTransactionHash (string): Destination transaction hash
- destinationTransactionStatus (TransactionStatus): Destination status
- originTokenAddress (string): Source token contract
- originTokenAmount (number): Source token amount
- originTokenMetadata (TokenMetadata): Source token details (symbol, decimals, logo)
- destinationTokenAddress (string): Destination token contract
- destinationTokenAmount (number): Destination token amount
- destinationTokenMetadata (TokenMetadata): Destination token details
Timestamps
- createdAt (string): Creation timestamp
- updatedAt (string): Last update timestamp
Examples
Basic Request (First Page)
const historyResponse = await fetch(
'https://trails-api.sequence.app/rpc/Trails/GetIntentTransactionHistory',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Access-Key': 'YOUR_ACCESS_KEY'
},
body: JSON.stringify({
page: {
pageSize: 20,
sort: [{
column: 'createdAt',
order: 'DESC'
}]
}
})
}
);
const { intents, nextPage } = await historyResponse.json();
console.log(`Loaded ${intents.length} intents`);
intents.forEach(intent => {
console.log(`${intent.intentId}: ${intent.status}`);
console.log(` ${intent.originChainId} → ${intent.destinationChainId}`);
console.log(` Amount: ${intent.quote.fromAmount} → ${intent.quote.toAmount}`);
});
// Check if there are more pages
if (nextPage) {
console.log('More pages available');
}
Paginated Loading
import { SortOrder, type IntentSummary, type Page } from "@0xtrails/api";
async function loadAllHistory() {
const allIntents: IntentSummary[] = [];
let page: Page | undefined = {
pageSize: 50,
sort: [{ column: 'createdAt', order: SortOrder.DESC }]
};
while (page) {
const { intents, nextPage }: { intents: IntentSummary[], nextPage: Page } = await fetch(
'https://trails-api.sequence.app/rpc/Trails/GetIntentTransactionHistory',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Access-Key': 'YOUR_ACCESS_KEY'
},
body: JSON.stringify({ page })
}
).then(r => r.json());
allIntents.push(...intents);
page = nextPage;
console.log(`Loaded ${allIntents.length} intents so far...`);
}
return allIntents;
}
const allHistory = await loadAllHistory();
console.log(`Total intents: ${allHistory.length}`);
import { SortOrder, type IntentSummary, type Page } from '@0xtrails/api';
import { useState, useEffect, useRef } from 'react';
function getExplorerUrl(chainId: number, txHash: string) {
const explorers = {
1: 'https://etherscan.io/tx/',
137: 'https://polygonscan.com/tx/',
8453: 'https://basescan.org/tx/',
42161: 'https://arbiscan.io/tx/',
10: 'https://optimistic.etherscan.io/tx/'
};
return explorers[chainId] + txHash;
}
export const IntentTransactionHistory = () => {
const [intents, setIntents] = useState<IntentSummary[]>([]);
const [nextPage, setNextPage] = useState<Page | null>({
pageSize: 20,
sort: [{ column: 'createdAt', order: SortOrder.DESC }]
});
const [loading, setLoading] = useState(false);
const observerTarget = useRef(null);
const loadMore = async () => {
if (!nextPage || loading) return;
setLoading(true);
try {
const response = await fetch(
'https://trails-api.sequence.app/rpc/Trails/GetIntentTransactionHistory',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Access-Key': 'YOUR_ACCESS_KEY'
},
body: JSON.stringify({ page: nextPage })
}
);
const data = await response.json();
setIntents(prev => [...prev, ...data.intents]);
setNextPage(data.nextPage);
} catch (error) {
console.error('Failed to load intents:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) {
loadMore();
}
},
{ threshold: 1 }
);
if (observerTarget.current) {
observer.observe(observerTarget.current);
}
return () => observer.disconnect();
}, [nextPage, loading]);
useEffect(() => {
loadMore();
}, []);
return (
<div className="transaction-history">
<h2>Transaction History</h2>
<div className="intents-list">
{intents.map(intent => (
<IntentCard key={intent.intentId} intent={intent} />
))}
</div>
{loading && <div>Loading...</div>}
{nextPage && <div ref={observerTarget} />}
{!nextPage && <div>No more transactions</div>}
</div>
);
}
export const IntentCard = ({ intent }: { intent: IntentSummary }) => {
return (
<div className="intent-card">
<div className="intent-header">
<span className="intent-id">{intent.intentId.slice(0, 8)}...</span>
<span className={`status-${intent.status.toLowerCase()}`}>
{intent.status}
</span>
</div>
<div className="intent-route">
<div className="token-info">
<img src={intent.originTokenMetadata.logoUri} alt="" />
<span>{intent.originTokenMetadata.symbol}</span>
<span>{intent.originTokenAmount}</span>
</div>
<span className="arrow">→</span>
<div className="token-info">
<img src={intent.destinationTokenMetadata.logoUri} alt="" />
<span>{intent.destinationTokenMetadata.symbol}</span>
<span>{intent.destinationTokenAmount}</span>
</div>
</div>
<div className="intent-details">
<div>Chain {intent.originChainId} → Chain {intent.destinationChainId}</div>
<div>{new Date(intent.createdAt).toLocaleString()}</div>
</div>
{intent.depositTransactionHash && (
<div className="transaction-links">
<a href={getExplorerUrl(intent.originChainId, intent.depositTransactionHash)}
target="_blank" rel="noopener noreferrer">
View Deposit TX
</a>
{intent.destinationTransactionHash && (
<a href={getExplorerUrl(intent.destinationChainId, intent.destinationTransactionHash)}
target="_blank" rel="noopener noreferrer">
View Destination TX
</a>
)}
</div>
)}
</div>
);
}
Sorting Options
// Sort by creation date (newest first)
{
page: {
pageSize: 20,
sort: [{ column: 'createdAt', order: 'DESC' }]
}
}
// Sort by creation date (oldest first)
{
page: {
pageSize: 20,
sort: [{ column: 'createdAt', order: 'ASC' }]
}
}
// Sort by ID
{
page: {
pageSize: 20,
sort: [{ column: 'id', order: 'DESC' }]
}
}
// Multiple sort columns
{
page: {
pageSize: 20,
sort: [
{ column: 'status', order: 'ASC' },
{ column: 'createdAt', order: 'DESC' }
]
}
}
Filtering by Status
async function getCompletedIntents() {
const { intents } = await fetch(
'https://trails-api.sequence.app/rpc/Trails/GetIntentTransactionHistory',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Access-Key': 'YOUR_ACCESS_KEY'
},
body: JSON.stringify({
page: {
pageSize: 100,
sort: [{ column: 'createdAt', order: 'DESC' }]
}
})
}
).then(r => r.json());
return intents.filter(i => i.status === 'SUCCEEDED');
}
IntentSummary objects are much lighter than full Intent objects. Use this endpoint for list views and only fetch full intent details when needed using GetIntent.
Best Practices
- Use reasonable page sizes: 20-50 items is optimal for most UIs
- Implement infinite scroll: Better UX than traditional pagination buttons
- Cache responses: Store pages locally to avoid refetching
- Show loading states: Display spinners while loading next page
- Handle errors gracefully: Network failures shouldn’t break the entire list
Always handle the case where nextPage is null, indicating no more results are available.
Next Steps
- Use
GetIntent to fetch full details when user clicks on an intent
- Use
GetIntentReceipt to show detailed transaction information
- Implement search/filter functionality on top of pagination
- Add real-time updates using
WaitIntentReceipt for pending intents