diff --git a/src/components/AboutLedger.js b/src/components/AboutLedger.js
index e07eef18..67e72f23 100644
--- a/src/components/AboutLedger.js
+++ b/src/components/AboutLedger.js
@@ -13,7 +13,7 @@ function AboutLedger(props) {
const { daemon_name, chain_id, node_home, codebase } = network.chain.data
const { git_repo, binaries } = codebase || {}
- const address = validator?.restake.address || GrantAddress
+ const address = validator?.restake?.address || GrantAddress
const show = props.show || (aboutParam == 'ledger' && network.authzSupport)
diff --git a/src/components/Delegations.js b/src/components/Delegations.js
index 15741e32..9caa655f 100644
--- a/src/components/Delegations.js
+++ b/src/components/Delegations.js
@@ -35,8 +35,8 @@ class Delegations extends React.Component {
}
async componentDidMount() {
- const isNanoLedger = this.props.wallet?.getIsNanoLedger();
- this.setState({ isNanoLedger: isNanoLedger });
+ const ledgerAuthzSupport = this.props.wallet?.ledgerAuthzSupport();
+ this.setState({ ledgerAuthzSupport });
this.refresh(true);
if (this.props.validator) {
@@ -52,9 +52,9 @@ class Delegations extends React.Component {
if ((this.props.network !== prevProps.network && !this.props.address)
|| (this.props.address !== prevProps.address)) {
this.clearRefreshInterval()
- const isNanoLedger = this.props.wallet?.getIsNanoLedger();
+ const ledgerAuthzSupport = this.props.wallet?.ledgerAuthzSupport();
this.setState({
- isNanoLedger: isNanoLedger,
+ ledgerAuthzSupport: ledgerAuthzSupport,
delegations: undefined,
rewards: undefined,
commission: {},
@@ -297,7 +297,7 @@ class Delegations extends React.Component {
}
restakePossible() {
- return this.props.address && !this.state.isNanoLedger && this.authzSupport();
+ return this.props.address && this.state.ledgerAuthzSupport && this.authzSupport();
}
totalRewards(validators) {
@@ -413,7 +413,7 @@ class Delegations extends React.Component {
)}
{this.authzSupport() &&
this.props.operators.length > 0 &&
- this.state.isNanoLedger && (
+ !this.state.ledgerAuthzSupport && (
<>
- {address && !isNanoLedger ? 'New Grant' : 'CLI/Ledger instructions'}
+ {address && !!ledgerAuthzSupport ? 'New Grant' : 'CLI/Ledger instructions'}
@@ -156,7 +156,7 @@ function GrantModal(props) {
{error}
}
- {!address || isNanoLedger && (
+ {!address || !ledgerAuthzSupport && (
<>
Enter your grant details to generate the relevant CLI command.
>
@@ -214,7 +214,7 @@ function GrantModal(props) {
>
)}
Incorrect use of Authz grants can be as dangerous as giving away your mnemonic. Make sure you trust the Grantee address and understand the permissions you are granting.
- {address && !isNanoLedger && (
+ {address && !!ledgerAuthzSupport && (
{
if(!grants) return
@@ -204,7 +204,7 @@ function Grants(props) {
{!props.grantQuerySupport && (
This network doesn't fully support this feature just yet. Save addresses to see them here.
)}
- {props.grantQuerySupport && isNanoLedger && (
+ {props.grantQuerySupport && !ledgerAuthzSupport && (
setShowModal(true)}>
- {address && !isNanoLedger ? 'New Grant' : 'CLI/Ledger Instructions'}
+ {address && !!ledgerAuthzSupport ? 'New Grant' : 'CLI/Ledger Instructions'}
diff --git a/src/components/RevokeGrant.js b/src/components/RevokeGrant.js
index 5cec83d7..61626e72 100644
--- a/src/components/RevokeGrant.js
+++ b/src/components/RevokeGrant.js
@@ -51,7 +51,7 @@ function RevokeGrant(props) {
}
function disabled(){
- return props.disabled || !wallet?.hasPermission(address, 'Revoke') || wallet?.getIsNanoLedger()
+ return props.disabled || !wallet?.hasPermission(address, 'Revoke') || !wallet?.ledgerAuthzSupport()
}
if(props.button){
diff --git a/src/converters/authz.mjs b/src/converters/authz.mjs
new file mode 100644
index 00000000..05444d74
--- /dev/null
+++ b/src/converters/authz.mjs
@@ -0,0 +1,126 @@
+import moment from 'moment'
+import { GenericAuthorization } from "cosmjs-types/cosmos/authz/v1beta1/authz";
+import { StakeAuthorization } from "cosmjs-types/cosmos/staking/v1beta1/authz";
+
+function createAuthzAuthorizationAminoConverter(){
+ return {
+ "/cosmos.authz.v1beta1.GenericAuthorization": {
+ aminoType: "cosmos-sdk/GenericAuthorization",
+ toAmino: (value) => GenericAuthorization.decode(value),
+ fromAmino: ({ msg }) => (GenericAuthorization.encode(GenericAuthorization.fromPartial({
+ msg
+ })).finish())
+ },
+ "/cosmos.staking.v1beta1.StakeAuthorization": {
+ aminoType: "cosmos-sdk/StakeAuthorization",
+ toAmino: (value) => {
+ const { allowList, maxTokens, authorizationType } = StakeAuthorization.decode(value)
+ return {
+ Validators: {
+ type: "cosmos-sdk/StakeAuthorization/AllowList",
+ value: {
+ allow_list: allowList
+ }
+ },
+ max_tokens: maxTokens,
+ authorization_type: authorizationType
+ }
+ },
+ fromAmino: ({ allow_list, max_tokens, authorization_type }) => (StakeAuthorization.encode(StakeAuthorization.fromPartial({
+ allowList: allow_list,
+ maxTokens: max_tokens,
+ authorizationType: authorization_type
+ })).finish())
+ }
+ }
+}
+
+const dateConverter = {
+ toAmino(date){
+ return moment(date.seconds.toNumber() * 1000).utc().format()
+ },
+ fromAmino(date){
+ return {
+ seconds: moment(date).unix(),
+ nanos: 0
+ }
+ }
+}
+
+export function createAuthzAminoConverters() {
+ const grantConverter = createAuthzAuthorizationAminoConverter()
+ return {
+ "/cosmos.authz.v1beta1.MsgGrant": {
+ aminoType: "cosmos-sdk/MsgGrant",
+ toAmino: ({ granter, grantee, grant }) => {
+ converter = grantConverter[grant.authorization.typeUrl]
+ return {
+ granter,
+ grantee,
+ grant: {
+ authorization: {
+ type: converter.aminoType,
+ value: converter.toAmino(grant.authorization.value)
+ },
+ expiration: dateConverter.toAmino(grant.expiration)
+ }
+ }
+ },
+ fromAmino: ({ granter, grantee, grant }) => {
+ protoType = Object.keys(grantConverter).find(type => grantConverter[type].aminoType === grant.authorization.type)
+ converter = grantConverter[protoType]
+ return {
+ granter,
+ grantee,
+ grant: {
+ authorization: {
+ typeUrl: protoType,
+ value: converter.fromAmino(grant.authorization.value)
+ },
+ expiration: dateConverter.fromAmino(grant.expiration)
+ }
+ }
+ },
+ },
+ "/cosmos.authz.v1beta1.MsgRevoke": {
+ aminoType: "cosmos-sdk/MsgRevoke",
+ toAmino: ({ granter, grantee, msgTypeUrl }) => ({
+ granter,
+ grantee,
+ msg_type_url: msgTypeUrl
+ }),
+ fromAmino: ({ granter, grantee, msg_type_url }) => ({
+ granter,
+ grantee,
+ msgTypeUrl: msg_type_url
+ }),
+ },
+ };
+}
+
+export function createAuthzExecAminoConverters(registry, aminoTypes) {
+ return {
+ "/cosmos.authz.v1beta1.MsgExec": {
+ aminoType: "cosmos-sdk/MsgExec",
+ toAmino: ({ grantee, msgs }) => ({
+ grantee,
+ msgs: msgs.map(({typeUrl, value}) => {
+ const msgType = registry.lookupType(typeUrl)
+ return aminoTypes.toAmino({ typeUrl, value: msgType.decode(value) })
+ })
+ }),
+ fromAmino: ({ grantee, msgs }) => ({
+ grantee,
+ msgs: msgs.map(({type, value}) => {
+ const proto = aminoTypes.fromAmino({ type, value })
+ const typeUrl = proto.typeUrl
+ const msgType = registry.lookupType(typeUrl)
+ return {
+ typeUrl,
+ value: msgType.encode(msgType.fromPartial(proto.value)).finish()
+ }
+ })
+ }),
+ },
+ };
+}
diff --git a/src/networks.json b/src/networks.json
index a726ca59..653c3b7c 100644
--- a/src/networks.json
+++ b/src/networks.json
@@ -232,5 +232,9 @@
"average": 0.025,
"high": 0.04
}
+ },
+ {
+ "name": "pulsar",
+ "testnet": true
}
]
diff --git a/src/utils/Chain.mjs b/src/utils/Chain.mjs
index ff6c69be..23164b9b 100644
--- a/src/utils/Chain.mjs
+++ b/src/utils/Chain.mjs
@@ -8,9 +8,10 @@ const Chain = (data) => {
prettyName: data.prettyName || data.pretty_name,
chainId: data.chainId || data.chain_id,
prefix: data.prefix || data.bech32_prefix,
- slip44: data.slip44 || data.slip44 || 118,
+ slip44: data.slip44 || 118,
estimatedApr: data.params?.calculated_apr,
authzSupport: data.authzSupport ?? data.params?.authz,
+ ledgerAuthzSupport: data.ledgerAuthzSupport ?? false,
denom: data.denom || baseAsset?.base?.denom,
display: data.display || baseAsset?.display?.denom,
symbol: data.symbol || baseAsset?.symbol,
diff --git a/src/utils/Network.mjs b/src/utils/Network.mjs
index 30fc4a5a..b9236d36 100644
--- a/src/utils/Network.mjs
+++ b/src/utils/Network.mjs
@@ -86,6 +86,7 @@ class Network {
this.estimatedApr = this.chain.estimatedApr
this.apyEnabled = data.apyEnabled !== false && !!this.estimatedApr && this.estimatedApr > 0
this.authzSupport = this.chain.authzSupport
+ this.ledgerAuthzSupport = this.chain.ledgerAuthzSupport
this.defaultGasPrice = this.decimals && format(bignumber(multiply(0.000000025, pow(10, this.decimals))), { notation: 'fixed', precision: 4}) + this.denom
this.gasPrice = this.data.gasPrice || this.defaultGasPrice
if(this.gasPrice){
diff --git a/src/utils/SigningClient.mjs b/src/utils/SigningClient.mjs
index a589ba57..d58922b1 100644
--- a/src/utils/SigningClient.mjs
+++ b/src/utils/SigningClient.mjs
@@ -8,7 +8,6 @@ import {
assertIsDeliverTxSuccess,
GasPrice,
AminoTypes,
- createAuthzAminoConverters,
createBankAminoConverters,
createDistributionAminoConverters,
createFreegrantAminoConverters,
@@ -25,6 +24,7 @@ import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing.js";
import { AuthInfo, Fee, TxBody, TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx.js";
import { coin } from './Helpers.mjs'
+import { createAuthzAminoConverters, createAuthzExecAminoConverters } from '../converters/Authz.mjs'
function SigningClient(network, signer) {
@@ -32,7 +32,7 @@ function SigningClient(network, signer) {
const { restUrl, gasModifier: defaultGasModifier, slip44: coinType, chainId } = network
const registry = new Registry(defaultStargateTypes);
- const aminoTypes = new AminoTypes({
+ const defaultConverters = {
...createAuthzAminoConverters(),
...createBankAminoConverters(),
...createDistributionAminoConverters(),
@@ -40,7 +40,9 @@ function SigningClient(network, signer) {
...createStakingAminoConverters(network.prefix),
...createIbcAminoConverters(),
...createFreegrantAminoConverters(),
- })
+ }
+ let aminoTypes = new AminoTypes(defaultConverters)
+ aminoTypes = new AminoTypes({...defaultConverters, ...createAuthzExecAminoConverters(registry, aminoTypes)})
function getAccount(address) {
return axios
@@ -99,8 +101,7 @@ function SigningClient(network, signer) {
const amount = ceil(bignumber(multiply(bignumber(gasPriceAmount.toString()), bignumber(gasLimit.toString()))));
return {
amount: [coin(amount, denom)],
- gas: gasLimit.toString(),
- gasLimit: gasLimit.toString()
+ gas: gasLimit.toString()
};
}
@@ -184,8 +185,8 @@ function SigningClient(network, signer) {
const txBodyBytes = makeBodyBytes(messages, memo)
let aminoMsgs
try {
- aminoMsgs = messages.map(el => aminoTypes.toAmino(el))
- } catch { }
+ aminoMsgs = convertToAmino(messages)
+ } catch (e) { console.log(e) }
if(aminoMsgs && signer.signAmino){
// Sign as amino if possible for Ledger and Keplr support
const signDoc = makeAminoSignDoc(aminoMsgs, fee, chainId, memo, accountNumber, sequence);
@@ -237,6 +238,15 @@ function SigningClient(network, signer) {
}
}
+ function convertToAmino(messages){
+ return messages.map(message => {
+ if(message.typeUrl.startsWith('/cosmos.authz') && !network.ledgerAuthzSupport){
+ throw new Error('This chain does not support amino conversion for Authz messages')
+ }
+ return aminoTypes.toAmino(message)
+ })
+ }
+
function parseTxResult(result){
return {
code: result.code,
diff --git a/src/utils/Wallet.mjs b/src/utils/Wallet.mjs
index 8fd9fed5..48d23353 100644
--- a/src/utils/Wallet.mjs
+++ b/src/utils/Wallet.mjs
@@ -30,7 +30,7 @@ class Wallet {
hasPermission(address, action){
if(address === this.address) return true
- if(this.getIsNanoLedger()) return false // Ledger Authz disabled for now
+ if(!this.ledgerAuthzSupport()) return false
let message = messageTypes.find(el => {
return el.split('.').slice(-1)[0].replace('Msg', '') === action
@@ -43,6 +43,12 @@ class Wallet {
})
}
+ ledgerAuthzSupport(){
+ if(!this.getIsNanoLedger()) return true
+
+ return this.network.ledgerAuthzSupport
+ }
+
async getAddress(){
this.address = this.address || await this.getAccountAddress()