diff --git a/src/bond.ts b/src/bond.ts index a2124b6..2e38f8f 100644 --- a/src/bond.ts +++ b/src/bond.ts @@ -37,6 +37,25 @@ const MSG_REFILL_BOND_TYPES = { ], } +const MSG_WITHDRAW_BOND_TYPES = { + MsgValue: [ + { name: 'id', type: 'string' }, + { name: 'signer', type: 'string' }, + { name: 'coins', type: 'TypeCoins[]' }, + ], + TypeCoins: [ + { name: 'denom', type: 'string' }, + { name: 'amount', type: 'string' }, + ], +} + +const MSG_CANCEL_BOND_TYPES = { + MsgValue: [ + { name: 'id', type: 'string' }, + { name: 'signer', type: 'string' }, + ] +} + export interface MessageMsgCreateBond { amount: string denom: string @@ -48,6 +67,16 @@ export interface MessageMsgRefillBond { denom: string } +export interface MessageMsgWithdrawBond { + id: string + amount: string + denom: string +} + +export interface MessageMsgCancelBond { + id: string +} + export function createTxMsgCreateBond( chain: Chain, sender: Sender, @@ -168,6 +197,124 @@ export function createTxMsgRefillBond( } } +export function createTxMsgWithdrawBond( + chain: Chain, + sender: Sender, + fee: Fee, + memo: string, + params: MessageMsgWithdrawBond, +) { + // EIP712 + const feeObject = generateFee( + fee.amount, + fee.denom, + fee.gas, + sender.accountAddress, + ) + const types = generateTypes(MSG_WITHDRAW_BOND_TYPES) + + const msg = createMsgWithdrawBond( + params.id, + sender.accountAddress, + params.amount, + params.denom + ) + + const messages = generateMessage( + sender.accountNumber.toString(), + sender.sequence.toString(), + chain.cosmosChainId, + memo, + feeObject, + msg, + ) + const eipToSign = createEIP712(types, chain.chainId, messages) + + // Cosmos + const msgCosmos = protoCreateMsgWithdrawBond( + params.id, + sender.accountAddress, + params.amount, + params.denom + ) + + const tx = createTransaction( + msgCosmos, + memo, + fee.amount, + fee.denom, + parseInt(fee.gas, 10), + 'ethsecp256', + sender.pubkey, + sender.sequence, + sender.accountNumber, + chain.cosmosChainId, + ) + + return { + signDirect: tx.signDirect, + legacyAmino: tx.legacyAmino, + eipToSign, + } +} + +export function createTxMsgCancelBond( + chain: Chain, + sender: Sender, + fee: Fee, + memo: string, + params: MessageMsgCancelBond, +) { + // EIP712 + const feeObject = generateFee( + fee.amount, + fee.denom, + fee.gas, + sender.accountAddress, + ) + const types = generateTypes(MSG_CANCEL_BOND_TYPES) + + const msg = createMsgCancelBond( + params.id, + sender.accountAddress + ) + + const messages = generateMessage( + sender.accountNumber.toString(), + sender.sequence.toString(), + chain.cosmosChainId, + memo, + feeObject, + msg, + ) + const eipToSign = createEIP712(types, chain.chainId, messages) + + // Cosmos + const msgCosmos = protoCreateMsgCancelBond( + params.id, + sender.accountAddress + ) + + const tx = createTransaction( + msgCosmos, + memo, + fee.amount, + fee.denom, + parseInt(fee.gas, 10), + 'ethsecp256', + sender.pubkey, + sender.sequence, + sender.accountNumber, + chain.cosmosChainId, + ) + + return { + signDirect: tx.signDirect, + legacyAmino: tx.legacyAmino, + eipToSign, + } +} + function createMsgCreateBond( signer: string, amount: string, @@ -251,3 +398,75 @@ const protoCreateMsgRefillBond = ( path: 'vulcanize.bond.v1beta1.MsgRefillBond', } } + +function createMsgWithdrawBond( + id: string, + signer: string, + amount: string, + denom: string, +) { + return { + type: 'bond/MsgWithdrawBond', + value: { + id, + coins: [ + { + amount, + denom, + }, + ], + signer + }, + } +} + +const protoCreateMsgWithdrawBond = ( + id: string, + signer: string, + amount: string, + denom: string, +) => { + const value = new coin.cosmos.base.v1beta1.Coin({ + denom, + amount, + }) + + const withdrawBondMessage = new bondTx.vulcanize.bond.v1beta1.MsgWithdrawBond({ + id, + signer, + coins: [value] + }) + + return { + message: withdrawBondMessage, + path: 'vulcanize.bond.v1beta1.MsgWithdrawBond', + } +} + +function createMsgCancelBond( + id: string, + signer: string +) { + return { + type: 'bond/MsgCancelBond', + value: { + id, + signer + }, + } +} + +const protoCreateMsgCancelBond = ( + id: string, + signer: string +) => { + const cancelBondMessage = new bondTx.vulcanize.bond.v1beta1.MsgCancelBond({ + id, + signer + }) + + return { + message: cancelBondMessage, + path: 'vulcanize.bond.v1beta1.MsgCancelBond', + } +} diff --git a/src/bonds.test.ts b/src/bonds.test.ts index d406f96..9c56cec 100644 --- a/src/bonds.test.ts +++ b/src/bonds.test.ts @@ -1,6 +1,8 @@ import { Registry } from './index'; import { getConfig, wait } from './testing/helper'; +const TX_WAIT_TIME = 5000; // in milliseconds. + const { mockServer, chibaClonk: { chainId, restEndpoint, gqlEndpoint, privateKey, accountAddress, fee } } = getConfig(); jest.setTimeout(90 * 1000); @@ -18,8 +20,8 @@ const bondTests = () => { test('Create bond.', async () => { bondId1 = await registry.getNextBondId(accountAddress); expect(bondId1).toBeDefined(); - await registry.createBond({ denom: 'aphoton', amount: '100' }, accountAddress, privateKey, fee); - await wait(5000) + await registry.createBond({ denom: 'aphoton', amount: '1000000000' }, accountAddress, privateKey, fee); + await wait(TX_WAIT_TIME) }) test('Get bond by ID.', async () => { @@ -27,7 +29,7 @@ const bondTests = () => { expect(bond).toBeDefined(); expect(bond.id).toBe(bondId1); expect(bond.balance).toHaveLength(1); - expect(bond.balance[0]).toEqual({ type: 'aphoton', quantity: '100' }); + expect(bond.balance[0]).toEqual({ type: 'aphoton', quantity: '1000000000' }); bondOwner = bond.owner; }); @@ -44,6 +46,38 @@ const bondTests = () => { const bond = bonds.filter((bond: any) => bond.id === bondId1); expect(bond).toBeDefined(); }); + + test('Refill bond.', async () => { + await registry.refillBond({ id: bondId1, denom: 'aphoton', amount: '500' }, accountAddress, privateKey, fee); + await wait(TX_WAIT_TIME); + + const [bond] = await registry.getBondsByIds([bondId1]); + expect(bond).toBeDefined(); + expect(bond.id).toBe(bondId1); + expect(bond.balance).toHaveLength(1); + expect(bond.balance[0]).toEqual({ type: 'aphoton', quantity: '1000000500' }); + }); + + test('Withdraw bond.', async () => { + await registry.withdrawBond({ id: bondId1, denom: 'aphoton', amount: '500' }, accountAddress, privateKey, fee); + await wait(TX_WAIT_TIME); + + const [bond] = await registry.getBondsByIds([bondId1]); + expect(bond).toBeDefined(); + expect(bond.id).toBe(bondId1); + expect(bond.balance).toHaveLength(1); + expect(bond.balance[0]).toEqual({ type: 'aphoton', quantity: '1000000000' }); + }); + + test('Cancel bond.', async () => { + await registry.cancelBond({ id: bondId1 }, accountAddress, privateKey, fee); + await wait(TX_WAIT_TIME); + + const [bond] = await registry.getBondsByIds([bondId1]); + expect(bond.id).toBe(""); + expect(bond.owner).toBe(""); + expect(bond.balance).toHaveLength(0); + }); }; if (mockServer) { diff --git a/src/index.ts b/src/index.ts index dcc41c9..570548f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { Fee, } from '@tharsis/transactions' -import { createTxMsgCreateBond, MessageMsgCreateBond } from "./bond"; +import { createTxMsgCancelBond, createTxMsgCreateBond, createTxMsgRefillBond, createTxMsgWithdrawBond, MessageMsgCancelBond, MessageMsgCreateBond, MessageMsgRefillBond, MessageMsgWithdrawBond } from "./bond"; import { RegistryClient } from "./registry-client"; import { Account } from "./account"; import { createTransaction } from "./txbuilder"; @@ -21,7 +21,6 @@ export const DEFAULT_CHAIN_ID = 'ethermint_9000-1'; export const parseTxResponse = (result: any) => { const { txhash: hash, height, ...txResponse } = result; txResponse.data = txResponse.data && Buffer.from(txResponse.data, 'base64').toString('utf8'); - txResponse.log = JSON.parse(txResponse.raw_log); txResponse.events.forEach((event:any) => { event.attributes = event.attributes.map(({ key, value }: { key: string, value: string }) => ({ @@ -138,6 +137,84 @@ export class Registry { return parseTxResponse(result); } + /** + * 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. */ diff --git a/src/testing/helper.ts b/src/testing/helper.ts index 2eb0161..cffd4f8 100644 --- a/src/testing/helper.ts +++ b/src/testing/helper.ts @@ -1,5 +1,5 @@ -const DEFAULT_PRIVATE_KEY = '794ce0bf3c75571416001c3415e69059aeba54038bcac8ce5b9792259e6d193b'; -const DEFAULT_ADDRESS = 'ethm10atmndy7sm46829rc3yr7cxqucgrz5e9jg58xp' +const DEFAULT_PRIVATE_KEY = '3d8e23810daecb66ec4ca97805f6bbfc102015c3f22cdda1a783b1d074c43bdd'; +const DEFAULT_ADDRESS = 'ethm1lrdrh056ce23h9d9d5rx34tp0uwj0u9zumynx3' export const wait = (time: number) => new Promise(resolve => setTimeout(resolve, time))