Release of @kadena/client Interacting with the Kadena Blockchain
// the blogpost is compatible with the following versions of "@kadena/client": "0.0.5", "@kadena/chainweb-node-client": "0.0.4", "@kadena/pactjs-cli": "0.0.4"
// the blogpost is compatible with the following versions of "@kadena/client": "0.0.5", "@kadena/chainweb-node-client": "0.0.4", "@kadena/pactjs-cli": "0.0.4"
We’ve released a new version of @kadena/client. Head over to Github @kadena/client/README.md for the updated docs.
The Kadena.js team has created a library that allows Javascript/Typescript users to easily interact with the Kadena Blockchain. Creating contracts is explicitly left out of the library as it is much morecomplicated to transpile Javascript to Pact.
Interaction with the Kadena Blockchain works in multiple ways. With the @kadena/client tool, there are two ways you will be able to interact with the Kadena Blockchain. The two ways are:
-
Contract based; and
There’s also information on an Automated Way of Signing using Chainweaver, which will be later detailed in this article. We will also be exploring the concepts and rationale of @kadena/client.
Please note that we are not going over installation of nodejs and package management as this information is widely available on the internet. We will, however, be providing the bare minimum of the information and details you will need in order to start using @kadena/client, which are:
-
install nodejs, version 14.x or 16.x
-
create a directory, bootstrap a
package.json
by runningnpm init
ornpm init -y
to use defaults -
install typescript
npm install -g typescript
-
install the client
npm install @kadena/client
-
install the commandline tool
npm install @kadena/pactjs-cli
npm init -y # creates a package.json npm install @kadena/client @kadena/pactjs-cli tsc --init # creates a tsconfig.json
npm init -y # creates a package.json npm install @kadena/client @kadena/pactjs-cli tsc --init # creates a tsconfig.json
Contract based interaction using @kadena/client
We wanted @kadena/client
to be independent of anything so this is just a tool
that can be used with arbitrary contracts. In addition, there exists another
reason why you have to generate the interfaces that are used by
@kadena/client
. You can use information from the blockchain or from your own
smart contracts, locally.
As you will see, we are providing a repository with templates to use with the template side of this tool.
Load contracts from the blockchain
Using the commandline tool @kadena/pactjs-cli
, download the contracts you want
to create Typescript interfaces for.
mkdir contracts && npx @kadena/pactjs-cli retrieve-contract --out "./contracts/coin.module.pact" --module "coin"
mkdir contracts && npx @kadena/pactjs-cli retrieve-contract --out "./contracts/coin.module.pact" --module "coin"
There are several options to retrieve contracts from another network or chain.
Help information on retrieve-contract
pactjs retrieve-contract --help
pactjs retrieve-contract --help
> pactjs retrieve-contract --help Usage: index retrieve-contract [options] Retrieve contract from api.chainweb.com in a /local call Options: -m, --module <module> The module you want to retrieve (e.g. "coin") -o, --out <file> File to write the contract -n, --network <network> Network to retrieve from (default "mainnet") (default: "mainnet") -c, --chain <number> Chain to retrieve from (default 1) (default: 1) -h, --help display help for command
> pactjs retrieve-contract --help Usage: index retrieve-contract [options] Retrieve contract from api.chainweb.com in a /local call Options: -m, --module <module> The module you want to retrieve (e.g. "coin") -o, --out <file> File to write the contract -n, --network <network> Network to retrieve from (default "mainnet") (default: "mainnet") -c, --chain <number> Chain to retrieve from (default 1) (default: 1) -h, --help display help for command
Generate interfaces
Using the contract we will now generate all the functions (defun
s) with their
(typed) arguments and the capabilities (defcap
s).
pactjs contract-generate --file "./contracts/coin.module.pact"
pactjs contract-generate --file "./contracts/coin.module.pact"
The log shows what has happened. Inside the node_modules
directory, a new
package has been created: .kadena/generated
. This package is extending the
@kadena/client
types to give you type information. Make sure to add
"types": [".kadena/client"]
to your tsconfig.json
.
Building a simple transaction from the contract
Now that everything is bootstrapped, we can start building transactions.
Create a new file and name it transfer.ts
(or .js
).
import { Pactjs } from '@kadena/client'; // store the builder in a variableconst transactionBuilder = // basic call results in Pact code `(coin.transfer "k:your-pubkey" "k:receiver-pubkey" 231.0)` Pact.modules.coin .transfer('k:your-pubkey', 'k:receiver-pubkey', 231) // add necessary coin.GAS capability (this defines who pays the gas) .addCap('coin.GAS', 'your-pubkey') // add necessary coin.TRANSFER capability .addCap( 'coin.TRANSFER', 'your-pubkey', 'k:your-pubkey', 'k:receiver-pubkey', 231, ) // the minimum you NEED to add is the sender of this transaction .setMeta({ sender: 'your-pubkey', });
import { Pactjs } from '@kadena/client'; // store the builder in a variableconst transactionBuilder = // basic call results in Pact code `(coin.transfer "k:your-pubkey" "k:receiver-pubkey" 231.0)` Pact.modules.coin .transfer('k:your-pubkey', 'k:receiver-pubkey', 231) // add necessary coin.GAS capability (this defines who pays the gas) .addCap('coin.GAS', 'your-pubkey') // add necessary coin.TRANSFER capability .addCap( 'coin.TRANSFER', 'your-pubkey', 'k:your-pubkey', 'k:receiver-pubkey', 231, ) // the minimum you NEED to add is the sender of this transaction .setMeta({ sender: 'your-pubkey', });
Take note of the following:
-
namespaced arguments (
k:
,w:
etc) are account-names, where non-namespaced arguments are public-keys -
the contract doesn’t specify whether you need to pass an account-name or public-key. This is knowledge that can be obtained by inspecting the contract downloaded earlier or consulting the documentation for the contract.
-
addCap
function accepts acapability
and apublic-key
of the signer of the capability. The other arguments are defined by the contract.coin.GAS
doesn't have any arguments, coin.TRANSFER does. -
setMeta
s object has asender
property, which is apublic-key
.
Manually singing the transaction
To sign the transaction, you can use the builder to output something that can be
pasted into the SigData
of Chainweaver.
// createTransaction() will calculate hashes and// finalizes the unsigned transactionconst unsignedTransaction = transactionBuilder.createTransaction(); console.log(JSON.stringify(unsignedTransaction));
// createTransaction() will calculate hashes and// finalizes the unsigned transactionconst unsignedTransaction = transactionBuilder.createTransaction(); console.log(JSON.stringify(unsignedTransaction));
Automated sign request to Chainweaver desktop
Using the transaction we can send a sign-request to Chainweaver. (NB: this can only with the desktop version, not the web-version, as it's exposing port 9467
In the future we will provide an interface with WalletConnect. This is not yet
finalized. Once it is, we’ll update the @kadena/client
accordingly
import { signAndSubmitWithChainweaver, signWithChainweaver, } from '@kadena/client'; // pass the transactionBuilder object, as metadata can still be changed const submitResult = signAndSubmitWithChainweaver(transactionBuilder); // use the finalized transaction, and sign it with Chainweaver cont signedTransaction = signWithChainweaver(unsignedTransaction) .then(console.log) .catch(console.error);
import { signAndSubmitWithChainweaver, signWithChainweaver, } from '@kadena/client'; // pass the transactionBuilder object, as metadata can still be changed const submitResult = signAndSubmitWithChainweaver(transactionBuilder); // use the finalized transaction, and sign it with Chainweaver cont signedTransaction = signWithChainweaver(unsignedTransaction) .then(console.log) .catch(console.error);
Take note of the following:
-
createTransaction()
will finalize the transaction. The hash will be calculated. Things likesender
,gasPrice
orgasLimit
CANNOT be changed anymore. -
signWithChainweaver
needs the finalized transaction -
signAndSubmitWithChainweaver
needs the "open" transaction, as it needs to calculate the hash for the metadata (sender, gas-parameters)
Template based interaction using @kadena/client
To provide contract-developers a way to communicate how their contracts should be used, we added a way to get autocompletion for templates. Contract-developers can now provide their contracts that consumers of their smart-contract can use in Javascript.
Load the contract repository
For now we have not added a way to directly generate the code from a remote git repository. Cloning the template repository as a submodule is a great option. This gives you a way to version the source of the templates.
git submodule add \ git@github.com:kadena-community/kadena-coin-templates.git \ ./templates/
git submodule add \ git@github.com:kadena-community/kadena-coin-templates.git \ ./templates/
Useful git submodule
commands (click to open)
-
Add a Git repository as a submodule:
git submodule add repository_url
-
Add a Git repository as a submodule at the specified directory:
git submodule add repository_url path/to/directory
-
Update every submodule to its latest commit:
git submodule foreach git pull
-
Install a repository’s specified submodules (after cloning the repo):
git submodule update --init --recursive
Generate code from templates
Usually a template directory/repository contains multiple templates, but they’re all from the same source. So we’re grouping them per directory/repository. This is done by selecting the directory as input for the command.
This command will result in one file containing all the templates.
pactjs template-generate --file ./templates/kadena-coin-templates/ --out ./generated/kadena-coin-templates.ts
pactjs template-generate --file ./templates/kadena-coin-templates/ --out ./generated/kadena-coin-templates.ts
Notes on the input (--file
) and output (--out
):
-f, --file
-
selecting a file as input will create ONLY code for that file
-
selecting a directory as input will create code for ALL the templates in the directory
-o, --out
-
when the output is a file, the code for the templates will end up in that file
-
when the output is a directory, an
index.ts
will be created in that directory, containing the code for the templates
A function is generated from a template
Each file in the repository is converted to a function that can be called. The function has one argument; an object that contains named key-value pairs for each variable in the template.
For example, a bogus template that looks like this
# ./hello.txt This is a Hello, {{name}}!
# ./hello.txt This is a Hello, {{name}}!
Will have it’s function call
import myTemplates from './myTemplates'; myTemplates.hello({ name: 'Albert' });
import myTemplates from './myTemplates'; myTemplates.hello({ name: 'Albert' });
Of course this isn’t a valid template to be used as a transaction, so this won’t work. But this outlines the general idea of how templates are used.
Using the generated code
Let’s say we’re using this template. Templates aren’t valid yaml
. They are
however checked to be valid transactions when used as templates.
code: |- (coin.transfer "{{fromAcct}}" "{{toAcct}}" {{amount}})data:publicMeta: chainId: '{{chain}}' sender: { { fromAcct } } gasLimit: 2500 gasPrice: 1.0e-8 ttl: 600networkId: { { network } }signers: - pubKey: { { fromKey } } caps: - name: 'coin.TRANSFER' args: [{ { fromAcct } }, { { toAcct } }, { { amount } }] - name: 'coin.GAS' args: []type: exec
code: |- (coin.transfer "{{fromAcct}}" "{{toAcct}}" {{amount}})data:publicMeta: chainId: '{{chain}}' sender: { { fromAcct } } gasLimit: 2500 gasPrice: 1.0e-8 ttl: 600networkId: { { network } }signers: - pubKey: { { fromKey } } caps: - name: 'coin.TRANSFER' args: [{ { fromAcct } }, { { toAcct } }, { { amount } }] - name: 'coin.GAS' args: []type: exec
Each of the {{name}}
s are variables that can be passed to the template
function.
The function returns a CommandBuilder
, this can be used in the
signAndSubmitWithChainweaver(cmd)
or to .createTransaction()
and use in
signWithChainweaver(unsignedTx)
as shown here
import kadenaCoinTemplates from './templates/kadena-coin-templates'; // this returns a commandBuilderconst commandBuilder = kadenaCoinTemplates['safe-transfer']({ fromAcct: 'k:sender-pubkey', toAcct: 'k:receiver-pubkey', fromKey: 'sender-pubkey', amount: '231', chain: '1', network: 'mainnet01',}); const unsignedTransaction = commandBuilder.createTransaction();
import kadenaCoinTemplates from './templates/kadena-coin-templates'; // this returns a commandBuilderconst commandBuilder = kadenaCoinTemplates['safe-transfer']({ fromAcct: 'k:sender-pubkey', toAcct: 'k:receiver-pubkey', fromKey: 'sender-pubkey', amount: '231', chain: '1', network: 'mainnet01',}); const unsignedTransaction = commandBuilder.createTransaction();
Further development
This is the launch post of @kadena/client
. Next steps will be to see what the
community thinks of this approach. We'd love to hear your feedback and use
cases, especially when the current @kadena/client
and @kadena/pactjs-cli
isn't sufficient.
We hope that you found this article helpful and informative!
Contact the team
We monitor our Discord channel and Github issues:
-
Discord in the #kadena-js channel