Ocelloids[^1] SDK is an open-source software development kit written in Typescript for Polkadot and Substrate based networks. It simplifies the implementation of multi-chain programs and provides domain-specific logic for different pallets.
Check our Quickstart Guide to set up your first Ocelloids program.
npm i @sodazone/ocelloids-sdk
Provides essential abstractions, reactive operators, base type converters, and pallet-independent functionality.
Source code packages/core.
npm i @sodazone/ocelloids-sdk-contracts
Provides operators and type converters for the contracts pallet.
Source code packages/pallets/contracts.
Refer to the SDK documentation.
You can also explore some example applications in the examples/ folder.
Here's a basic usage example that filters balance transfer events from finalized blocks above a certain amount:
import { WsProvider } from '@polkadot/api';
import {
SubstrateApis,
finalizedBlocks,
filterEvents
} from '@sodazone/ocelloids-sdk';
const apis = new SubstrateApis({
polkadot: {
provider: new WsProvider('wss://rpc.polkadot.io')
}
});
apis.rx.polkadot.pipe(
finalizedBlocks(),
filterEvents({
section: 'balances',
method: 'Transfer',
'data.amount': { $bn_gte: '50000000000' }
})
).subscribe(
x => console.log(x.toHuman())
);
Extended event output with contextual information:
{
eventId: '16134479-5-1',
extrinsicId: '16134479-5',
extrinsicPosition: 1,
blockNumber: '16,134,479',
method: 'Transfer',
section: 'balances',
index: '0x0502',
data: {
from: '14GuP6QAfK9uwo3MQ9LrcmEqttcrtoNfDaSHn2BVaYcJJBg6',
to: '12But7r26e2UwZkSYC8bU5nQdyfqWXswZEwS1tbH9nD8CXvK',
amount: '54,719,854,400'
}
}
The event identifier eventId
consists of the block number, the position of the extrinsic within the block,
and the position of the event within the extrinsic.
The filterEvents
operator used in the example is composed of the following stack:
source.pipe(
// Extracts extrinsics with events
extractTxWithEvents(),
// Flattens and correlates events for nested
// batch, multisig, proxy and derivative calls
flattenCalls(),
// Filters at the extrinsic level
// mainly for success or failure
mongoFilter(extrinsicsCriteria),
// Maps the events with
// block and extrinsic context
extractEventsWithTx(),
// Filters over the events
mongoFilter(eventsQuery),
// Share multicast
share()
)
Here's an example that monitors the balance of an account with an amount threshold condition:
import { WsProvider } from '@polkadot/api';
import { switchMap } from 'rxjs';
import {
SubstrateApis,
mongoFilter
} from '@sodazone/ocelloids-sdk';
const apis = new SubstrateApis({
polkadot: {
provider: new WsProvider('wss://rpc.polkadot.io')
}
});
apis.query.polkadot.pipe(
switchMap(q => q.system.account(
'15QFBQY6TF6Abr6vA1r6opRh6RbRSMWgBC1PcCMDDzRSEXf5'
)),
mongoFilter({
'data.free': { $bn_lt: '6038009840776279' }
})
).subscribe(x => console.log('Account Balance:', x.toHuman()));
Now, let's explore a dynamic query example that collects and monitors balance transfer events for a set of addresses, starting from ALICE's address:
import { WsProvider } from '@polkadot/api';
import '@polkadot/api-augment';
import {
SubstrateApis,
blocksInRange,
filterEvents,
ControlQuery
} from '@sodazone/ocelloids-sdk';
function transfersOf(addresses: string[]) {
return ControlQuery.from({
$and: [
{ section: 'balances' },
{ method: 'Transfer' },
{
$or: [
{ 'data.from': { $in: addresses } },
{ 'data.to': { $in: addresses } }
]
}
]
});
}
const apis = new SubstrateApis({
polkadot: {
provider: new WsProvider('wss://rpc.polkadot.io')
}
});
const seenAddresses = new Set<string>([ALICE]);
let dynamicQuery = transfersOf([...seenAddresses]);
apis.rx.polkadot.pipe(
blocksInRange(16134439, 100),
filterEvents(dynamicQuery)
).subscribe(event => {
console.log('Event: ', event.toHuman());
if (apis.promise.polkadot.events.balances.Transfer.is(event) ) {
const transfer = event.data;
const from = transfer.from.toPrimitive();
const to = transfer.to.toPrimitive();
seenAddresses.add(from);
seenAddresses.add(to);
// Updates dynamic query, probably you want
// to update it only for new seen addresses
dynamicQuery.change(transfersOf([...seenAddresses]));
}
});
This example introduces the concept of a dynamic query. As new addresses are encountered, the dynamic query is updated with the newly seen addresses by calling dynamicQuery.change()
. This ensures that future events will be filtered based on the updated set of addresses.
To contribute to the development of Ocelloids, ensure that you have the following requirements installed:
To set up the development environment, follow these steps:
corepack enable
yarn install
yarn build
The Ocelloids repository utilizes workspaces for modularization.
The repository contains three main folders: packages
, examples
and tools
.
The packages
folder contains the Ocelloids SDK implementation, which is further divided into core
, pallets
, and test
modules.
The development support tools include functionalities such as chain data capture to assist in the development of the SDK.
The examples/ folder contains example applications.
To run unit tests, use the following command:
yarn test
Additional test data and mocks are available in packages/test/
for your convenience. If necessary, you can capture specific data using the development support tools located in tools/
for testing purposes.
If you encounter the issue of @sodazone/ocelloids-sdk-test
being marked as unresolved
in the spec
test files after building the project, you can resolve it by following these steps:
For further assistance or troubleshooting, please consult the project's documentation or reach out to the Ocelloids community.
[^1]: Noun ocelloid (plural ocelloids): “(microbiology) a cellular structure found in unicellular microorganisms that is analogous in structure and function to eyes, which focus, process and detect light.”