Home

Awesome

<div align="center"> <picture> <img src="https://github.com/sodazone/ocelloids-sdk/blob/main/.github/assets/oc_sdk.png?raw=true" width="330" height="auto" alt="" /> </picture>

Packages | Documentation | Examples

<p align="center"> <a href="https://github.com/sodazone/ocelloids-sdk/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/sodazone/ocelloids-sdk/ci.yml?branch=main&color=69D2E7&labelColor=A7DBD8" alt="CI" /></a> <a href="https://github.com/sodazone/ocelloids-sdk/blob/main/LICENSE"><img src="https://img.shields.io/github/license/sodazone/ocelloids-sdk?color=69D2E7&labelColor=A7DBD8" alt="License" /></a> </p> </div>

Ocelloids1 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.

Features

Quickstart

Check our Quickstart Guide to set up your first Ocelloids program.

Installation

Ocelloids Core

<a href="https://www.npmjs.com/package/@sodazone/ocelloids-sdk"> <img src="https://img.shields.io/npm/v/@sodazone/ocelloids-sdk?color=69D2E7&labelColor=69D2E7&logo=npm&logoColor=333333" alt="npm @sodazone/ocelloids-sdk" /> </a>
npm i @sodazone/ocelloids-sdk

Provides essential abstractions, reactive operators, base type converters, and pallet-independent functionality.

Source code packages/core.

Ocelloids Contracts

<a href="https://www.npmjs.com/package/@sodazone/ocelloids-sdk-contracts"> <img src="https://img.shields.io/npm/v/@sodazone/ocelloids-sdk-contracts?color=69D2E7&labelColor=69D2E7&logo=npm&logoColor=333333" alt="npm @sodazone/ocelloids-sdk-contracts" /> </a>
npm i @sodazone/ocelloids-sdk-contracts

Provides operators and type converters for the contracts pallet.

Source code packages/pallets/contracts.

Usage

Refer to the SDK documentation.

You can also explore some example applications in the examples/ folder.

Example: Filtering Transfer Events

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())
);
<details> <summary>Extended event output - click to expand</summary>

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'
  }
}
</details>

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.

<details> <summary>Filter events operator details - click to expand</summary>

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()
)
</details>

Example: Account Balance

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()));

Example: Dynamic Query

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:

<details> <summary>Dynamic query example - click to expand</summary>
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]));
  }
});
</details>

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.

Development

Requirements

To contribute to the development of Ocelloids, ensure that you have the following requirements installed:

Install

To set up the development environment, follow these steps:

  1. Install the latest LTS version of Node.js.

  2. Enable Corepack at the root of the project:

corepack enable
  1. Install dependencies:
yarn install
  1. Build Ocelloids libraries:
yarn build

Project Layout

The Ocelloids repository utilizes workspaces for modularization.

The repository contains three main folders: packages, examples and tools.

Packages

The packages folder contains the Ocelloids SDK implementation, which is further divided into core, pallets, and test modules.

Tools

The development support tools include functionalities such as chain data capture to assist in the development of the SDK.

Examples

The examples/ folder contains example applications.

Testing

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.

Troubleshooting

Visual Studio Code

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.

Footnotes

  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.”