pchain-dapp-sdk-js
Version:
ParallelChain Mainnet DApp JavaScript SDK
527 lines (400 loc) • 17.3 kB
Markdown
# Usage and Examples
**Table of Contents**
- [Create PChain Instance](#create-pchain-instance)
- [Import Related Packages](#import-related-packages)
- [Reading From PChain](#reading-from-pchain)
- [getAccountBalance](#get-account-balance)
- [getAccountNonce](#get-account-nonce)
- [getBlock](#get-block)
- [getContractCode](#get-contract-code)
- [getLatestCommittedBlock](#get-latest-committed-block)
- [getTransaction](#get-transaction)
- [Writing To PChain](#writing-to-pchain)
- [submitAndConfirmTransaction](#submit-and-confirm-transaction)
- [submitTransaction](#submit-transaction)
- [Convenience Methods](#convenience-methods)
- [buildContractAddress](#build-contract-address)
- [buildTransaction](#build-transaction)
- [callContractStateChange](#call-contract-state-change)
- [callContractView](#call-contract-view)
- [transferToken](#transfer-token)
## Create PChain Instance
```ts
// note: use a named import instead of a default import
import { PChain } from "pchain-dapp-sdk-js";
// provide an RPC endpoint, in this case, PChain testnet
const pchain = new PChain("https://pchain-test-rpc02.parallelchain.io");
```
## Import Related Packages
For many routine operations, you will require `pchain-types-js` and `bn.js` in conjunction with this package.
This can be easily done as shown in the following example:
```ts
import { PublicAddress } from "pchain-types-js";
import BN from "bn.js";
// separately we prepare the toAddress (string), transferAmt(number or string), and signingKeypair
// refer to `examples/token-transfer.ts` for more details
const transactionHash = await pchain.transferToken(
// transferToken() also accepts the address string directly, other than a PublicAddress instance
// here we are using the latter for purposes of the import demonstration
new PublicAddress(toAddress),
new BN(transferAmt),
signingKeypair
);
let afterBalance = await pchain.getAccountBalance(toAddress);
console.log(
`Token transfer completed with tx hash ${transactionHash.toBase64url()}`
);
```
## Reading From PChain
### Get Account Balance
Retrieves token balance for a chosen account in grays (the smallest denomination of XPLL).
```ts
// accepts either a base64Url string
const balanceA = await pchain.getAccountBalance(
"6YokxrV0U2y6zg8FElk3Rb7j_jGolP28ZuMd6P2XXn0"
);
// or an instance of PublicAddress
import { PublicAddress } from "pchain-types-js";
const balanceB = await pchain.getAccountBalance(
new PublicAddress("6YokxrV0U2y6zg8FElk3Rb7j_jGolP28ZuMd6P2XXn0")
);
// always returns a BN
console.log(balanceA instanceof BN); // true
console.log(balanceB instanceof BN); // true
console.log(balanceA.toString()); // 518806329 (grays)
console.log(balanceB.toString()); // 518806329 (grays)
```
### Get Account Nonce
Retrieves the latest nonce for the account.
This value can be used directly, without incrementing, to submit the next transaction.
The nonce represents the number of transactions performed by the account.
For accounts with no prior transactions, the nonce will be 0.
```ts
// accepts either a base64Url string
const nonce = await pchain.getAccountNonce(
"6YokxrV0U2y6zg8FElk3Rb7j_jGolP28ZuMd6P2XXn0"
);
// or an instance of PublicAddress
import { PublicAddress } from "pchain-types-js";
const nonce = await pchain.getAccountNonce(
new PublicAddress("6YokxrV0U2y6zg8FElk3Rb7j_jGolP28ZuMd6P2XXn0")
);
console.log(nonce instanceof BN); // true
console.log(nonce.toString()); // 10, given 10 previous transactions
```
### Get Block
Retrieves a particular block using one of several possible identifiers.
When passed a block hash, this method returns `null` if no matching block is found.
When passed a block number above the current block height, this method will return an error.
```ts
// accepts a base64Url string
const block = await pchain.getBlock(
"iyJSh7SUwk-v1VoXt0YbZvCpqbxeSKWQ-o8xMkhvakU"
);
// or a Sha256Hash representing a block hash
import { Sha256Hash } from "pchain-types-js";
const block = await pchain.getBlock(
new Sha256Hash("iyJSh7SUwk-v1VoXt0YbZvCpqbxeSKWQ-o8xMkhvakU")
);
// returns null if the hash is unknown
// or a BN for blocknumber
const block = await pchain.getBlock(new BN("762049"));
// raises Error unknown block number if the number is out of range
```
By querying the transactions included across successive blocks, it is possible to construct the history of the blockchain.
Here's a more detailed example of how we could query a single block for token transfers:
```ts
// the ExitStatus enum can help format human-readable values of a command's exit status
import { ExitStatus } from "pchain-types-js";
const block = await pchain.getBlock(new BN("749315"));
if (!block) {
throw new Error("Block is not found");
}
const { blockHeader, transactions, receipts } = block;
console.log(blockHeader.hash.toBase64url());
// Prints: SLB4JfkeKtq6h9GHclW7RNHgq77kP1j23dR3-LqW1U8
// iterate through the block's transactions and respective commands and receipts
// every command has a matching receipt
for (let txIndex = 0; txIndex < transactions.length; txIndex++) {
const txCommands = transactions[txIndex].commands;
const txCommandReceipts = receipts[txIndex].command_receipts;
for (let commandIdx = 0; commandIdx < txCommands.length; commandIdx++) {
const command = txCommands[commandIdx];
// the second check for the "enum" property is optional
if (command.transfer && command.enum === "transfer") {
console.log("Found a token transfer...");
const transfer = command.transfer;
const { recipient, amount } = transfer;
console.log(recipient.toBase64url());
// Prints: mC23wtyCuuku5jK6AHnHmYPE3YHXaBh1WZbR3bOmMJQ
console.log(amount.toString());
// Prints: 2000000000
console.log("Finding matching receipt...");
const matchingReceipt = txCommandReceipts[commandIdx];
console.log(ExitStatus[matchingReceipt.exit_status]);
// Prints: Success
// optionally, the raw value is `exit_status` is the numeric enum, 0 in this case
console.log(matchingReceipt.gas_used.toString());
// Prints: 32820
}
}
}
```
### Get Contract Code
Retrieves the Wasm bytecode stored at a smart contract address.
Use this method to confirm that the desired smart contract bytecode was deployed to the blockchain.
```ts
// accepts either a base64Url string
const contractCode = await pchain.getContractCode(
"ZxjCCuD8s5TEnXlbn9F7JMYmjNFMs5cGHSj21g9Ea0Y"
);
// or an instance of PublicAddress
const contractCode = await pchain.getContractCode(
new PublicAddress("ZxjCCuD8s5TEnXlbn9F7JMYmjNFMs5cGHSj21g9Ea0Y")
);
if (contractCode === null) {
throw new Error("Expected contract code");
}
console.log(contractCode);
// Prints Uint8Array(42761) [0, 97, 115, 109, 1, ....]
```
### Get Latest Committed Block
Retrieves the latest committed block on the blockchain, at the time of query.
Inspecting this block will provide information about the latest confirmed transactions (if any) and the height of the ParallelChain Mainnet blockchain.
```ts
const block = await pchain.getLatestCommittedBlock();
const { blockHeader, transactions, receipts } = block;
// note: exact responses will differ based on query time
// the following shows a sample response for a block with no new transactions
console.log(blockHeader.hash.toBase64url());
// Prints: DJLBAupobi2CXyqzbRdZjDnU-fiC-WGTfg1XgF9YYx4
console.log(blockHeader.height.toString());
// Prints: 763256
console.log(transactions);
// Prints: []
console.log(receipts);
// Prints: []
```
### Get Transaction
Retrieves a TransactionResult object given a transaction hash.
```ts
// accepts a base64Url string
const transactionResult = await pchain.getTransaction(
"hRle-75dVeso4_dZxUQN5NFKQ2eoxm_wmHYySTGscOU"
);
// or any Sha256Hash representing a transaction hash
import { Sha256Hash } from "pchain-types-js";
const transactionResult = await pchain.getTransaction(
new Sha256Hash("hRle-75dVeso4_dZxUQN5NFKQ2eoxm_wmHYySTGscOU")
);
```
Following is a demonstration of how we can parse a TransactionResult for relevant metadata and any transfers:
```ts
const transactionResult = await pchain.getTransaction(
"hRle-75dVeso4_dZxUQN5NFKQ2eoxm_wmHYySTGscOU"
);
if (!transactionResult) {
throw new Error("Transaction not found");
}
const { block_hash, position, transaction, receipt } = transactionResult;
console.log(block_hash.toBase64url());
// Prints: SLB4JfkeKtq6h9GHclW7RNHgq77kP1j23dR3-LqW1U8
// position describes the index of the transaction within the block
console.log(position);
// Prints: 0
console.log(transaction.signer.toBase64url());
// Prints: pveHxHcfPNRH-ljtu6kN7d6T5cTugjg8QauT0A8OkAU
const txCommands = transaction.commands;
const txCommandReceipts = receipt.command_receipts;
// iterate through transaction to detect any transfers
for (let commandIdx = 0; commandIdx < txCommands.length; commandIdx++) {
const command = txCommands[commandIdx];
// the second check for the "enum" property is optional
if (command.transfer && command.enum === "transfer") {
const transfer = command.transfer;
const { recipient, amount } = transfer;
console.log(recipient.toBase64url());
// Prints: mC23wtyCuuku5jK6AHnHmYPE3YHXaBh1WZbR3bOmMJQ
console.log(amount.toString());
// Prints: 2000000000
console.log("Finding matching receipt...");
const matchingReceipt = txCommandReceipts[commandIdx];
console.log(ExitStatus[matchingReceipt.exit_status]);
// Prints: Success
// optionally, the raw value is `exit_status` is the numeric enum, 0 in this case
console.log(matchingReceipt.gas_used.toString());
// Prints: 32820
}
}
```
## Writing To PChain
### Submit And Confirm Transaction
Submits a signed transaction to the blockchain and wait for its confirmation.
Upon confirmation, the method will return the Receipt for the transaction. The Receipt contains an array of CommandReceipts, which individually indicate the ExitStatus and other data related to the executed commands.
```ts
import { ExitStatus, PublicAddress, Transfer } from "pchain-types-js";
// separately prepare the signingKeypair, nonce and toAddress
// use the buildTransaction helper method to create a signed transaction
const signedTx = await pchain
.buildTransaction(signingKeypair, nonce)
.addCommand(
new Transfer({
amount: new BN("99"),
recipient: new PublicAddress(toAddress),
})
)
.build();
const { command_receipts } = await pchain.submitAndConfirmTransaction(signedTx);
// for each command, we expect a separate receipt, in this case there is only 1
// compare against the Success value of the ExitStatus enum from 'pchain-types-js'
const { exit_status, return_values, logs } = command_receipts[0];
if (exit_status !== ExitStatus.Success) {
throw new Error("Transaction execution failed");
}
console.log("Transfer successful");
```
### Submit Transaction
_N.B. In most cases, you probably want to use another method, e.g. submitAndConfirmTransaction() which waits for transaction confirmation, or convenience methods such as transferToken()._
Submits a signed transaction to the blockchain, without waiting for confirmation.
If accepted with no error, the method returns the transaction hash. The transaction is considered "pending" until included in a confirmed block. Note that developers will need to check for confirmation separately.
Otherwise, the method will throw an error indicating the reason, such as an incorrectly formed transaction payload.
```ts
import { PublicAddress, Transfer } from "pchain-types-js";
// separately prepare the signingKeypair nonce and toAddress
// use the buildTransaction helper method to create a signed transaction
const signedTx = await pchain
.buildTransaction(signingKeypair, nonce)
.addCommand(
new Transfer({
amount: new BN("99"),
recipient: new PublicAddress(toAddress),
})
)
.build();
const txHash = await pchain.submitTransaction(signedTx);
console.log("Transaction Submitted, transaction hash is", txHash.toBase64url());
// at this point, the transaction is still not confirmed
// query for confirmation separately
```
## Convenience Methods
### Build Contract Address
Compute the address of a deployed smart contract, given the deployer's public key and nonce of the deployment transaction.
```ts
const contractAddress = pchain.buildContractAddress(
new PublicAddress("oK8Kvd-2cWYloQaPNlGtG3Q5dV6JFKzVrXOAhBRt5hs"),
new BN("47987")
);
console.log(contractAddress.toBase64url());
// Prints: lu-2SF7uOB5EBNLGFkLWHzieJ4BNqxQJ48sQiRpzj90
```
### Build Transaction
_N.B. The method is asynchronous due to the nature of the signature verification, please call using `await`._
Helper method to build and sign a transaction, which can be passed to `submitAndConfirmTransaction()`.
The method requires at least one of the following commands which can be imported from `pchain-types-js`.
- Transfer
- Deploy
- Call
- CreateDeposit
- SetDepositSettings
- TopUpDeposit
- WithdrawDeposit
- StakeDeposit
- UnstakeDeposit
Gas-related limits are defaulted but can be overridden.
The following example uses the Deploy command to deploy a smart contract.
```ts
// build a Transaction to deploy a smart contract
// prepare the bytes of the .wasm contract (wasmBytes parameter) separately
import { Deploy } from "pchain-types-js";
const deployTx = await pchain
.buildTransaction(signingKeypair, deployTxNonce)
.addCommand(
new Deploy({
// the bytes of the .wasm file can be passed directly to the contract param
contract: wasmBytes,
// Contract Binary Interface (CBI) is a standard specifying a valid contract, it is set to 0 in the current protocol version
cbi_version: 0,
})
)
// the Deploy command is relatively expensive, set a high gas limit to be be safe
.setGasLimit(new BN(250_000_000))
.build();
const { command_receipts: deployCommandReceipts } =
await pchain.submitAndConfirmTransaction(deployTx);
console.log(deployCommandReceipts[0].exit_status);
// Prints: "0", indicating success
```
### Call Contract State Change
Convenience method for calling a smart contract method that changes the blockchain state.
Under the hood, this method creates a transaction with the Call command. It is required to provide a signing keypair and specify the gas limit for the call.
```ts
// pre-deployed instance of the HelloContract on Testnet
// contract code: https://github.com/parallelchain-io/example-smart-contracts/blob/main/chapter_1/src/lib.rs
const contractAddress = "ZxjCCuD8s5TEnXlbn9F7JMYmjNFMs5cGHSj21g9Ea0Y";
// pass arguments based on the smart contract method signature
// refer to the smart contract code
const {
exit_status: stateChangeExitStatus,
return_values: stateChangeReturnValues,
logs: stateChangeReturnLogs,
} = await pchain.callContractStateChange(
contractAddress,
"hello_set_many",
null,
null,
signingKeypair,
// note this an expensive contract method call as a demonstration, pass in a high gas limit
new BN(250_000_000)
);
console.log(
"Invoking method as a State Change method, Exit status:",
stateChangeExitStatus
);
// Prints "Exit status: 0", i.e. Success
// the method does not return any value
// nor does it emit any logs, hence we expect these fields to be empty
console.log("Return values:", stateChangeReturnValues);
// Prints "Return values: Uint8Array(0) []"
console.log("Logs:", stateChangeReturnLogs);
// Prints "Logs: []"
```
### Call Contract View
Convenience method for calling a smart contract method through the view procedure.
The method in question must not write (store) any data to the blockchain and is called in a read-only way.
```ts
import { BinaryReader } from "pchain-types-js";
// pre-deployed instance of the HelloContract on Testnet
// contract code: https://github.com/parallelchain-io/example-smart-contracts/blob/main/chapter_1/src/lib.rs
const contractAddress = "ZxjCCuD8s5TEnXlbn9F7JMYmjNFMs5cGHSj21g9Ea0Y";
// pass arguments based on the smart contract method signature
// refer to the smart contract code
const { exit_status, return_values } = await pchain.callContractView(
contractAddress,
"i_say_hello",
null
);
console.log(exit_status);
// Prints 0, i.e. Success
// use the BinaryReader class (from pchain-types-js) to decode the return value of type String
console.log(new BinaryReader(Buffer.from(return_values)).readString());
// Prints "you say world!"
```
### Transfer Token
Convenience method for transferring tokens to a designated address.
The method returns the transaction hash if the transfer succeeded, throwing an error otherwise. Note that you will need to pass a signing key pair.
Behind the scenes, a default gas fee has been set that is sufficient to pay for the basic transfer.
```ts
// accepted address formats include a string, or a PublicAddress instance
// here we use a string for convenience
const transactionHash = await pchain.transferToken(
toAddress,
new BN("99"),
signingKeypair
);
console.log(
`Token transfer completed with tx hash ${transactionHash.toBase64url()}`
);
// use the getAccountBalance method to validate the updated balance
const afterBalance = await pchain.getAccountBalance(toAddress);
console.log(`After balance is ${afterBalance.toString()}`);
```