Skip to content

Commit

Permalink
Merge branch 'tx-revamp-dev' of https://github.com/crypto-org-chain/c…
Browse files Browse the repository at this point in the history
…hain-jslib into crypto-org-chain#284/ibc-MsgCreateClient
  • Loading branch information
cdc-Hitesh committed Jul 8, 2021
2 parents d19c60d + 71c5888 commit b2f852c
Show file tree
Hide file tree
Showing 81 changed files with 4,919 additions and 753 deletions.
93 changes: 92 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,97 @@ const queryResult = await client.query().<module>.<operation>
// example client.query().bank.allBalances(<address>)
```

### 1.6. Transaction Decoding/Encoding support
Our SDK supports transaction decoding from hex-encoded strings.

```typescript
import { TxDecoder } from './txDecoder';
const txDecoder = new TxDecoder();
const decodedTx = txDecoder.fromHex('0a9b010a8c010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126c0a2b7463726f31667a63727a61336a3466323637376a667578756c6b6733337a36383532717371733868783530122b7463726f31667a63727a61336a3466323637376a667578756c6b6733337a363835327173717338687835301a100a08626173657463726f120431303030120a616d696e6f2074657374126b0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a210223c9395d41013e6470c8d27da8b75850554faada3fe3e812660cbdf4534a85d712040a020801180112170a110a08626173657463726f1205313030303010a08d061a4031f4c489b98decb367972790747139c7706f54aafd9e5a3a5ada4f72c7b017646f1eb5cb1bdf518603d5d8991466a13c3f68844dcd9b168b5d4ca0cb5ea514bc');

//Prints decoded in Cosmos compatible JSON format
console.log(decodedTx.toCosmosJSON())

// Prints
// "{"tx":{"body":{"messages":[{"@type":"/cosmos.bank.v1beta1.MsgSend","amount":[{"denom":"basetcro","amount":"1000"}],"from_address":"tcro1fzcrza3j4f2677jfuxulkg33z6852qsqs8hx50","to_address":"tcro1fzcrza3j4f2677jfuxulkg33z6852qsqs8hx50"}],"memo":"amino test","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AiPJOV1BAT5kcMjSfai3WFBVT6raP+PoEmYMvfRTSoXX"},"mode_info":{"single":{"mode":"SIGN_MODE_DIRECT"}},"sequence":"1"}],"fee":{"amount":[{"denom":"basetcro","amount":"10000"}],"gas_limit":"100000","payer":"","granter":""}},"signatures":["MfTEibmN7LNnlyeQdHE5x3BvVKr9nlo6WtpPcsewF2RvHrXLG99RhgPV2JkUZqE8P2iETc2bFotdTKDLXqUUvA=="]}}"

```

### 1.7. Offline Signing
Our SDK supports offline signing for secure external transaction management.

#### Flow:
Machine 1(Online):
1. Build a `RawTransaction` instance.
2. Export Cosmos compatible JSON by using `.toCosmosJSON()`.
3. Export Signer(s) list using `.exportSignerAccounts()`.

Machine 2 (Offline/Online):
1. Create a `SignableTransaction` instance from a stringified cosmos compatible JSON string.
2. You can import Signer(s) list using two methods:
1. call `importSignerAccounts()` on the instance above **OR**
2. (Advance usage) call `setSignerAccountNumberAtIndex()` to manually set AccountNumber at a specified index.
3. You can choose to export the signed hex encoded transaction and broadcast it manually

Eg:
```typescript
// import respective classes
// ....

/* Machine 1: */
const rawTx = new cro.RawTransaction();
// .... Do rest operations here
const exportUnsignedCosmosJSON = rawTx.toCosmosJSON();
const exportSignerInfoToJSON = rawTx.exportSignerAccounts();

/* Machine 2: */
const signerAccountsOptional: SignerAccount[] = [{
publicKey: <Bytes>;
accountNumber: new Big(0);
signMode: SIGN_MODE.DIRECT;
}];

const signableTx = new SignableTransaction({
rawTxJSON: exportUnsignedCosmosJSON,
network: <CroNetwork>,
signerAccounts: signerAccountsOptional,
});

/* `Import SignerAccounts` starts */

