diff --git a/__tests__/stake.test.ts b/__tests__/stake.test.ts new file mode 100644 index 00000000..aae1a3c6 --- /dev/null +++ b/__tests__/stake.test.ts @@ -0,0 +1,65 @@ +import { getClient, address } from "./utils" + +describe("stake management", () => { + beforeEach(() => { + jest.setTimeout(50000) + }) + + it("bsc delegate", async () => { + const client = await getClient(true) + const validatorAddress = "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2" + + try { + const res = await client.stake.bscDelegate({ + delegateAddress: address, + validatorAddress, + amount: 10, + }) + expect(res.status).toBe(200) + } catch (err) { + if (err.message.includes("insufficient fund")) { + expect(1).toBeTruthy() + } + throw err + } + }) + + it("bsc undelegate", async () => { + const client = await getClient(true) + const validatorAddress = "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2" + + try { + const res = await client.stake.bscUndelegate({ + delegateAddress: address, + validatorAddress, + amount: 10, + }) + expect(res.status).toBe(200) + } catch (err) { + if (err.message.includes("insufficient fund")) { + expect(1).toBeTruthy() + } + throw err + } + }) + + it("bsc redelegate", async () => { + const client = await getClient(true) + const validatorSrcAddress = "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2" + const validatorDstAddress = "bva1pcd6muhehuz6fy05wfhq9sd5fww6ggdap3adxg" + try { + const res = await client.stake.bscReDelegate({ + delegateAddress: address, + validatorSrcAddress, + validatorDstAddress, + amount: 10, + }) + expect(res.status).toBe(200) + } catch (err) { + if (err.message.includes("insufficient fund")) { + expect(1).toBeTruthy() + } + throw err + } + }) +}) diff --git a/docs/api-docs/README.md b/docs/api-docs/README.md index c95393c9..62cba05d 100644 --- a/docs/api-docs/README.md +++ b/docs/api-docs/README.md @@ -9,6 +9,7 @@ * [Bridge](classes/bridge.md) * [LedgerApp](classes/ledgerapp.md) * [RpcClient](classes/rpcclient.md) +* [Stake](classes/stake.md) * [TokenManagement](classes/tokenmanagement.md) * [Transaction](classes/transaction.md) diff --git a/docs/api-docs/classes/stake.md b/docs/api-docs/classes/stake.md new file mode 100644 index 00000000..0f7d793a --- /dev/null +++ b/docs/api-docs/classes/stake.md @@ -0,0 +1,28 @@ + +# Class: Stake + +Stake + +## Hierarchy + +* **Stake** + +## Index + +### Constructors + +* [constructor](stake.md#constructor) + +## Constructors + +### constructor + +\+ **new Stake**(`bncClient`: [BncClient](bncclient.md)): *[Stake](stake.md)* + +**Parameters:** + +Name | Type | Description | +------ | ------ | ------ | +`bncClient` | [BncClient](bncclient.md) | | + +**Returns:** *[Stake](stake.md)* diff --git a/src/client/index.ts b/src/client/index.ts index 48e13ec3..09d0df5d 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -12,6 +12,7 @@ import Gov from "./gov" import Swap from "./swap" import TokenManagement, { validateMiniTokenSymbol } from "./token" import { Bridge } from "./bridge" +import { Stake } from "./stake" const BASENUMBER = Math.pow(10, 8) @@ -145,6 +146,7 @@ export class BncClient { public swap: Swap public gov: Gov public bridge: Bridge + public stake: Stake public chainId?: string | null public addressPrefix: typeof NETWORK_PREFIX_MAPPING[keyof typeof NETWORK_PREFIX_MAPPING] = "tbnb" @@ -173,6 +175,7 @@ export class BncClient { this.swap = new Swap(this) this.gov = new Gov(this) this.bridge = new Bridge(this) + this.stake = new Stake(this) } /** @@ -816,6 +819,7 @@ export class BncClient { sequence: typeof sequence !== "number" ? parseInt(sequence!) : sequence, source: this._source, }) + return this._signingDelegate.call(this, tx, stdSignMsg) } diff --git a/src/client/stake/index.ts b/src/client/stake/index.ts new file mode 100644 index 00000000..6e6ea7db --- /dev/null +++ b/src/client/stake/index.ts @@ -0,0 +1,156 @@ +import Big from "big.js" + +import { BncClient } from "../" +import * as crypto from "../../crypto" +import { + BscDelegateMsg, + BaseMsg, + BscUndelegateMsg, + BscReDelegateMsg, +} from "../../types" + +/** + * Stake + */ +export class Stake { + private _bncClient!: BncClient + + /** + * @param {BncClient} bncClient + */ + constructor(bncClient: BncClient) { + this._bncClient = bncClient + } + + public async bscDelegate({ + delegateAddress, + validatorAddress, + amount, + symbol = "BNB", + sideChainId = "chapel", //default value is ganges(testnet) + }: { + delegateAddress: string + validatorAddress: string + amount: number + symbol?: string + sideChainId?: string + }) { + if (!amount) { + throw new Error("amount should not be empty") + } + + if (!delegateAddress) { + throw new Error("delegate address should not be null") + } + + if (!crypto.checkAddress(validatorAddress, "bva")) { + throw new Error("validator address is not valid") + } + + amount = Number(new Big(amount).mul(Math.pow(10, 8)).toString()) + + const bscDelegateMsg = new BscDelegateMsg({ + delegator_addr: delegateAddress, + validator_addr: validatorAddress, + delegation: { denom: symbol, amount }, + side_chain_id: sideChainId, + }) + + return await this.broadcast(bscDelegateMsg, delegateAddress) + } + + public async bscUndelegate({ + delegateAddress, + validatorAddress, + amount, + symbol = "BNB", + sideChainId = "chapel", //default value is ganges(testnet) + }: { + delegateAddress: string + validatorAddress: string + amount: number + symbol?: string + sideChainId?: string + }) { + if (!amount) { + throw new Error("amount should not be empty") + } + + if (!delegateAddress) { + throw new Error("delegate address should not be null") + } + + if (!crypto.checkAddress(validatorAddress, "bva")) { + throw new Error("validator address is not valid") + } + + amount = Number(new Big(amount).mul(Math.pow(10, 8)).toString()) + + const unDelegateMsg = new BscUndelegateMsg({ + delegator_addr: delegateAddress, + validator_addr: validatorAddress, + amount: { denom: symbol, amount }, + side_chain_id: sideChainId, + }) + + return await this.broadcast(unDelegateMsg, delegateAddress) + } + + public async bscReDelegate({ + delegateAddress, + validatorSrcAddress, + validatorDstAddress, + amount, + symbol = "BNB", + sideChainId = "chapel", //default value is ganges(testnet) + }: { + delegateAddress: string + validatorSrcAddress: string + validatorDstAddress: string + amount: number + symbol?: string + sideChainId?: string + }) { + if (!amount) { + throw new Error("amount should not be empty") + } + + if (!delegateAddress) { + throw new Error("delegate address should not be null") + } + + if (!crypto.checkAddress(validatorSrcAddress, "bva")) { + throw new Error("validator source address is not valid") + } + + if (!crypto.checkAddress(validatorDstAddress, "bva")) { + throw new Error("validator dest address is not valid") + } + + amount = Number(new Big(amount).mul(Math.pow(10, 8)).toString()) + + const bscReDelegateMsg = new BscReDelegateMsg({ + delegator_addr: delegateAddress, + validator_src_addr: validatorSrcAddress, + validator_dst_addr: validatorDstAddress, + amount: { denom: symbol, amount }, + side_chain_id: sideChainId, + }) + + return await this.broadcast(bscReDelegateMsg, delegateAddress) + } + + private async broadcast( + msg: BaseMsg, + fromAddress: string, + sequence?: number + ) { + const signedTx = await this._bncClient._prepareTransaction( + msg.getMsg(), + msg.getSignMsg(), + fromAddress, + sequence + ) + return this._bncClient._broadcastDelegate(signedTx) + } +} diff --git a/src/types/msg/index.ts b/src/types/msg/index.ts index b0fec9f8..356037cf 100644 --- a/src/types/msg/index.ts +++ b/src/types/msg/index.ts @@ -3,3 +3,4 @@ export * from "./dex" export * from "./token" export * from "./send" export * from "./claim" +export * from "./stake" diff --git a/src/types/msg/stake/bscDelegateMsg.ts b/src/types/msg/stake/bscDelegateMsg.ts new file mode 100644 index 00000000..71372cef --- /dev/null +++ b/src/types/msg/stake/bscDelegateMsg.ts @@ -0,0 +1,80 @@ +import { BaseMsg, Msg, SignMsg, Coin } from ".." +import * as crypto from "../../../crypto" +import { AminoPrefix } from "../../tx" + +export interface SignedBscDelegate extends SignMsg { + delegator_addr: string + validator_addr: string + delegation: Coin + side_chain_id: string +} + +export interface BscDelegateData extends Msg { + delegator_addr: Buffer + validator_addr: Buffer + delegation: Coin + side_chain_id: string + aminoPrefix: AminoPrefix +} + +export class BscDelegateMsg extends BaseMsg { + private delegator_addr: string + private validator_addr: string + private delegation: Coin + private side_chain_id: string + + constructor({ + delegator_addr, + validator_addr, + delegation, + side_chain_id, + }: { + delegator_addr: string + validator_addr: string + delegation: Coin + side_chain_id: string + }) { + super() + this.delegator_addr = delegator_addr + this.validator_addr = validator_addr + this.delegation = delegation + this.side_chain_id = side_chain_id + } + + getSignMsg() { + const { denom, amount } = this.delegation + const signMsg: SignedBscDelegate = { + delegator_addr: this.delegator_addr, + validator_addr: this.validator_addr, + delegation: { denom, amount: String(amount) }, + side_chain_id: this.side_chain_id, + } + + return { + type: "cosmos-sdk/MsgSideChainDelegate", + value: signMsg, + } + } + + getMsg() { + const data: BscDelegateData = { + delegator_addr: crypto.decodeAddress(this.delegator_addr), + validator_addr: crypto.decodeAddress(this.validator_addr), + delegation: this.delegation, + side_chain_id: this.side_chain_id, + aminoPrefix: AminoPrefix.MsgSideChainDelegate, + } + + return data + } + + static defaultMsg() { + return { + delegator_addr: Buffer.from(""), + validator_addr: Buffer.from(""), + delegation: [{ denom: "", amount: 0 }], + side_chain_id: "", + aminoPrefix: AminoPrefix.MsgSideChainDelegate, + } + } +} diff --git a/src/types/msg/stake/bscRedelegateMsg.ts b/src/types/msg/stake/bscRedelegateMsg.ts new file mode 100644 index 00000000..b69a3a44 --- /dev/null +++ b/src/types/msg/stake/bscRedelegateMsg.ts @@ -0,0 +1,89 @@ +import { BaseMsg, Msg, SignMsg, Coin } from ".." +import * as crypto from "../../../crypto" +import { AminoPrefix } from "../../tx" + +export interface SignedBscReDelegate extends SignMsg { + delegator_addr: string + validator_src_addr: string + validator_dst_addr: string + amount: Coin + side_chain_id: string +} + +export interface BscReDelegateData extends Msg { + delegator_addr: Buffer + validator_src_addr: Buffer + validator_dst_addr: Buffer + amount: Coin + side_chain_id: string + aminoPrefix: AminoPrefix +} + +export class BscReDelegateMsg extends BaseMsg { + private delegator_addr: string + private validator_src_addr: string + private validator_dst_addr: string + private amount: Coin + private side_chain_id: string + + constructor({ + delegator_addr, + validator_src_addr, + validator_dst_addr, + amount, + side_chain_id, + }: { + delegator_addr: string + validator_src_addr: string + validator_dst_addr: string + amount: Coin + side_chain_id: string + }) { + super() + this.delegator_addr = delegator_addr + this.validator_src_addr = validator_src_addr + this.validator_dst_addr = validator_dst_addr + this.amount = amount + this.side_chain_id = side_chain_id + } + + getSignMsg() { + const { denom, amount } = this.amount + const signMsg: SignedBscReDelegate = { + delegator_addr: this.delegator_addr, + validator_src_addr: this.validator_src_addr, + validator_dst_addr: this.validator_dst_addr, + amount: { denom, amount: String(amount) }, + side_chain_id: this.side_chain_id, + } + + return { + type: "cosmos-sdk/MsgSideChainRedelegate", + value: signMsg, + } + } + + getMsg() { + const data: BscReDelegateData = { + delegator_addr: crypto.decodeAddress(this.delegator_addr), + validator_src_addr: crypto.decodeAddress(this.validator_src_addr), + validator_dst_addr: crypto.decodeAddress(this.validator_dst_addr), + amount: this.amount, + side_chain_id: this.side_chain_id, + aminoPrefix: AminoPrefix.MsgSideChainRedelegate, + } + + return data + } + + static defaultMsg() { + return { + delegator_addr: Buffer.from(""), + validator_src_addr: Buffer.from(""), + validator_dst_addr: Buffer.from(""), + amount: [{ denom: "", amount: 0 }], + side_chain_id: "", + aminoPrefix: AminoPrefix.MsgSideChainRedelegate, + } + } +} diff --git a/src/types/msg/stake/bscUndelegateMsg.ts b/src/types/msg/stake/bscUndelegateMsg.ts new file mode 100644 index 00000000..c71c857b --- /dev/null +++ b/src/types/msg/stake/bscUndelegateMsg.ts @@ -0,0 +1,80 @@ +import { BaseMsg, Msg, SignMsg, Coin } from ".." +import * as crypto from "../../../crypto" +import { AminoPrefix } from "../../tx" + +export interface SignedBscUndelegate extends SignMsg { + delegator_addr: string + validator_addr: string + amount: Coin + side_chain_id: string +} + +export interface BscUndelegateData extends Msg { + delegator_addr: Buffer + validator_addr: Buffer + amount: Coin + side_chain_id: string + aminoPrefix: AminoPrefix +} + +export class BscUndelegateMsg extends BaseMsg { + private delegator_addr: string + private validator_addr: string + private amount: Coin + private side_chain_id: string + + constructor({ + delegator_addr, + validator_addr, + amount, + side_chain_id, + }: { + delegator_addr: string + validator_addr: string + amount: Coin + side_chain_id: string + }) { + super() + this.delegator_addr = delegator_addr + this.validator_addr = validator_addr + this.amount = amount + this.side_chain_id = side_chain_id + } + + getSignMsg() { + const { denom, amount } = this.amount + const signMsg: SignedBscUndelegate = { + delegator_addr: this.delegator_addr, + validator_addr: this.validator_addr, + amount: { denom, amount: String(amount) }, + side_chain_id: this.side_chain_id, + } + + return { + type: "cosmos-sdk/MsgSideChainUndelegate", + value: signMsg, + } + } + + getMsg() { + const data: BscUndelegateData = { + delegator_addr: crypto.decodeAddress(this.delegator_addr), + validator_addr: crypto.decodeAddress(this.validator_addr), + amount: this.amount, + side_chain_id: this.side_chain_id, + aminoPrefix: AminoPrefix.MsgSideChainUndelegate, + } + + return data + } + + static defaultMsg() { + return { + delegator_addr: Buffer.from(""), + validator_addr: Buffer.from(""), + amount: [{ denom: "", amount: 0 }], + side_chain_id: "", + aminoPrefix: AminoPrefix.MsgSideChainUndelegate, + } + } +} diff --git a/src/types/msg/stake/index.ts b/src/types/msg/stake/index.ts new file mode 100644 index 00000000..09963b77 --- /dev/null +++ b/src/types/msg/stake/index.ts @@ -0,0 +1,3 @@ +export * from "./bscDelegateMsg" +export * from "./bscUndelegateMsg" +export * from "./bscRedelegateMsg"