Skip to content

Commit

Permalink
docs(client-examples): add transfer to readme
Browse files Browse the repository at this point in the history
  • Loading branch information
javadkh2 committed Aug 18, 2023
1 parent b5ba340 commit af6f07f
Showing 1 changed file with 149 additions and 11 deletions.
160 changes: 149 additions & 11 deletions packages/libs/client-examples/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,155 @@
<!-- genericHeader start -->
## Preparation

# @kadena/client-examples
In the following examples, we will interact with the `coin` contract. To have
better type support, we strongly recommend generating the type definition from
the contract using `@kadena/pactjs-cli`.

Test project to verify pactjs-cli and pactjs-generator
To install `@kadena/pactjs-cli` as a dev dependency for your project, run the
following command in the terminal:

<picture>
<source srcset="https://raw.githubusercontent.com/kadena-community/kadena.js/main/common/images/Kadena.JS_logo-white.png" media="(prefers-color-scheme: dark)"/>
<img src="https://raw.githubusercontent.com/kadena-community/kadena.js/main/common/images/Kadena.JS_logo-black.png" width="200" alt="kadena.js logo" />
</picture>
```shell
npm install @kadena/pactjs-cli --save-dev
```

<!-- genericHeader end -->
You can generate type definitions from either a local file or directly from the
chain.

## Client examples
### Creating a type definition from a contract deployed on the chain

This project demonstrates the use of the `@kadena/pact-cli` together with
`@kadena/client` for _smart contracts_.
```shell
npx pactjs contract-generate --contract="coin" --api http://104.248.41.186:8080/chainweb/0.0/development/chain/0/pact
```

### Creating a type definition from a pact file

```shell
npx pactjs contract-generate --file=./coin.pact --api http://104.248.41.186:8080/chainweb/0.0/development/chain/0/pact
```

Note: Passing the API is optional here, but it's better to always do that so the
script can generate types for all of the dependencies as well.

Note: You can use `--file` and `--contract` several times, and even together.

## Transfer KDA

In this example, we will use `@kadena/client` to transfer `1` KDA from `bob` to
`alice`.

This example demonstrates how to use `Pact.builder`, `Pact.modules`,
`signWithChainweaver`, and `getClient` utilities.

```ts
import {
getClient,
isSignedCommand,
Pact,
signWithChainweaver,
} from '@kadena/client';

interface IAccount {
// In KDA, the account name is not the same as the public key. The reason is that the account could be multi-signature, and you can choose a user-friendly name for yourself.
accountName: string;
// We need this for signing the transaction.
publicKey: string;
}

const { submit, listen } = getClient();

async function transfer(
sender: IAccount,
receiver: IAccount,
amount: string,
): Promise<void> {
// The first step for interacting with the blockchain is creating a request object.
// We call this object a transaction object. `Pact.builder` will help you to create this object easily.
const transaction = Pact.builder
// In Pact, we have two types of commands: execution and continuation. Most of the typical use cases only use execution.
.execution(
// This function uses the type definition we generated in the previous step and returns the pact code as a string.
// This means you want to call the transfer function of the coin module (contract).
// You can open the coin.d.ts file and see all of the available functions.
Pact.modules.coin.transfer(
sender.accountName,
receiver.accountName,
// As we know JS rounds float numbers, which is not a desirable behavior when you are working with money. So instead, we send the amount as a string in this format.
{
decimal: amount,
},
),
)
// The sender needs to sign the command; otherwise, the blockchain node will refuse to do the transaction.
.addSigner(sender.publicKey, (withCapability) => [
// The sender also mentions they want to pay the transaction fee by adding the "coin.GAS" capability.
withCapability('coin.GAS'),
// The sender scopes their signature only to "coin.TRANSFER" with the specific arguments.
withCapability(
'coin.TRANSFER',
sender.accountName,
receiver.accountName,
{
decimal: amount,
},
),
])
// Since Kadena has a multi-chain architecture, we need to set the chainId.
// We also need to mention who is going to pay the gas fee.
.setMeta({ chainId: '0', sender: sender.accountName })
// We set the networkId to "testnet04"; this could also be "mainnet01" or something else if you use a private network or a fork.
.setNetworkId('testnet04')
// Finalize the command and add default values and hash to it. After this step, no one can change the command.
.createTransaction();

// The transaction now has three properties:
// - cmd: stringified version of the command
// - hash: the hash of the cmd field
// - sigs: an array that has the same length as signers in the command but all filled by undefined

// Now you need to sign the transaction; you can do it in a way that suits you.
// We exported some helpers like `signWithChainweaver` and signWithWalletConnect, but you can also use other wallets.
// For example, if you are using EckoWallet, it has a specific API for signing transactions.
// By the end, the signer function should fill the sigs array and return the signed transaction.
const signedTr = await signWithChainweaver(transaction);

// As the signer function could be an external function, we double-check if the transaction is signed correctly.
if (isSignedCommand(signedTr)) {
// So it's time to submit the transaction; this function returns the requestKey.
const requestKey = await submit(signedTr);
// We listen for the result of the request.
const response = await listen(requestKey);
// Now we need to check the status.
if (response.result.status === 'failure') {
throw response.result.error;
} else {
// Congratulations! You have successfully submitted a transfer transaction.
console.log(response.result);
}
}
}

// Calling the function with proper input
transfer(
{ accountName: 'bob', publicKey: 'bob_public_key' },
{ accountName: 'alice', publicKey: 'alice_public_key' },
'1',
).catch(console.error);
```

### More in-depth

_What is a capability?_

A capability is the security model of Pact - the Kadena smart contract language.
It is used widely in Pact contracts for security reasons, but from the user's
perspective, it allows users to scope their signatures. For example, you can say
"I signed this contract only for paying gas," or "I want a transfer to happen
but only to a specific account and with a specific maximum amount."

_Why do we add `coin.TRANSFER` via `withCapability` once we already added a
similar thing via `Pact.modules.coin.transfer`?_

Pact is a modular language, which means other contracts can import the coin
contract and call the transfer function. To prevent unauthorized transfers, the
coin contract needs to guard against this and ensure that the user is aware of
the transfer happening. `withCapability` is the place for that. So you tell Pact
that you expect a transfer to happen during the execution of this transaction.

0 comments on commit af6f07f

Please sign in to comment.