// METHOD 1: using importSignerAccounts()
signableTx.importSignerAccounts([
// SignerAccount 1
{
publicKey: Bytes.fromHexString('hexString');
accountNumber: new Big(0);
signMode: SIGN_MODE.DIRECT;
},
// SignerAccount 2
{
publicKey: Bytes.fromUint8Array(<Uint8>);
accountNumber: new Big(2);
signMode: SIGN_MODE.DIRECT;
}
]);

// METHOD 2 (For Advance Users): using setSignerAccountNumberAtIndex()
const signerInfoListINDEX: number = 1;
const newAccountNumber: Big = new Big(1);
signableTx.setSignerAccountNumberAtIndex(signerInfoListINDEX, newAccountNumber);

/* `Import SignerAccounts` ends */

// .... Do rest operations here on SignableTransaction

const signedTx = signableTx.toSigned();

console.log(signedTx.getHexEncoded());
// 0aa4010a8c010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126c0a2b7463726f313635747a63726832796c3833673871657178756567326735677a6775353779336665336b6333122b7463726f313635747a63726832796c3833673871657178756567326735677a6775353779336665336b63331a100a08626173657463726f120431323130120f48656c6c6f2054657374204d656d6f1896ef14126a0a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2103c3d281a28592adce81bee3094f00eae26932cbc682fba239b90f47dac9fe703612040a020801180d12160a100a08626173657463726f12043635303010c08b111a40fe9b30f29bb9a83df3685f5bf8b7e6c34bae9ee8ba93115af4136289354c5bf947698ef3a3c0a1f6092ba7a2069616c436f4bcf6f3ecef11b92ad4d319ec0347

// Note that the result of signedTx.getHexEncoded() can be directly broadcasted to the network as a raw tx

```

## 2. Cosmos Protobuf Definitions

Expand Down Expand Up @@ -191,4 +282,4 @@ npm run docs:build
The resulting generated documentation will be created in the `docs/dist` directory

## 4. License
[Apache 2.0](./LICENSE)
[Apache 2.0](./LICENSE)
120 changes: 120 additions & 0 deletions lib/e2e/transaction.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable */
import 'mocha';
import Big from 'big.js';
import { expect } from 'chai';
Expand Down Expand Up @@ -57,6 +58,73 @@ const env = {
};

describe('e2e test suite', function () {
describe('`v2` message types', function () {
it('[BANK] creates a MsgSend Type Transaction and Broadcasts it.', async function () {
const hdKey = HDKey.fromMnemonic(env.mnemonic.communityAccount);
const hdKey2 = HDKey.fromMnemonic(env.mnemonic.reserveAccount);
const hdKey3 = HDKey.fromMnemonic(env.mnemonic.randomEmptyAccount);
const privKey = hdKey.derivePrivKey(`m/44'/${customNetwork.bip44Path.coinType}'/0'/0/0`);
const privKey2 = hdKey2.derivePrivKey(`m/44'/${customNetwork.bip44Path.coinType}'/0'/0/0`);
const randomPrivKey = hdKey3.derivePrivKey(`m/44'/${customNetwork.bip44Path.coinType}'/0'/0/0`);
const keyPair = Secp256k1KeyPair.fromPrivKey(privKey);
const keyPair2 = Secp256k1KeyPair.fromPrivKey(privKey2);
const randomKeyPair = Secp256k1KeyPair.fromPrivKey(randomPrivKey);

const cro = CroSDK({ network: customNetwork });
const rawTx = new cro.RawTransaction();
const address1 = new cro.Address(keyPair.getPubKey());
const address2 = new cro.Address(keyPair2.getPubKey());
const randomAddress = new cro.Address(randomKeyPair.getPubKey());
const client = await cro.CroClient.connect();

const msgSend1 = new cro.v2.bank.MsgSendV2({
fromAddress: address1.account(),
toAddress: randomAddress.account(),
amount: [new cro.Coin('100000', Units.BASE)],
});

const msgSend2 = new cro.v2.bank.MsgSendV2({
fromAddress: address2.account(),
toAddress: address1.account(),
amount: [new cro.Coin('20000', Units.BASE)],
});

const account1 = await client.getAccount(address1.account());
const account2 = await client.getAccount(address2.account());

expect(account1).to.be.not.null;
expect(account2).to.be.not.null;

