Skip to content

Instantly share code, notes, and snippets.

@slavafomin
Last active April 7, 2022 20:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slavafomin/1bcb401b5dc336bb4f9a2005b1660cbd to your computer and use it in GitHub Desktop.
Save slavafomin/1bcb401b5dc336bb4f9a2005b1660cbd to your computer and use it in GitHub Desktop.
TON (TonWeb) — get address transactions in JavaScript (TypeScript)
import TonWeb from 'tonweb';
import {
BatchLoadedHandler,
getAddressTransactions,
HttpErrorHandler,
TransactionDiscoveredHandler,
} from './address-transactions';
(async () => {
const provider = new TonWeb.HttpProvider('https://toncenter.com/api/v2/jsonRPC', {
apiKey: '<MAINNET-API-KEY>',
});
const myAddress = 'EQBvI0aFLnw2QbZgjMPCLRdtRHxhUyinQudg6sdiohIwg5jL';
const onBatchLoaded: BatchLoadedHandler = (
event => {
const { offsetLt, offsetHash, transactions } = event;
if (offsetLt) {
console.log(
`Got ${transactions.length} transaction(s) before ` +
`transaction #${offsetLt}:${offsetHash}`
);
} else {
console.log(
`Got ${transactions.length} last transaction(s)`
);
}
}
);
const onTransactionDiscovered: TransactionDiscoveredHandler = (
event => {
const { transaction } = event;
const { lt, hash } = transaction.transaction_id;
console.log(`Discovered transaction #${lt}:${hash}`);
}
);
const onHttpError: HttpErrorHandler = (
error => console.error(error)
);
let skipBeforeTime: (number | undefined);
while (true) {
const result = await getAddressTransactions({
provider,
address: myAddress,
skipBeforeTime,
itemsPerPage: 5,
useHistoricalNodes: true,
onBatchLoaded,
onTransactionDiscovered,
onHttpError,
});
for (const transaction of result.transactions) {
const { lt, hash } = transaction.transaction_id;
const {
source,
destination,
value,
msg_data,
} = transaction.in_msg;
const isExternal = !source;
const hasOutMessages = (
transaction.out_msgs.length > 0
);
let payload: (Uint8Array | undefined);
let message: (string | undefined);
switch (msg_data['@type']) {
case 'msg.dataText': {
message = TonWeb.utils.base64toString(msg_data.text || '');
break;
}
case 'msg.dataRaw': {
payload = TonWeb.utils.base64ToBytes(msg_data.body || '');
break;
}
default: {
console.warn(`Unknown payload type: ${msg_data['@type']}`);
break;
}
}
console.log(
`Processing transaction #${lt}:${hash}\n` +
`from: ${isExternal ? 'external' : source}\n` +
`to: ${destination}\n` +
`value: ${TonWeb.utils.fromNano(value)}\n` +
`has out messages: ${hasOutMessages ? 'Yes' : 'No'}\n` +
(message ? `message: ${message}\n` : '')
);
}
skipBeforeTime = result.lastTransactionTime;
console.log(`Waiting for 5 seconds…`);
await wait(5 * 1000);
}
})();
function wait(timeout: number) {
return new Promise(resolve => setTimeout(resolve, timeout));
}
import TonWeb from 'tonweb';
import {
AddressType,
HttpProvider,
GetTransactionsResult,
GetTransactionsResultTransaction,
} from 'tonweb';
interface ProcessAddressTransactionsResult {
transactions: GetTransactionsResultTransaction[];
lastTransactionTime?: number;
}
export type BatchLoadedHandler = (
(event: BatchLoadedEvent) => void
);
export interface BatchLoadedEvent {
offsetLt?: string;
offsetHash?: Hash;
transactions: GetTransactionsResultTransaction[];
}
type Hash = string;
export type TransactionDiscoveredHandler = (
(event: TransactionDiscoveredEvent) => void
);
export type HttpErrorHandler = (
(error: Error) => void
);
export interface TransactionDiscoveredEvent {
transaction: GetTransactionsResultTransaction;
}
/**
* Returns transactions for the specified address by
* querying the server and processing the transactions
* iteratively batch-by-batch, optionally limited by the
* specified time.
*
* Historical nodes could be used to return all transactions
* for the specified address from the beginning of time.
*/
export async function getAddressTransactions(options: {
provider: HttpProvider;
address: AddressType;
skipBeforeTime?: number;
itemsPerPage?: number;
useHistoricalNodes?: boolean;
maxRetries?: number;
retryDelay?: number;
onBatchLoaded?: BatchLoadedHandler;
onTransactionDiscovered?: TransactionDiscoveredHandler;
onHttpError?: HttpErrorHandler;
}): Promise<ProcessAddressTransactionsResult> {
const {
provider,
skipBeforeTime,
itemsPerPage = 20,
useHistoricalNodes,
} = options;
const address = new TonWeb.Address(options.address);
let lastTransactionTime: (number | undefined);
let isLastPage = false;
let offsetLt: (string | undefined);
let offsetHash: (Hash | undefined);
let transactions: GetTransactionsResultTransaction[] = [];
const newTransactions: GetTransactionsResultTransaction[] = [];
const processedTransactions = new Set<string>();
// Fetching transactions batch-by-batch, until all
// the desired transactions were processed
loop: do {
transactions = await fetchTransactions({
provider,
address,
// At this moment TonCenter returns overlapping results
// in its responses, when offset LT i used, so we have
// to drop one transaction from the response. Increasing
// the page size by one to accord for this discrepancy.
// @todo: remove this when it's fixed.
limit: (offsetLt ? itemsPerPage + 1 : itemsPerPage),
lt: offsetLt,
hash: offsetHash,
archival: useHistoricalNodes,
maxRetries: options.maxRetries,
retryDelay: options.retryDelay,
onHttpError: options.onHttpError,
});
options.onBatchLoaded?.({
offsetLt,
offsetHash,
transactions,
});
// Checking if we've reached the end
if (transactions.length === 0) {
break;
}
// Setting last transaction time to the time
// of the first transaction from the first batch
if (!lastTransactionTime) {
lastTransactionTime = transactions[0].utime;
}
for (const transaction of transactions) {
// Stopping the further processing if the
// desired time mark is reached
// @todo: check if it should be "<" or "<="
if (
(skipBeforeTime !== undefined) &&
transaction.utime <= skipBeforeTime
) {
break loop;
}
// Skipping transaction that are already processed.
// At this moment TonCenter returns overlapping results
// in its responses.
// @todo: remove this when it's fixed.
const { lt, hash } = transaction.transaction_id;
const transactionId = `${lt}:${hash}`;
if (processedTransactions.has(transactionId)) {
continue;
}
newTransactions.push(transaction);
processedTransactions.add(transactionId)
options.onTransactionDiscovered?.({ transaction });
}
// Getting offset values for the next batch API call
// -----
const { transaction_id } = transactions[
(transactions.length - 1)
];
offsetLt = transaction_id.lt;
offsetHash = transaction_id.hash;
isLastPage = (transactions.length < itemsPerPage);
} while (!isLastPage);
return {
lastTransactionTime,
transactions: newTransactions,
};
}
async function fetchTransactions(options: {
provider: HttpProvider;
address: AddressType;
limit?: number;
lt?: string;
hash?: string;
archival?: any;
maxRetries?: number;
retryDelay?: number;
onHttpError?: HttpErrorHandler;
}): Promise<GetTransactionsResult> {
const {
provider,
limit,
lt,
hash,
archival,
maxRetries = 5,
retryDelay = 3000,
} = options;
const address = new TonWeb.Address(options.address);
let triesCount = 0;
let gotResponse = false;
while (!gotResponse) {
triesCount++;
try {
return await provider.getTransactions(
address.toString(false),
limit,
parseInt(lt, 10),
hash,
undefined,
archival
);
} catch (error: any) {
options.onHttpError?.(error);
if (triesCount >= maxRetries) {
throw error;
}
await wait(retryDelay);
}
}
}
function wait(timeout: number) {
return new Promise(resolve => setTimeout(resolve, timeout));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment