laconic-sdk/src/index.ts

240 lines
6.8 KiB
TypeScript
Raw Normal View History

import isUrl from 'is-url';
import { sha256 } from 'js-sha256';
import { generatePostBodyBroadcast } from '@tharsis/provider';
2022-03-30 05:21:03 +00:00
import {
Chain,
2022-03-31 10:00:02 +00:00
Sender,
Fee,
2022-03-30 05:21:03 +00:00
} from '@tharsis/transactions'
import { createTxMsgCancelBond, createTxMsgCreateBond, createTxMsgRefillBond, createTxMsgWithdrawBond, MessageMsgCancelBond, MessageMsgCreateBond, MessageMsgRefillBond, MessageMsgWithdrawBond } from "./bond";
import { RegistryClient } from "./registry-client";
import { Account } from "./account";
import { createTransaction } from "./txbuilder";
2022-03-31 10:00:02 +00:00
const DEFAULT_WRITE_ERROR = 'Unable to write to chiba-clonk.';
export const DEFAULT_CHAIN_ID = 'ethermint_9000-1';
// Parse Tx response from cosmos-sdk.
export const parseTxResponse = (result: any) => {
const { txhash: hash, height, ...txResponse } = result;
txResponse.data = txResponse.data && Buffer.from(txResponse.data, 'base64').toString('utf8');
txResponse.events.forEach((event:any) => {
event.attributes = event.attributes.map(({ key, value }: { key: string, value: string }) => ({
key: Buffer.from(key, 'base64').toString('utf8'),
value: Buffer.from(value, 'base64').toString('utf8')
}));
});
return { hash, height, ...txResponse };
};
export const isKeyValid = (key: string) => key && key.match(/^[0-9a-fA-F]{64}$/);
export class Registry {
_endpoint: string
_chain: Chain
_client: RegistryClient
static processWriteError(error: Error) {
/**
Example:
{
message: '{"code":18,"data":null,"log":"invalid request: Name already reserved.: failed to execute message; message index: 0","info":"","gasWanted":"200000","gasUsed":"86717","events":[],"codespace":"sdk"}',
path: [ 'submit' ]
}g
*/
const message = JSON.parse(error.message);
return message.log || DEFAULT_WRITE_ERROR;
}
2022-04-04 07:05:16 +00:00
constructor(restUrl: string, gqlUrl: string, cosmosChainId = DEFAULT_CHAIN_ID) {
if (!isUrl(restUrl)) {
throw new Error('Path to a REST endpoint should be provided.');
}
2022-03-30 05:21:03 +00:00
2022-04-04 07:05:16 +00:00
if (!isUrl(gqlUrl)) {
throw new Error('Path to a GQL endpoint should be provided.');
}
this._endpoint = restUrl;
this._client = new RegistryClient(restUrl, gqlUrl);
2022-03-30 05:21:03 +00:00
this._chain = {
chainId: 9000,
cosmosChainId
}
2022-03-30 05:21:03 +00:00
}
/**
* Get account by addresses.
*/
async getAccount(address: string) {
return this._client.getAccount(address);
2022-03-30 05:21:03 +00:00
}
/**
* Computes the next bondId for the given account private key.
*/
async getNextBondId(address: string) {
let result;
2022-03-31 10:00:02 +00:00
try {
const { account } = await this.getAccount(address);
const accountObj = account.base_account;
2022-03-31 10:00:02 +00:00
const nextSeq = parseInt(accountObj.sequence, 10) + 1;
2022-04-04 07:05:16 +00:00
result = sha256(`${accountObj.address}:${accountObj.account_number}:${nextSeq}`);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
2022-03-31 10:00:02 +00:00
return result;
2022-03-31 10:00:02 +00:00
}
2022-04-04 07:05:16 +00:00
/**
* Get bonds by ids.
*/
async getBondsByIds(ids: string[]) {
return this._client.getBondsByIds(ids);
}
/**
* Query bonds by attributes.
*/
async queryBonds(attributes = {}) {
return this._client.queryBonds(attributes);
}
/**
* Create bond.
*/
async createBond(params: MessageMsgCreateBond, senderAddress: string, privateKey: string, fee: Fee) {
let result;
2022-03-31 10:00:02 +00:00
try {
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
2022-03-31 10:00:02 +00:00
const sender = {
accountAddress: accountInfo.address,
sequence: accountInfo.sequence,
accountNumber: accountInfo.account_number,
pubkey: accountInfo.pub_key.key,
}
2022-03-31 10:00:02 +00:00
const msg = createTxMsgCreateBond(this._chain, sender, fee, '', params)
result = await this._submitTx(msg, privateKey, sender);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
2022-03-31 10:00:02 +00:00
return parseTxResponse(result);
2022-03-31 10:00:02 +00:00
}
/**
* Refill bond.
*/
async refillBond(params: MessageMsgRefillBond, senderAddress: string, privateKey: string, fee: Fee) {
let result;
try {
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
const sender = {
accountAddress: accountInfo.address,
sequence: accountInfo.sequence,
accountNumber: accountInfo.account_number,
pubkey: accountInfo.pub_key.key,
}
const msg = createTxMsgRefillBond(this._chain, sender, fee, '', params)
result = await this._submitTx(msg, privateKey, sender);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
return parseTxResponse(result);
}
/**
* Withdraw (from) bond.
*/
async withdrawBond(params: MessageMsgWithdrawBond, senderAddress: string, privateKey: string, fee: Fee) {
let result;
try {
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
const sender = {
accountAddress: accountInfo.address,
sequence: accountInfo.sequence,
accountNumber: accountInfo.account_number,
pubkey: accountInfo.pub_key.key,
}
const msg = createTxMsgWithdrawBond(this._chain, sender, fee, '', params)
result = await this._submitTx(msg, privateKey, sender);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
return parseTxResponse(result);
}
/**
* Cancel bond.
*/
async cancelBond(params: MessageMsgCancelBond, senderAddress: string, privateKey: string, fee: Fee) {
let result;
try {
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
const sender = {
accountAddress: accountInfo.address,
sequence: accountInfo.sequence,
accountNumber: accountInfo.account_number,
pubkey: accountInfo.pub_key.key,
}
const msg = createTxMsgCancelBond(this._chain, sender, fee, '', params)
result = await this._submitTx(msg, privateKey, sender);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
return parseTxResponse(result);
}
/**
* Submit a generic Tx to the chain.
*/
async _submitTx(message: any, privateKey: string, sender: Sender) {
// Check private key.
if (!isKeyValid(privateKey)) {
throw new Error('Registry privateKey should be a hex string.');
}
2022-04-01 03:31:00 +00:00
// Check that the account exists on-chain.
const account = new Account(Buffer.from(privateKey, 'hex'));
2022-04-01 03:31:00 +00:00
// Generate signed Tx.
const transaction = createTransaction(message, account, sender, this._chain);
2022-04-01 03:31:00 +00:00
const tx = generatePostBodyBroadcast(transaction)
2022-04-01 03:31:00 +00:00
// Submit Tx to chain.
const { tx_response: response } = await this._client.submit(tx);
return response;
2022-04-01 03:31:00 +00:00
}
}