Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional Creditcoin js examples #1346

Draft
wants to merge 19 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 227 additions & 1 deletion creditcoin-js/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,232 @@
# creditcoin-js

**WARNING**: This is an alpha version of creditcoin-js. It is not ready for production use. It will have breaking changes often.
## Getting started

### Preqrequisites

Creditcoin-js requires the following to be installed:

- [Node.js](https://nodejs.org/en/)
- [TypeScript](https://www.typescriptlang.org/)

### Install

Adding Creditcoin-JS to your project is easy. Install it by using your favorite package manager:

```shell
yarn add creditcoin-js
```

This will install the latest release version, which should allow you to interact with Creditcoin's main network and your own local chains that use the latest Creditcoin binaries.

## Usage

### Import

Importing the library into your project:

```typescript
import { creditcoinApi } from 'creditcoin-js';

const { api } = await CreditcoinApi('ws://localhost:9944');

// don't forget to disconnect when you are done
await api.disconnect();
```

### Using the API

The API is a collection of modules that provide access to the various functions of the Creditcoin blockchain.

```typescript
const { api, extrinsics, utils } = await CreditcoinApi('ws://localhost:9944');
```

### Creating transactions

```typescript
const { api } = await CreditcoinApi('ws://localhost:9944');

const tx = api.
.tx
.balances
.transfer(
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"1000000000000000" // CTC amount in microunits
// (1 CTC = 1e18 microunits)
)
```

### Signing & sending

```typescript
import { Keyring } from 'creditcoin-js';

const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');

await tx.signAndSend(alice);
```

### Batching transactions

```typescript
const tx1 = api.tx.balances.transfer(addrBob, 10);
const tx2 = api.tx.balances.transfer(addrCharlie, 10);
const txs = [tx1, tx2];

const batch_tx = api.tx.utility.batch(txs);

await batch_tx.signAndSend(alice);
```

### Registering External Addresses
```typescript
import { CreditcoinApi } from '../types';
import { Wallet } from 'ethers';
import { Blockchain } from '../model';
import { personalSignAccountId } from '../utils';
import { KeyringPair } from '@polkadot/keyring/types';
import { personalSignSignature } from '../extrinsics/register-address-v2';

export async function registerAddressV2Example(
ccApi: CreditcoinApi,
ethSigner: Wallet,
creditcoinAddress: KeyringPair,
blockchain: Blockchain,
) {
const {
api,
extrinsics: { registerAddressV2 },
} = ccApi;

const accountId = creditcoinAddress.addressRaw;
const externalAddress = ethSigner.address;

const signature = await personalSignAccountId(api, ethSigner, accountId);
const proof = personalSignSignature(signature);

return registerAddressV2(externalAddress, blockchain, proof, creditcoinAddress);
}
```

### Swap GCRE -> CTC
```typescript
import { GCREContract } from 'creditcoin-js/lib/extrinsics/request-collect-coins-v2';

// Create a wrapper that holds the details for the burned tokens
// externalAddress is the address of the burner and must be previously registered
// The GCREContract refers to ethereum mainnet CTC, we could also use a GATEContract here for Gluwa Gate Token
const burnDetails = GCREContract(externalAddress, burnTxHash);

export async function CollectCoinsV2Example(
ccApi: CreditcoinApi,
burnDetails: CollectCoinsContract,
creditcoinSigner: KeyringPair,
) {
const {
extrinsics: { requestCollectCoinsV2 },
} = ccApi;

// Submit the swap request, adding it to the task queue of the off chain worker
const collectCoins = await requestCollectCoinsV2(burnDetails, creditcoinSigner);

// Wait for the offchain worker to finish processing this request
// Under the hood waitForVerification tracks CollectedCoinsMinted and CollectedCoinsFailedVerification events using the TaskId as a unique key
// 900_000 (milliseconds) comes from an assumed 60 block task timeout deadline and assumed 15 second blocktime (check the constants provided by the runtime in production code)
return await collectCoins.waitForVerification(900_000);
}

const result = await CollectCoinsV2Example(ccApi, burnDetails, creditcoinAddress);
```

### Reading Runtime Constants
```typescript
import { U64, U32 } from "@polkadot/types-codec";

const { api } = ccApi;

const expectedBlockTime = (api.consts.babe.expectedBlockTime as U64).toNumber()
const unverifiedTaskTimeout = (api.consts.creditcoin.unverifiedTaskTimeout as u32).toNumber();

const taskTimeout = expectedBlockTime * unverifiedTaskTimeout;
```


### Setting Offchain Local Storage (Ethereum RPC URI)
```typescript
function u8aToHex = (bytes: Uint8Array): string {
return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '0x');
};

const rpcUri = u8aToHex(api.createType('String', 'http://localhost:8545').toU8a());
await api.rpc.offchain.localStorageSet('PERSISTENT', 'ethereum-rpc-uri', rpcUri);
```

### Submitting Sudo Calls (Set CollectCoins Contract)
```typescript
// The blockchain the contract lives one
const blockchain = "Ethereum";

// Address for Ethereum Gluwa Creditcoin Vesting Token
const contractAddress = "0xa3EE21C306A700E682AbCdfe9BaA6A08F3820419";

import { KeyringPair } from '@polkadot/keyring/types';
import { Blockchain } from 'src/model';
import { CreditcoinApi } from 'src/types';

export async function setCollectCoinsContractExample(
ccApi: CreditcoinApi,
contractAddress: string,
blockchain: Blockchain,
sudoSigner: KeyringPair,
) {
const { api } = ccApi;

const contract = api.createType('PalletCreditcoinOcwTasksCollectCoinsDeployedContract', {
address: contractAddress,
chain: blockchain,
});

await api.tx.sudo.sudo(api.tx.creditcoin.setCollectCoinsContract(contract)).signAndSend(sudoSigner, { nonce: -1 });
}

await setCollectCoinsContractExample(ccApi, contractAddress, blockchain, sudoSigner);
```

## Development

### Build

To build the project, run the following command from the root directory:

```shell
yarn build
```

### Updating Type definitions

Creditcoin-JS uses actual chain metadata to generate the API types and augmented endpoints. When the Creditcoin blockchain gets updated and includes new extrinsics or storage fields in it’s pallets, Creditcoin-JS must regenerate its types to include the newly available methods.

1. Fetch Chain Metadata

This process begins with pulling the current metadata from a running creditcoin-node by making an RPC call:

```shell
curl -H "Content-Type: application/json" -d '{"id":"1", "jsonrpc":"2.0", "method": "state_getMetadata", "params":[]}' http://localhost:9933
```

2. Add Metadata to creditcoin.json

The full JSON output must be saved into a creditcoin.json file as specified by the generation scripts included in package.json.

3. Generate Types

The types can be generated by running the following command:

```shell
yarn build:types
```

## Errors & Troubleshooting

Expand Down
21 changes: 21 additions & 0 deletions creditcoin-js/src/examples/collect-coins-v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CreditcoinApi } from '../types';
import { CollectCoinsContract } from '../model';
import { KeyringPair } from '@polkadot/keyring/types';

export async function collectCoinsV2Example(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for all of these examples, it would be best if they were written expecting no arguments. The examples original provided by Pablo all follow the same signature of an async function with no parameters, and I think we should maintain that where we can.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be possible for the register-address and set-collect-coins-contract examples because they are light on dependencies. The collect coins example needs the fully deployed token setup and also calls to setGateContract and setGateFaucet. I think all that extra code might take away from the point of the example.

ccApi: CreditcoinApi,
burnDetails: CollectCoinsContract,
creditcoinSigner: KeyringPair,
) {
const {
extrinsics: { requestCollectCoinsV2 },
} = ccApi;

// Submit the swap request, adding it to the task queue of the off chain worker
const collectCoins = await requestCollectCoinsV2(burnDetails, creditcoinSigner);

// Wait for the offchain worker to finish processing this request
// Under the hood waitForVerification tracks CollectedCoinsMinted and CollectedCoinsFailedVerification events using the TaskId as a unique key
// 900_000 (milliseconds) comes from an assumed 60 block task timeout deadline and assumed 15 second blocktime (check the constants provided by the runtime in production code)
return await collectCoins.waitForVerification(900_000);
}
26 changes: 26 additions & 0 deletions creditcoin-js/src/examples/register-address-v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CreditcoinApi } from '../types';
import { Wallet } from 'ethers';
import { Blockchain } from '../model';
import { personalSignAccountId } from '../utils';
import { KeyringPair } from '@polkadot/keyring/types';
import { personalSignSignature } from '../extrinsics/register-address-v2';

