Skip to content

Commit

Permalink
implement the propose new masternode part
Browse files Browse the repository at this point in the history
  • Loading branch information
wjrjerome committed Jul 30, 2023
1 parent 80f9b8c commit f93a5c5
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 52 deletions.
29 changes: 29 additions & 0 deletions frontend/src/services/grandmaster-manager/extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Web3 from 'web3';

export interface Web3WithExtension extends Web3 {
xdcSubnet: {
getCandidates: (epochNum: "latest") => Promise<{
candidates: {
[key: string]: {
capacity: number;
status: 'MASTERNODE' | 'PROPOSED' | 'SLASHED'
}
};
epoch: number;
success: boolean;
}>
};
}

export const networkExtensions = (extensionName = 'xdcSubnet') => {
return {
property: extensionName,
methods: [
{
name: 'getCandidates',
params: 1,
call: 'eth_getCandidates',
}
],
};
};
151 changes: 99 additions & 52 deletions frontend/src/services/grandmaster-manager/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import Web3 from "web3";
import {
JsonRpcResponse
} from 'web3-core-helpers';
import { ErrorTypes, ManagerError } from "@/services/grandmaster-manager/errors";
import { networkExtensions, Web3WithExtension } from "@/services/grandmaster-manager/extensions";
import { getSigningMsg } from "@/services/grandmaster-manager/utils";

