0.0.9 • Published 1 year ago

@nietpham/cap-js v0.0.9

Weekly downloads
-
License
GPL-3.0
Repository
github
Last release
1 year ago

npm.io

A client library for the CAP Open Internet Service (OIS), implemented in JavaScript. The interface is based on the candid file Cap Candid allowing dApps to interact with the main canister. In addition, this client library will support endpoint caching using Kyasshu.

The CAP-js library is utilized to integrate UIs/FEs/Apps to CAP to query & surface data from CAP. Any frontend can query the transaction/activity history for any Token or NFTs that uses CAP. (Instead of having to integrate manually with each asset!)

IMPORTANT: CAP is currently in development 🚧 and will release in the first week of November, thus it is not on mainnet or usable yet. You might see our documentation is light on the SDK/Main repo still. We're delayed in this to focus on testing, but will soon update this page with guides & detailed examples for developers.

Table of Contents

Getting Started

⚠️ ⚠️ Library not stable, will stay unstable until Cap is finalized ⚠️ ⚠️

Install

yarn add @psychedelic/cap-js

Usage

To interface with Router and Root toolkits, you have to instantiate them in the client application.

The Router instance is long lived and can persist across your application lifetime, as the Canister Id it depends on is of fixed value (e.g. calls to the method get_index_canisters will be sent to the same canister id in the network you're connected to). For this reason, it's recommended ⚡️ to create the instance in the application top-level and reuse during the application lifetime.

The Root instance in the other hand is short-lived and only useful during the token contract use cases (e.g. calls to the method get_transaction are sent to the particular token contract canister id which is unique). As such, it's necessary 🤝 to create a new instance for each token contract canister id and reuse the instance in the context of the token contract.

These are available as:

import { CapRouter, CapRoot } from '@psychedelic/cap-js';

Here's an example of how to instantiate the CapRouter, which is similar to CapRoot:

import { CapRouter } from '@psychedelic/cap-js';

const getCapRouterInstance = async ({
  canisterId,
  host,
}: {
  canisterId?: string,
  host?: string,
}) => await CapRouter.init({
  host,
  canisterId,
});

// On a hypotetical application top-level or entry-point
(async () => {
	const capRouter = new getCapRouterInstance({
		canisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai',
		host: 'http://localhost:8000',
	});
})();

💡 The root canister id and host can be computed by an environment variable. Although, these parameters can be omited and the mainnet values are set by default.

Also, there's a Hosts object that can be used to retrieve defaults:

import { Hosts } from '@psychedelic/cap-js';

// The Mainnet
const mainnetHost = Hosts.mainnet;

Similarily, a CAP CanisterInfo object is available that provides defaults:

import { CanisterInfo } from '@psychedelic/cap-js';

// The `ic-history-router` mainnet canister id
const ICHistoryRouterCanisterId = CanisterInfo['ic-history-router'].mainnet;

Quick usage examples

In order to get transactions of a particular token (e.g: XTC token):

const tokenId = 'aanaa-xaaaa-aaaah-aaeiq-cai'	// XTC Canister Id

const { canister: rootTokenId } = capRouter.get_token_contract_root_bucket({canister: tokenId, witness})

const capRootXTC = await CapRoot.init({
  canisterId: rootTokenId[0],
})

const xtcTransactions = await capRootXTC.get_transactions()

Or instead, pass an instance of capRouter to have the inner call to get_token_contract_root_bucket handled for you:

const tokenId = 'aanaa-xaaaa-aaaah-aaeiq-cai'	// XTC Canister Id

const capRootXTC = await CapRoot.init({ tokenId, router: capRouter })

const xtcTransactions = await capRootXTC.get_transactions()

Alternatively, for the case where you don't have a CapRouter instance:

const tokenId = 'aanaa-xaaaa-aaaah-aaeiq-cai'	// XTC Canister Id

const capRootXTC = await CapRoot.init({
	tokenId,
	routerCanisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai',
	host: 'http://localhost:8000'
})

const xtcTransactions = await capRootXTC.get_transactions()

You're advised to understand Candid, the interface description language for the Dfinity Internet Computer Services, because ultimately, that's where the latest method signatures for the Service endpoints are defined, to avoid any typos described in the documentation.

Once you start looking at the original source-code, you might start questioning some decisions.

For example, why cap-js at time of writing is wrapping the Actor methods which are accessible directly in the actor (e.g. capRoot.actor.get_transactions())?

As it's mainly a wrapper to original method accessible in the actor and that's for your own convenience (e.g. provides default values) it also provides a chance to the Cache layer to intercept and handle those requests.

Since Cache is available (optionally) it helps to have a common API to switch between.

const tnx = await caRoot.get_transactions(...);
const tnx = await capCache.get_transactions(...);

Find more about here

Router Canister

capRouter.get_index_canisters(witness)

Return all Cap index canisters (Router and any replicas, when scaled)

Parameters

NameTypeDescription
witnessboolean?The optional Certified response, defaults to false

Returns

TypeDescription
GetIndexCanistersResponseAn object returning all index canisters. If witness = true the certified response will be appended to the response

Example

const indexCanisters = await capRouter.get_index_canisters(false);
console.log(indexCanisters);
{
	witness: [] | [Witness];
	canisters: Array<Principal>;
}

capRouter.get_token_contract_root_bucket({canister, witness})

For a given token contract, return the entry root bucket

Parameters

NameTypeDescription
canisterPrincipalThe canister Id of the requested root bucket
witnessboolean?The optional witness for the Certified response

Returns

TypeDescription
GetTokenContractRootBucketResponseAn object returning root canister for a given token. If witness = true the certified response will be appended to the response

Example

const tokenRootBucket = await capRouter.get_token_contract_root_bucket({
 canister: "aaaa-aa",
 witness: false,
});
console.log(tokenRootBucket);
{
	witness: [] | [Witness];
	canister: [] | [Principal];
}

capRouter.get_user_root_buckets({user, witness})

query a users root bucket, each user root bucket exposes an interface to interact with and get transactions

Parameters

NameTypeDescription
userPrincipalThe user Id of the requested root bucket
witnessboolean?The optional witness for the Certified response

Returns

TypeDescription
GetUserRootBucketsResponseAn object returning root canister for a given user. If witness = true the certified response will be appended to the response

Example

const userRootBucket = await capRouter.get_user_root_buckets({
 user: "avesb-mgo2l-ds25i-g7kd4-3he5l-z7ary-3biiq-sojiw-xjgbk-ich5l-mae",
 witness: false,
});
console.log(userRootBucket);
{
	witness: [] | [Witness];
	contracts: Array<Principal>;
}

capRouter.insert_new_users(contractId, users)

insert new users for a token contract

Parameters

NameTypeDescription
contractIdPrincipalThe token contract Id
usersArray of PrincipalA list of user principal

Returns

TypeDescription
Promise<undefined>an empty response ()

capRouter.install_bucket_code(principal)

instantiate a new bucket canister for a token

Parameters

NameTypeDescription
principalPrincipalprincipal Id of a newly created empty canister

Returns

TypeDescription
Promise<undefined>an empty response ()

Example

# create root bucket canister
dfx canister --wallet=$(dfx identity get-wallet) call aaaaa-aa create_canister "(record { cycles=(4_000_000_000_000:nat64); controller=(opt principal \"$(dfx identity get-principal)\") })"

# set cap as the controller for the root bucket
dfx canister --wallet=$(dfx identity get-wallet) call aaaaa-aa update_settings "(record { canister_id=(principal \"r7inp-6aaaa-aaaaa-aaabq-cai\"); settings=(record { controller = opt principal \"rrkah-fqaaa-aaaaa-aaaaq-cai\"; null; null; null; }) })"

# Check controller of bucket canister
dfx canister info r7inp-6aaaa-aaaaa-aaabq-cai

Notes

  • It is better to programmatically execute this install_bucket_code from your canister or through DFX.

Root Canister

capRoot.get_transaction(id, witness)

Return a specifc transaction based on global transaction Id from a token contract (tokenId)

Parameters

NameTypeDescription
idbigintThe global txnId of a transaction to return
witnessboolean?The optional witness for the Certified response

Returns

TypeDescription
GetTransactionResponseAn object returning either Delegate or Found. If witness = true the certified response will be appended to the response

Example

const transaction = await capRoot.get_transaction(
	BigInt(1), 
	false,
)
console.log(transaction);
{
	Delegate: [Principal, [] | [Witness]];
}
# or
{ 
	Found: [[] | [Event], [] | [Witness]] 
}

Notes

  • Unstable endpoint

capRoot.get_transactions({page, witness})

Get all transactions for a single token contract

Parameters

NameTypeDescription
pagenumber?The optional number of the page to query for transctions, each page can hold up to 64 transactions. Defaults to page 0.
witnessboolean?The optional witness for the Certified response

Returns

TypeDescription
GetTransactionsResponseBorrowedAn object returning an array of data as well as the page queried. If witness = true the certified response will be appended to the response

Example

const tokenTxns = await capRoot.get_transactions({
 page: 4,
 witness: false,
});

// or

const tokenTxns = await capRoot.get_transactions({
 witness: false,
}); // this will default to page 0

console.log(tokenTxns);
{
	data: Array<Event>;
	page: number;
	witness: [] | [Witness];
}

Notes

  • If no page param provided, we will query the first page, this is oppposite to what the main canister does which is query the last page transactions.

capRoot.get_user_transactions(({page, user, witness})

Get all user transactions for the token contract

NameTypeDescription
pagenumber?The optional number of the page to query for transctions, each page can hold up to 64 transactions. Defaults to page 0.
userPrincipalThe user principal of the requested transactions
witnessboolean?The optional witness for the Certified response

Returns

TypeDescription
GetTransactionsResponseBorrowedAn object returning an array of data as well as the page queried. If witness = true the certified response will be appended to the response

Example

import { cap } from '@psychedelic/cap-js'

const userTxns = await capRoot.get_user_transactions({
 userId: "avesb-mgo2l-ds25i-g7kd4-3he5l-z7ary-3biiq-sojiw-xjgbk-ich5l-mae",
 page: 4,
 witness: false,
});

// or

const userTxns = await capRoot.get_user_transactions({
 userId: "avesb-mgo2l-ds25i-g7kd4-3he5l-z7ary-3biiq-sojiw-xjgbk-ich5l-mae",
}); // this will default to page `0` and witness `false`

console.log(userTxns);
{
	data: Array<Event>;
	page: number;
	witness: [] | [Witness];
}

capRoot.insert({operation, caller, details})

Insert a transaction event to the token contract

NameTypeDescription
operationoperation
callerprincipal
detailsvec record

Returns

TypeDescription
Promise<bigint>a number

Notes

  • If no page param provided, we will query the first page, this is oppposite to what the main canister does which is query the last page transactions.

capRoot.get_bucket_for({})

ToDo

capRoot.get_next_canisters({})

ToDo

time : () -> (nat64) query;

capRoot.time([options])

ToDo

Kyasshu Layer

kyasshu is a cache layer, that is originally used in the for xtc token service by utilizing lambda, sqs, redis and dynamodb. Why would you want to use Kyasshu? Because caching allows you to efficiently reuse previously retrieved data, improving your application performance.

capRoot.get_all_user_transactions(userId, LastEvaluatedKey)

Return all of the user transactions for userId, if LastEvaluatedKey is returned, you must provide it in subsequent calls to query the rest of the data.

Parameters

NameTypeDescription
userIdprincipalThe user Id of the requested transactions
LastEvaluatedKeystring?The optional LastEvaluatedKey, If LastEvaluatedKey is empty, then the "last page" of results has been processed and there is no more data to be retrieved. If LastEvaluatedKey is not empty, it does not necessarily mean that there is more data in the result set. The only way to know when you have reached the end of the result set is when LastEvaluatedKey is empty.

Returns

TypeDescription
GetTransactionsResponseBorrowedAn object returning an array of data as well as the page queried. If witness = true the certified response will be appended to the response

Example

const capCache = new CapCache();

let userTxns = await capCache.get_all_user_transactions({
	user: Principal.from("zxt4e-ian3w-g4ll2-3n5mz-lfqkc-eyj7k-yg6jl-rsbud-f6sft-zdfq3-pae"),
});

userTxns = await capCache.get_all_user_transactions({
	user: Principal.from("zxt4e-ian3w-g4ll2-3n5mz-lfqkc-eyj7k-yg6jl-rsbud-f6sft-zdfq3-pae"),
	LastEvaluatedKey: userTxns.LastEvaluatedKey,
});

console.log(userTxns);
{
	Items?: {
			[key: string]: Event;
	}[] | undefined;
	LastEvaluatedKey?: {
			[key: string]: any;
	} | undefined;
}

Notes

  • Unstable endpoint

API

  • ToDo

Local Development

  • ToDo

Roadmap

  • Cache every endpoint with Kyasshu

  • Support update calls to the main Cap canister

Testing

  • ToDo

To run tests, run the following command

  npm run test

Roadmap

  • Cache every endpoint with Kyasshu or with a cache canister

  • Support update calls to the main Cap canister

Contributing

Contributions are always welcome!

License

MIT