export async function registerAddressV2Example(
ccApi: CreditcoinApi,
ethSigner: Wallet,
creditcoinAddress: KeyringPair,
blockchain: Blockchain,
) {
const {
api,
extrinsics: { registerAddressV2 },
} = ccApi;

const accountId = creditcoinAddress.addressRaw;
const externalAddress = ethSigner.address;

const signature = await personalSignAccountId(api, ethSigner, accountId);
const proof = personalSignSignature(signature);

return registerAddressV2(externalAddress, blockchain, proof, creditcoinAddress);
}
19 changes: 19 additions & 0 deletions creditcoin-js/src/examples/set-collect-coins-contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { KeyringPair } from '@polkadot/keyring/types';
import { Blockchain } from 'src/model';
import { CreditcoinApi } from 'src/types';

export async function setCollectCoinsContractExample(
ccApi: CreditcoinApi,
contractAddress: string,
blockchain: Blockchain,
sudoSigner: KeyringPair,
) {
const { api } = ccApi;

const contract = api.createType('PalletCreditcoinOcwTasksCollectCoinsDeployedContract', {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get some comments here about what the createType call is doing and how the string argument was determined? It's a bit ambiguous for someone trying to learn to use the library.

address: contractAddress,
chain: blockchain,
});

await api.tx.sudo.sudo(api.tx.creditcoin.setCollectCoinsContract(contract)).signAndSend(sudoSigner, { nonce: -1 });
}
Loading
Loading