interface AccountDetails {
accountAddress: string;
Expand All @@ -12,26 +17,50 @@ interface AccountDetails {
interface CandidateDetails {
address: string;
delegation: number;
rank: number;
status: 'MASTERNODE' | 'PROPOSED' | 'SLASHED'
}

type ContractAvailableActions = 'propose' | 'resign' | 'vote' | 'unvote';
interface ContractAvailableActionDetail {
name: string;
action: ContractAvailableActions;
}
const contractAvailableActions: {[key in ContractAvailableActions]: ContractAvailableActionDetail} = {
propose: {
action: "propose",
name: "Propose a new masternode"
},
resign: {
action: "resign",
name: "Resign an existing masternode"
},
vote: {
action: "vote",
name: "Increase masternode delegation",
},
unvote: {
action: "unvote",
name: "Decrease masternode delegation",
}
}


const CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000088";

export class GrandMasterManager {
private web3Client;
private web3Client: Web3WithExtension;
constructor() {
const win = window as any;
this.web3Client = new Web3(win.xdc ? win.xdc : win.ethereum);
this.web3Client = new Web3((window as any).ethereum).extend(networkExtensions());
}

private isXdcWalletInstalled() {
if (this.web3Client.currentProvider && (window as any).xdc) {
if (this.web3Client.currentProvider) {
return true;
}
return false;
}

private async getAccountDetails() {
private async getGrandMasterAccountDetails() {
const accounts = await this.web3Client.eth.getAccounts();
if (!accounts || !accounts.length || !accounts[0].length) {
throw new Error("No wallet address found, have you logged in?");
Expand All @@ -40,6 +69,7 @@ export class GrandMasterManager {
const balance = await this.web3Client.eth.getBalance(accountAddress);
const networkId = await this.web3Client.eth.getChainId();
// TODO: Get denom, rpcAddress
// TODO: Check with the grand master info from the node. Make sure they are the same, otherwise NOT_GRANDMASTER error
return {
accountAddress, balance, networkId
}
Expand All @@ -48,18 +78,15 @@ export class GrandMasterManager {
/**
* This method will detect XDC-Pay and verify if customer has alraedy loggin.
* @returns Account details will be returned if all good
* @throws ManagerError with type of "WALLET_NOT_INSTALLED" || "CONFLICT_WITH_METAMASK" || "WALLET_NOT_LOGIN"
* @throws ManagerError with type of "WALLET_NOT_INSTALLED" || "WALLET_NOT_LOGIN"
*/
async login(): Promise<AccountDetails> {
if (!this.isXdcWalletInstalled) {
throw new ManagerError("XDC Pay Not Installed", ErrorTypes.WALLET_NOT_INSTALLED)
}
if ((this.web3Client.currentProvider as any).chainId) {
throw new ManagerError("Metamask need to be disabled", ErrorTypes.CONFLICT_WITH_METAMASK)
}

try {
const { accountAddress, balance, networkId } = await this.getAccountDetails();
const { accountAddress, balance, networkId } = await this.getGrandMasterAccountDetails();
return {
accountAddress,
balance,
Expand All @@ -72,21 +99,54 @@ export class GrandMasterManager {
}
}

/**
* Remove a masternode from the manager view list
* @param address The master node to be removed
* @returns If transaction is successful, return. Otherwise, an error details will be thrown
*/
async removeMasterNode(address: string): Promise<true> {
return true;
private encodeAbi(functionName: ContractAvailableActions, address: string) {
return this.web3Client.eth.abi.encodeFunctionCall({
name: functionName,
type: 'function',
inputs: [{
type: 'string',
name: 'address'
}]
}, [address])
}

private async signTransaction(action: ContractAvailableActionDetail, targetAddress: string, value: string) {
const { accountAddress, networkId } = await this.getGrandMasterAccountDetails();
const nonce = await this.web3Client.eth.getTransactionCount(accountAddress);
const encodedData = this.encodeAbi(action.action, targetAddress);
const msg = getSigningMsg(action.name, networkId, nonce, encodedData, value);
const payload = {
method: "eth_signTypedData_v4",
params: [accountAddress, msg],
from: accountAddress
};
(this.web3Client.currentProvider as any).sendAsync(payload, (err: any, result: JsonRpcResponse) => {
if (err) {
throw err;
}
if (result.error) {
throw new ManagerError("Received error when signing the transaction")
}
return result.result;
})
}
/**
* Propose to add a new masternode for being a mining candidate from the next epoch
* @param address The master node to be added
* @returns If transaction is successful, return. Otherwise, an error details will be thrown
*/
async addNewMasterNode(address: string): Promise<true> {
const signedTransaction = await this.signTransaction(contractAvailableActions.propose, address, "0x84595161401484a000000");

return true;
}

/**
* Remove a masternode from the manager view list
* @param address The master node to be removed
* @returns If transaction is successful, return. Otherwise, an error details will be thrown
*/
async removeMasterNode(address: string): Promise<true> {
return true;
}

Expand All @@ -105,9 +165,11 @@ export class GrandMasterManager {
* @param changeHandler The handler function to process when the accounts changed. The provided value will be the new wallet address.
*/
onAccountChange(changeHandler: (accounts: string) => any) {
changeHandler("0x3c03a0abac1da8f2f419a59afe1c125f90b506c5");
// TODO: 1. Handle the account change via accountsChanged
// TODO: 2. Handle the chain change via chainChanged. This could happen if switch from testnet to mainnet etc.
(this.web3Client.currentProvider as any).on("accountsChanged", (accounts: string[]) => {
if (accounts && accounts.length) {
changeHandler(accounts[0])
}
})
}

/**
Expand All @@ -118,37 +180,22 @@ export class GrandMasterManager {
* 'SLASHED' means it's been taken out from the masternode list
*/
async getCandidates(): Promise<CandidateDetails[]> {
return [
{
address: "xdc25B4CBb9A7AE13feadC3e9F29909833D19D16dE5",
delegation: 5e+22,
rank: 0,
status: "MASTERNODE"
},
{
address: "xdc2af0Cacf84899F504a6dC95e6205547bDfe28c2c",
delegation: 5e+22,
rank: 1,
status: "MASTERNODE"
},
{
address: "xdc30f21E514A66732DA5Dff95340624fa808048601",
delegation: 5e+22,
rank: 2,
status: "MASTERNODE"
},
{
address: "xdc3C03a0aBaC1DA8f2f419a59aFe1c125F90B506c5",
delegation: 5e+22,
rank: 3,
status: "PROPOSED"
},
{
address: "xdc3D9fd0c76BB8B3B4929ca861d167f3e05926CB68",
delegation: 5e+22,
rank: 4,
status: "SLASHED"
try {
const { candidates, success } = await this.web3Client.xdcSubnet.getCandidates("latest");
if (!success) {
throw new ManagerError("Fail to get list of candidates from xdc subnet");
}
];
return Object.entries(candidates).map(entry => {
const [ address, { capacity, status }] = entry;
return {
address,
delegation: capacity,
status
}
}).sort((a, b) => b.delegation - a.delegation);

} catch (error: any) {
throw new ManagerError(error.message);
}
}
}
36 changes: 36 additions & 0 deletions frontend/src/services/grandmaster-manager/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export const getSigningMsg = (name: string, chainId: number, nonce: number, encodedData: string, value: string) => {
return JSON.stringify({
domain: {
chainId: chainId.toString(),
name,
// verifyingContract: "0x0000000000000000000000000000000000000088", // To be replaced by the actual sm address
version: "1"
},
message: {
nonce: Number(nonce),
gasPrice: 250000000,
gasLimit: 220000,
to: '0x0000000000000000000000000000000000000088',
value,
data: encodedData,
},
primaryType: 'Grandmaster',
types: {
// This refers to the domain the contract is hosted on.
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
// { name: 'verifyingContract', type: 'address' },
],
Grandmaster: [
{ name: 'nonce', type: 'uint256' },
{ name: 'gasPrice', type: 'uint256' },
{ name: 'gasLimit', type: 'uint256' },
{ name: 'to', type: 'string' },
{ name: 'value', type: 'string' },
{ name: 'data', type: 'string' },
],
},
});
}

0 comments on commit f93a5c5

Please sign in to comment.