Skip to content
This repository has been archived by the owner on Dec 1, 2021. It is now read-only.

Track total number of Cosmos accounts #549

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
1 change: 1 addition & 0 deletions both/i18n/en-us.i18n.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ chainStatus:
outOfValidators: 'out of {$totalValidators} validators'
onlineVotingPower: 'Online Voting Power'
fromTotalStakes: '{$percent} from {$totalStakes} {$denomPlural}'
totalAccounts: 'Cosmos Accounts (Total)'
analytics:
blockTimeHistory: 'Block Time History'
averageBlockTime: 'Average Block Time'
Expand Down
2 changes: 2 additions & 0 deletions client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import App from '/imports/ui/App.jsx';
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom'
// import ReactDOM from 'react-dom';
import { getCosmosAccountsNumber } from '../imports/ui/home/CosmosAccounts'

import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
Expand All @@ -31,4 +32,5 @@ Meteor.startup(() => {
// </Router>, document.getElementById('app')
// );
// });
setTimeout(getCosmosAccountsNumber, 60000);
});
24 changes: 24 additions & 0 deletions imports/api/chain/server/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,28 @@ Meteor.methods({
this.unblock();
Chain.find().sort({created:-1}).limit(1);
},
'chain.shouldUpdateCosmosAccountsNumber': function () {
this.unblock();
let date = new Date();
let chain = Chain.find().fetch();
let lastUpdated = chain?.lastUpdated ? new Date(chain?.lastUpdated) : new Date();
let timeDifference = moment(date).diff(moment(lastUpdated), 'hours');
let shouldUpdate = timeDifference >= 24 ? true : false;
return shouldUpdate;
},
'chain.getCosmosAccountsNumber': function (totalNumberOfAccountsIndex) {
this.unblock();
let date = new Date();
let dateUTC = date.toUTCString();
let totalNumberOfCosmosAccounts = {
total: totalNumberOfAccountsIndex,
lastUpdated: dateUTC
}
try {
Chain.upsert({ chainId: Meteor.settings.public.chainId }, { $set: { "totalNumberOfCosmosAccounts": totalNumberOfCosmosAccounts } });
}
catch (e) {
console.log(e);
}
},
})
27 changes: 17 additions & 10 deletions imports/ui/home/ChainStatus.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export default class ChainStatus extends React.Component {
numValidators: this.props.status.validators,
totalNumValidators: this.props.status.totalValidators,
bondedTokens: this.props.states.bondedTokens,
totalSupply: this.props.states.totalSupply
totalSupply: this.props.states.totalSupply,
totalNumberOfCosmosAccounts: this.props.status.totalNumberOfCosmosAccounts
})