const signableTx = rawTx
.appendMessage(msgSend1)
.appendMessage(msgSend2)
.addSigner({
publicKey: keyPair.getPubKey(),
accountNumber: new Big(account1!.accountNumber),
accountSequence: new Big(account1!.sequence),
})
.addSigner({
publicKey: keyPair2.getPubKey(),
accountNumber: new Big(account2!.accountNumber),
accountSequence: new Big(account2!.sequence),
})
.toSignable();

const signedTx = signableTx
.setSignature(0, keyPair.sign(signableTx.toSignDocumentHash(0)))
.setSignature(1, keyPair2.sign(signableTx.toSignDocumentHash(1)))
.toSigned();

expect(msgSend1.fromAddress).to.eq(account1!.address);
expect(msgSend1.toAddress).to.eq(randomAddress.account());
const broadcastResult = await client.broadcastTx(signedTx.encode().toUint8Array());
assertIsBroadcastTxSuccess(broadcastResult);

const { transactionHash } = broadcastResult;
expect(transactionHash).to.match(/^[0-9A-F]{64}$/);
});
})

it('[BANK] creates a MsgSend type Transaction Signed by Legacy Amino JSON mode and Broadcasts it', async function () {
const hdKey = HDKey.fromMnemonic(env.mnemonic.communityAccount);
const hdKey2 = HDKey.fromMnemonic(env.mnemonic.reserveAccount);
Expand Down Expand Up @@ -188,6 +256,58 @@ describe('e2e test suite', function () {
const { transactionHash } = broadcastResult;
expect(transactionHash).to.match(/^[0-9A-F]{64}$/);
});
it('[BANK] creates a MsgSend Type Transaction with `Fee` amount and Broadcasts it.', async function () {
const hdKey = HDKey.fromMnemonic(env.mnemonic.communityAccount);
const hdKey2 = HDKey.fromMnemonic(env.mnemonic.reserveAccount);
const hdKey3 = HDKey.fromMnemonic(env.mnemonic.randomEmptyAccount);
const privKey = hdKey.derivePrivKey(`m/44'/${customNetwork.bip44Path.coinType}'/0'/0/0`);
const privKey2 = hdKey2.derivePrivKey(`m/44'/${customNetwork.bip44Path.coinType}'/0'/0/0`);
const randomPrivKey = hdKey3.derivePrivKey(`m/44'/${customNetwork.bip44Path.coinType}'/0'/0/0`);
const keyPair = Secp256k1KeyPair.fromPrivKey(privKey);
const keyPair2 = Secp256k1KeyPair.fromPrivKey(privKey2);
const randomKeyPair = Secp256k1KeyPair.fromPrivKey(randomPrivKey);

const cro = CroSDK({ network: customNetwork });
const rawTx = new cro.RawTransaction();
const address1 = new cro.Address(keyPair.getPubKey());
const address2 = new cro.Address(keyPair2.getPubKey());
const randomAddress = new cro.Address(randomKeyPair.getPubKey());
const client = await cro.CroClient.connect();

const msgSend1 = new cro.bank.MsgSend({
fromAddress: address1.account(),
toAddress: randomAddress.account(),
amount: new cro.Coin('100000', Units.BASE),
});

const account1 = await client.getAccount(address1.account());
const account2 = await client.getAccount(address2.account());

expect(account1).to.be.not.null;
expect(account2).to.be.not.null;

const signableTx = rawTx
.appendMessage(msgSend1)
.addSigner({
publicKey: keyPair.getPubKey(),
accountNumber: new Big(account1!.accountNumber),
accountSequence: new Big(account1!.sequence),
})
.setFee(cro.Coin.fromCRO("0.002"))
.toSignable();

const signedTx = signableTx
.setSignature(0, keyPair.sign(signableTx.toSignDocumentHash(0)))
.toSigned();

expect(msgSend1.fromAddress).to.eq(account1!.address);
expect(msgSend1.toAddress).to.eq(randomAddress.account());
const broadcastResult = await client.broadcastTx(signedTx.encode().toUint8Array());
assertIsBroadcastTxSuccess(broadcastResult);

const { transactionHash } = broadcastResult;
expect(transactionHash).to.match(/^[0-9A-F]{64}$/);
});

it('[STAKING] Creates, signs and broadcasts a `MsgDelegate` Tx', async function () {
const hdKey = HDKey.fromMnemonic(env.mnemonic.ecosystemAccount);
Expand Down
70 changes: 69 additions & 1 deletion lib/src/coin/coin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable */
// @ts-nocheck
import { expect } from 'chai';
import { fuzzyDescribe } from '../test/mocha-fuzzy/suite';

Expand Down Expand Up @@ -99,6 +101,72 @@ describe('Coin', function () {
expect(coins.toString()).to.eq(expectedBaseValue);
});
});

context('When `denom` is passed along other params', function () {
it('should throw Error when the provided `units` and `denom` do not belong to same network', function () {
expect(() => new cro.Coin('1000000', cro.Coin.UNIT_CRO, 'cosmos')).to.throw(
'Provided Units and Denom do not belong to the same network.',
);
});
// it('should throw Error on empty `denom`', function () {
// expect(() => new cro.Coin('1000000', cro.Coin.UNIT_CRO, '')).to.throw(
// 'Expected string `denom` to have a minimum length of `1`, got ``',
// );
// });
it('should set the `denom` correctly', function () {
expect(() => new cro.Coin('1000000', cro.Coin.UNIT_BASE, 'cosmos')).to.not.throw();

const coin = new cro.Coin('1000000', cro.Coin.UNIT_BASE, 'cosmos');
expect(coin.denom).to.equal('cosmos');
expect(coin.baseAmount.toString()).to.equal('1000000');
});
it('should return `baseAmount` correctly on same network `unit` & `denom`', function () {
expect(() => new cro.Coin('1000000', cro.Coin.UNIT_CRO, 'cro')).to.not.throw();
expect(() => new cro.Coin('1000000', cro.Coin.UNIT_CRO, 'tcro')).to.not.throw();

const CROcoin = new cro.Coin('11111111', cro.Coin.UNIT_CRO, 'cro');
const TCROcoin = new cro.Coin('22222222', cro.Coin.UNIT_CRO, 'tcro');

expect(CROcoin.denom).to.equal('cro');
expect(TCROcoin.denom).to.equal('tcro');

expect(TCROcoin.baseAmount.toString()).to.equal('2222222200000000');
expect(TCROcoin.toString()).to.equal('2222222200000000');
expect(TCROcoin.toString(cro.Coin.UNIT_CRO)).to.equal('22222222');

expect(CROcoin.baseAmount.toString()).to.equal('1111111100000000');
expect(CROcoin.toString()).to.equal('1111111100000000');
expect(CROcoin.toString(cro.Coin.UNIT_CRO)).to.equal('11111111');
});
});
});

