🧞♂️Custom Scripting
Custom processor scripts run in a Javascript (Node.js) runtime.
Event Processors
Learn about custom event processor scripts structure and handler function here.
Available Globals
To facilitate writing scripts faster and easier several global variables are available in your custom scripts. It is optional to use them.
💫 database
to store entities in a managed schemaless SQL database
Provides interface to a managed database for storing and retrieving entities or running SQL queries from within your custom processors.
1️⃣ database.upsert(...)
1️⃣ database.upsert(...)
Creates or updates an entity based on Type and Id. This function handles re-orgs automatically.
Remember that database
is always available as a global variable and you can access it anywhere in your custom script, no need to import or inject it.
Example usage:
await database.upsert({
// Entity type is used to group entities together,
// also will be used when querying your data.
entityType: 'Swap',
// Unique ID for this entity.
//
// Soem useful tips:
// - chainId makes sure if potentially same tx has happened on different chains it will be stored separately.
// - hash and logIndex make sure this event is stored uniquely.
// - hash also makes sure with potential reorgs we don't store same event twice.
// - logIndex also makes sure if such event has happened multiple times in same tx it will be stored separately.
entityId: `${event.chainId}-${event.txHash}-${event.log.logIndex}`,
// Horizon helps with handling re-orgs and chronological ordering of entities.
horizon: event.horizon,
// You can store any data you want.
// Must not include "entityId" field as it's already defined above.
chainId: event.chainId,
contractAddress: event.log.address,
blockTimestamp: event.blockTimestamp,
txHash: event.txhash,
// e.g. You can save all event args as-is:
// Note that internal storage of Flair is schemaless,
// so even each entity can have different fields for the same entityType.
...event.parsed.args,
})
2️⃣ database.get(...)
2️⃣ database.get(...)
Gets a single entity based on Type and Id.
Remember that database
is always available as a global variable and you can access it anywhere in your custom script, no need to import or inject it.
const pairId =
`${event.parsed.args.token0.toString()}#${event.parsed.args.token1.toString()}`;
// You can fetch single entity by its ID and Type
const pair = await database.get({
entityType: 'Pair',
// A best practice is to use lowercase for IDs (especially for addresses)
entityId: pairId.toLowerCase(),
// If you don't need a fresh data (e.g. just to check if entity exists)
// you can use cache (default: false)
cache: true,
});
// One usage of database.get() is to populate a complex entity
// if it does not exist yet.
if (!pair || !pair?.underlyingAsset) {
const contract = await blockchain.getContract(
transaction.chainId,
event.log.address,
['function asset() external view returns (address)'],
);
await database.upsert({
entityType: 'Pair',
entityId: pairId,
horizon: event.horizon,
chainId: event.chainId,
token0: event.parsed.args.token0,
token1: event.parsed.args.token1,
underlyingAsset: await contract.asset(),
});
}
Need help for your special database use-case?
Ping our engineers to help you on that 🙂
💫 blockchain
to interact with any contract on any chain
Provides interface to get ethers.js instances easily. You can still use ethers.*
variable directly if you need to.
1️⃣ blockchain.getProvider(chainId)
1️⃣ blockchain.getProvider(chainId)
Returns an ethers.js Provider instance, that uses all your RPC sources behind the scenes and failovers if one of them is not working.
Remember that blockchain
is always available as a global variable and you can access it anywhere in your custom script, no need to import or inject it.
Example usage:
// Note chainId must be defined in your manifest.yml
const provider = await blockchain.getProvider(chainId);
console.log(`My example block number`, {
blockNumber: await provider.getBlockNumber(),
});
2️⃣ blockchain.getContract(chainId, address, abi)
2️⃣ blockchain.getContract(chainId, address, abi)
Returns a read-only ethers.js Contract instance, that uses all your RPC sources behind the scenes and failovers if one of them is not working.
Remember that blockchain
is always available as a global variable and you can access it anywhere in your custom script, no need to import or inject it.
Example usage:
const contract = await blockchain.getContract(
transaction.chainId,
event.log.address, // This is the contract that emitted current event log
[
// A good practice is to put the ABI methods you use as
// inline, unless you're using many methods.
'function balanceOf(address) view returns (uint256)',
'function claimableBalanceOf(address) view returns (uint256)',
'function claimedBalanceOf(address) view returns (uint256)',
'function lockedBalanceOf(address) view returns (uint256)',
],
);
// Running all at once causes requests to be batched together towards RPC endpoints
const [balance, claimable, claimed, locked] = await Promise.allSettled([
contract.balanceOf(userAddress),
contract.claimedBalanceOf(userAddress),
contract.claimableBalanceOf(userAddress),
contract.lockedBalanceOf(userAddress),
]);
console.log(`My example data`, {
balance: balance.value,
claimable: claimable.value,
claimed: claimed.value,
locked: locked.value,
});
Need help?
Reach out to our engineers 🙂
💫 cache
to keep temporary key-value data for performance reasons
Provides interface to a managed Redis instance that must be used as temporary storage only for the purpose of performance gains, or idempotency facility. Remember that all keys will eventually be evicted based on LRU algorithm.
1️⃣ cache.get(key)
1️⃣ cache.get(key)
Get a specific cached item. If item was originally an object (or any other type) it'll be return as fully parsed (as original type).
Remember that cache
is always available as a global variable and you can access it anywhere in your custom script, no need to import or inject it.
Example usage:
const idempotencyKey =
`discord-notify:${chainId}-${transaction?.forkIndex}-${contractAddress}-${txHash}-${event?.log?.logIndex}`.toLowerCase();
const alreadySent = await cache.get(idempotencyKey);
if (!alreadySent) {
await integrations.discord.sendMessage({
guildId: process.env.DISCORD_GUILD_ID,
channelName: process.env.DISCORD_CHANNEL_NAME,
embeds: [embed],
});
await cache.set(idempotencyKey, true, { EX: 60 * 60 * 24 * 5 });
}
2️⃣ cache.set(key, value, options)
2️⃣ cache.set(key, value, options)
Sets a cache entry, the value will be stored as JSON.stringify and will be parsed back on retrieval.
Remember that cache
is always available as a global variable and you can access it anywhere in your custom script, no need to import or inject it.
options
object is same as @redis/client SetOptions, such as:
options.EX
key expiry duration in seconds.
// Cache some variable for up to 5 days
const someVariable = { abc: 123 };
await cache.set('my-unique-key', someVariable, { EX: 60 * 60 * 24 * 5 });
Need help?
Reach out to our engineers 🙂
Integrations
💫 prices
for ERC20 tokens in USD for any contract on any chain
Already built integration with CoinGecko and Moralis to get USD-equivalent value of any amount of ERC20 tokens right from your custom processors.
You don't need an API Key from these providers, but if you need you can set your own API Key, for example if you have a bigger plan with these providers.
Read the full docs here.
💫 discord
to send any custom messages to any Discord channel
Already built integration with Discord APIs to send any arbitrary message to any channel from right your custom processors.
Read the full docs here.
External NPM Libraries
Any packages defined in "dependencies"
field in your package.json will be automatically installed. Make sure that your lock files (e.g. pnpm-lock.yaml) do exist in your repo (same place as manifest.yml).
Last updated