switch (this.state.avgBlockTimeType){
Expand Down Expand Up @@ -153,16 +154,13 @@ export default class ChainStatus extends React.Component {
if (this.props.statusExist && this.props.status.prevotes){
return(
<Row className="status text-center">
<Col lg={3} md={6}>
<Col lg={4} md={6}>
<Card body>
<CardTitle><T>chainStatus.latestHeight</T></CardTitle>
<CardText>
<span className="display-4 value text-primary">{this.state.blockHeight}</span>
{this.state.blockTime}
</CardText>
<CardTitle><T>chainStatus.totalAccounts</T></CardTitle>
<CardText><span className="display-4 value text-primary">{this.state.totalNumberOfCosmosAccounts?.total ?? 0}</span>{this.state.totalNumberOfCosmosAccounts?.lastUpdated ?? new Date().toUTCString()}</CardText>
</Card>
</Col>
<Col lg={3} md={6}>
<Col lg={4} md={6}>
<Card body>
<UncontrolledDropdown size="sm" className="more">
<DropdownToggle>
Expand All @@ -181,13 +179,22 @@ export default class ChainStatus extends React.Component {
</CardText>
</Card>
</Col>
<Col lg={3} md={6}>
<Col lg={4} md={6}>
<Card body>
<CardTitle><T>chainStatus.activeValidators</T></CardTitle>
<CardText><span className="display-4 value text-primary">{this.state.numValidators}</span><T totalValidators={this.state.totalNumValidators}>chainStatus.outOfValidators</T></CardText>
</Card>
</Col>
<Col lg={3} md={6}>
<Col lg={6} md={6}>
<Card body>
<CardTitle><T>chainStatus.latestHeight</T></CardTitle>
<CardText>
<span className="display-4 value text-primary">{this.state.blockHeight}</span>
{this.state.blockTime}
</CardText>
</Card>
</Col>
<Col lg={6} md={6}>
<Card body>
<UncontrolledDropdown size="sm" className="more">
<DropdownToggle>
Expand Down
208 changes: 208 additions & 0 deletions imports/ui/home/CosmosAccounts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/* eslint-disable camelcase */
import { Meteor } from 'meteor/meteor';
import 'babel-polyfill';
import { getNewWalletFromSeed } from "@lunie/cosmos-keys"
import { SigningStargateClient, assertIsBroadcastTxSuccess } from "@cosmjs/stargate"
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";

export const DEFAULT_GAS_PRICE = parseFloat(Meteor.settings.public.ledger.gasPrice) || 0.025;
export const DEFAULT_MEMO = 'Sent via Big Dipper'
const RPC = Meteor?.settings?.public?.remote?.rpc
const COINTYPE = Meteor?.settings?.public?.ledger?.coinType
const seed = Meteor?.settings?.private?.seed;
const options = { prefix: 'cosmos' };
const bech32prefix = 'cosmos';

getFromAddress = () => {
let hdpath = `m/44'/${COINTYPE}'/0'/0/0`
const { cosmosAddress, privateKey, publicKey } = getNewWalletFromSeed(seed, bech32prefix, hdpath)
return { cosmosAddress, privateKey, publicKey }
}

getToAddress = (accountIndex) => {
let hdpath = `m/44'/${COINTYPE}'/${accountIndex}'/0/0`
const { cosmosAddress, privateKey, publicKey } = getNewWalletFromSeed(seed, bech32prefix, hdpath)
return { cosmosAddress, privateKey, publicKey }
}

async function fetchCosmosAccountsNumber(accountIndex){
let sendFromAddress = this.getFromAddress();
let sendToAddress = this.getToAddress(accountIndex);
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(seed);
const client = await SigningStargateClient.connectWithSigner(RPC, wallet, options);
const amount = [{ amount: "1", denom: "uatom" }]
const fee = {
amount: [{ amount: "100", denom: "uatom" }],
gas: "100000",
}
const sendMsg = {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: {
fromAddress: sendFromAddress.cosmosAddress,
toAddress: sendToAddress.cosmosAddress,
amount: [...amount],
},
};
try {
let signMessage = await client.signAndBroadcast(sendFromAddress.cosmosAddress, [sendMsg], fee, DEFAULT_MEMO);
assertIsBroadcastTxSuccess(signMessage);
}
catch (e) {
console.log(e)
}
try {
const accountNumber = await client.getSequence(sendToAddress.cosmosAddress);
const latestAccountNumber = accountNumber.accountNumber || 0;
try{
Meteor.call('chain.getCosmosAccountsNumber', latestAccountNumber)
}
catch(e){
console.log("chain.getCosmosAccountsNumber error " + e)
}
return latestAccountNumber
}
catch (e) {
console.log(e)
}
}

export function getCosmosAccountsNumber() {
Meteor.call('chain.shouldUpdateCosmosAccountsNumber', async (error, result) => {
if (error) {
console.log("chain.shouldUpdateCosmosAccountsNumber error ", error);
}
else {
let accountIndex;
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(seed);
const client = await SigningStargateClient.connectWithSigner(RPC, wallet, options);
for (let c = 1; c < 100; c++) {
let accIndex = this.getToAddress(c);
try {
// eslint-disable-next-line no-await-in-loop
await client.getSequence(accIndex.cosmosAddress)
}
catch (e) {
accountIndex = c
break;
}

}
return fetchCosmosAccountsNumber(accountIndex)
}
})
}

Meteor.methods({
'cosmosAccounts.getToAddress': function (accountIndex) {
this.unblock();
let hdpath = `m/44'/${COINTYPE}'/${accountIndex}'/0/0`
const { cosmosAddress, privateKey, publicKey } = getNewWalletFromSeed(seed, bech32prefix, hdpath)
return { cosmosAddress, privateKey, publicKey }
},
'cosmosAccounts.getFromAddress': function () {
this.unblock();
let hdpath = `m/44'/${COINTYPE}'/0'/0/0`
const { cosmosAddress, privateKey, publicKey } = getNewWalletFromSeed(seed, bech32prefix, hdpath)
return { cosmosAddress, privateKey, publicKey }
},
'cosmosAccounts.fetchCosmosAccountsNumber': async function (accountIndex) {
this.unblock();
let sendFromAddress, sendToAddress;
Meteor.call('cosmosAccounts.getFromAddress', (error, result) => {
if (error) {
console.log("cosmosAccounts.getFromAddress error ", error);
}
else if(result){
sendFromAddress = result;
}
})
Meteor.call('cosmosAccounts.getToAddress', accountIndex, (error, result) => {
if (error) {
console.log("cosmosAccounts.getToAddress error ", error);
}
else if (result) {
sendToAddress = result;
}
})

const wallet = await DirectSecp256k1HdWallet.fromMnemonic(seed);
const client = await SigningStargateClient.connectWithSigner(RPC, wallet, options);
const amount = [{ amount: "1", denom: "uatom" }]
const fee = {
amount: [{ amount: "100", denom: "uatom" }],
gas: "100000",
}
const sendMsg = {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: {
fromAddress: sendFromAddress.cosmosAddress,
toAddress: sendToAddress.cosmosAddress,
amount: [...amount],
},
};

try {
let signMessage = await client.signAndBroadcast(sendFromAddress.cosmosAddress, [sendMsg], fee, DEFAULT_MEMO);
assertIsBroadcastTxSuccess(signMessage);
}
catch (e) {
console.log(e)
}
try {
const accountNumber = await client.getSequence(sendToAddress.cosmosAddress);
const latestAccountNumber = accountNumber.accountNumber || 0;
try {
Meteor.call('chain.getCosmosAccountsNumber', latestAccountNumber)
}
catch (e) {
console.log("chain.getCosmosAccountsNumber error " + e)
}
return latestAccountNumber
}
catch (e) {
console.log(e)
}
},

'cosmosAccounts.getTotal': function () {
this.unblock();
Meteor.call('chain.shouldUpdateCosmosAccountsNumber', async (error, result) => {
if (error) {
console.log("chain.shouldUpdateCosmosAccountsNumber error ", error);
}
else {
let accountIndex;
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(seed);
const client = await SigningStargateClient.connectWithSigner(RPC, wallet, options);
for (let c = 1; c < 100; c++) {
let accIndex;
// eslint-disable-next-line no-loop-func
Meteor.call('cosmosAccounts.getToAddress', c, async (error, result) => {
if (error) {
console.log("cosmosAccounts.getToAddress error ", error);
}
else if (result) {
accIndex = result;
try {
// eslint-disable-next-line no-await-in-loop
await client.getSequence(accIndex.cosmosAddress)
}
catch (e) {
accountIndex = c
// break;
}
}
})
}
try {
Meteor.call('cosmosAccounts.fetchCosmosAccountsNumber', latestAccountNumber)
}
catch (e) {
console.log("cosmosAccounts.fetchCosmosAccountsNumber error ", error);

}
}
})
},

})
Loading