describe('fromCustomAmountDenom', function () {
fuzzyDescribe('should throw Error when the provided value is not a string', function (fuzzy) {
const testRunner = fuzzy(fuzzy.StringArg('1000'));
testRunner(
function (arg) {
expect(() => cro.Coin.fromCustomAmountDenom(arg.value, arg.value)).to.throw(
'Expected `amount` to be of type `string`',
);
},
{ invalidArgsOnly: true },
);
});

it('should throw Error when the provided string is not a valid number', function () {
expect(() => cro.Coin.fromCustomAmountDenom('invalid', 'invalid')).to.throw(
'Expected amount to be a base10 number represented as string,',
);
});

it('should return `coin` instance on correct params', function () {
const coin = cro.Coin.fromCustomAmountDenom('1000', 'uatom');
expect(coin.denom).to.equal('uatom');
expect(coin.baseAmount.toString()).to.equal('1000');
expect(coin.toCosmosCoin().amount).to.equal('1000');
expect(coin.toCosmosCoin().denom).to.equal('uatom');
});
});

describe('fromBaseUnit', function () {
Expand Down Expand Up @@ -197,7 +265,7 @@ describe('Coin', function () {
});
});

describe('add', function () {
xdescribe('add', function () {
fuzzyDescribe('should throw Error when the provided coins is not an instance of Coin', function (fuzzy) {
const anyValidCoin = cro.Coin.fromBaseUnit('1000');
const testRunner = fuzzy(fuzzy.ObjArg(anyValidCoin));
Expand Down
Loading

0 comments on commit b2f852c

Please sign in to comment.