- v0.1.1-beta.1Latest
- v0.1.1-beta.0
- v0.1.0-gamma.1
- v0.1.0-gamma.0
- v0.1.0-beta.49
- v0.1.0-beta.48
- v0.1.0-beta.47
- v0.1.0-beta.46
- v0.1.0-beta.45
- v0.1.0-beta.42
- v0.1.0-beta.41
- v0.1.0-beta.40
- v0.1.0-beta.39
- v0.1.0-beta.38
- v0.1.0-beta.37
- v0.1.0-beta.36
- v0.1.0-beta.35
- v0.1.0-beta.34
- v0.1.0-beta.33
- v0.1.0-beta.32
- v0.1.0-beta.31
- v0.1.0-beta.30
- v0.1.0-beta.29
- v0.1.0-beta.28
- v0.1.0-beta.27
- v0.1.0-beta.26
- v0.1.0-beta.25
- v0.1.0-beta.24
- v0.1.0-beta.23
- v0.1.0-beta.22
- v0.1.0-beta.21
- v0.1.0-beta.20
- v0.1.0-beta.19
- v0.1.0-beta.18
- v0.1.0-beta.17
- v0.1.0-beta.16
- v0.1.0-beta.15
- v0.1.0-beta.14
- v0.1.0-beta.13
- v0.1.0-beta.12
- v0.1.0-beta.11
- v0.1.0-beta.10
- v0.1.0-beta.9
- v0.1.0-beta.8
- v0.1.0-beta.7
- v0.1.0-beta.6
- v0.1.0-beta.5
- v0.1.0-beta.4
- v0.1.0-beta.2
- v0.1.0-beta.1
Capi
Capi is a WIP TypeScript toolkit for crafting interactions with Substrate-based chains.
Capi consists of FRAME-oriented utilities and a high-level functional effect system which facilitate multistep, multichain interactions without compromising on performance or safety.
⚠ This Is a Work in Progress
️Please share feedback or even join us in Capi’s development; issues and PRs are very welcome!
In Good Shape
- RPC
call
andsubscribe
utils - Metadata types and SCALE codecs
- Metadata-based codec derivation
- Storage key encoding
- Storage value decoding
- Creating and decoding extrinsics
Needs Love
- High-level “Effect” System
- Std lib of effects
TODO
- RPC Client Error Handing
- Get async iterable from RPC subscription
- … TODO, the remainder of this TODO section (we primarily use this repo’s issues)
Setup
Note: we have yet to publish a beta of Capi. Expect the first publish to occur in the next few days (written on June 10th, 2022).
If you’re using Deno, import via the denoland/x
specifier.
import * as C from "https://deno.land/x/capi/mod.ts";
Note: you may want to pin the version in your import specifier (
https://deno.land/x/capi@x.x.x/mod.ts
).
If you’re using Node, install Capi from NPM.
npm install capi
Then import as follows.
import * as C from "capi";
WIP DX vs. North Star
For now, we will manually instantiate an RPC client (in this case, with a proxy WebSocket URL).
const rpc = C.wsRpcClient(C.POLKADOT_PROXY_WS_URL);
// Use the client here
await rpc.close();
Our north star is a version of Capi which manages the connection lifecycle on your behalf.
const chain = C.chain(C.POLKADOT_PROXY_WS_URL);
Additionally, our north star is fluent. Instead of writing a pallet reference as follows.
const systemPallet = C.pallet(C.chain(C.POLKADOT_CHAIN_SPEC), "System");
One will write it like so:
const systemPallet = C.chain(C.POLKADOT_CHAIN_SPEC).pallet("System");
The following examples detail the north star experience, not the in-development experience. For examples of the current API’s usage, look in the examples
folder (all of which can be run with deno task example:<example-name>
).
Read a Balance
// 1. Which chain?
const chain = C.chain(C.POLKADOT_CHAIN_SPEC);
// 2. Which key within the balances storage map?
const accountId = chain.ss58(MY_ADDR).toAccountId32();
// 3. Which value within the storage map?
const value = await chain
.pallet("System")
.storageMap("Account")
.get(accountId)
.read(); // ... as opposed to `subscribe`
Note About Typings
Signatures
The signature value
is a union of Read<unknown>
and all possible error types.
assertTypeEquals<
typeof value,
C.Read<unknown> | C.WsRpcError | C.StorageEntryDneError | C.StorageValueDecodeError
>();
Narrow Error Handling
We can utilize an instanceof
check to narrow the result
before accessing the read value.
if (result instanceof Error) {
// Handle narrow error types here
} else {
// Handle `C.Read<unknown>` here
}
Assertion of Type
The on-chain world is evolving rapidly. This creates uncertainty regarding types. To mitigate this uncertainty, you can (optionally) utilize Capi’s virtual type system to assert a given shape.
const value = await chain
.pallet("System")
.storageMap("Account")
.get(accountId)
+ .as(C.$.sizedUint8Array(32))
.read();
There are three main reason to utilize as
:
- We can confirm that a given interaction’s type-level expectations align with the metadata before dispatch.
- Legibility: the
as
call makes obvious the value encapsulated by theRead
. - We can produce a narrow signature.
- C.Read<unknown> | C.WsRpcError | C.StorageEntryDneError | C.StorageValueDecodeError
+ C.Read<Uint8Array> | C.WsRpcError | C.StorageEntryDneError | C.StorageValueDecodeError
Transfer Some Dot
// 1. Which chain?
const chain = C.chain(C.POLKADOT_CHAIN_SPEC);
// 2. Where to send the funds?
const dest = chain.ss58(ALICE_ADDR).toMultiAddress();
// 3. Craft and submit the transaction.
await C
.pallet("Balances")
.txFactory("transfer")
.call(dest, 42);
.sign(signingFn)
.submit()
Note: it is up to the developer to supply
sign
with a signing function, which will vary depending on your environment, wallet, misc.
Derived Queries / Composing Effects
Let’s read the heads of Polkadot’s parachains. This requires that we first obtain a list of parachain IDs, and then use those IDs to read their heads.
// 1. Which pallet?
const pallet = C.chain(C.POLKADOT_CHAIN_SPEC).pallet("Paras");
// 2. What is the first step in the derived query? In this case, reading the heads.
const parachainHeads = pallet.storageMap("Heads");
// 3. Map from the to-be-evaluated result.
const parachainIds = await pallet
.entry("Parachains")
.as(C.$.array($.u32))
.map(parachainHeads.get)
.read();
Testing
In the future, Gitpod and dev containers will simplify spinning up a Capi development environments. The Dockerfile, Gitpod configuration and Dev Containers / Codespaces configuration are in need some finessing.
Make sure you have the following installed on your machine (and please submit issues if errors crop up).
System Requirements
- Rustup and the wasm32-unknown-unknown target
- Deno
- Docker
- NodeJS (only necessary if you’re going to run the build_npm task)
- Wasm Bindgen
- Binaryen
Bootstrapping
After cloning the repository, CD into it and execute the following.
deno task bootstrap
Running an Example
After running the bootstrap script, you should be able to run any of the examples.
deno task example:balances
Utilizing the Package in a NodeJS Project
Build the NPM package and link it locally.
deno task build_npm_pkg && cd target/npm && npm link
From the project in which you wish to use Capi…
npm link capi
Code Structure
You may have noticed that the Capi repository looks somewhat different from a traditional TypeScript repository. This is because Capi is developed Deno-first. Deno is a TypeScript runtime and toolkit written in Rust. Unlike NodeJS, Deno emphasizes web standards and exposes a performant and type-safe standard library. Deno-first TypeScript can be easily packaged for consumption in NodeJS, Browsers, CloudFlare Workers and other environments. Some things to note:
src
nor Distinct package/*
No We no longer need to think about the separation of code for the sake of packaging. We can think about separation of code in terms of what best suits our development needs.
For example, exports of util/types.ts
can be imported directly into any other TypeScript file, without specifying the dependency in a package manifest. We are free to use (for example) U2I
, the union to intersection utility, in out-of-band processes, the effect system or even GitHub workflow scripts. From anywhere in the repository, we can import and use any code with configuration overhead.
When it comes time to build our code for NPM distribution, DNT takes care of transforming our dependency graph into something that NodeJS and web browsers will understand.