Skip to content

Commit

Permalink
Merge pull request #597 from eco-stake/ledger-support
Browse files Browse the repository at this point in the history
Authz amino support
  • Loading branch information
tombeynon committed Sep 21, 2022
2 parents 87d4fdd + eae15b6 commit 97e7c5a
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/components/AboutLedger.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 || <kbd>GrantAddress</kbd>
const address = validator?.restake?.address || <kbd>GrantAddress</kbd>

const show = props.show || (aboutParam == 'ledger' && network.authzSupport)

Expand Down
12 changes: 6 additions & 6 deletions src/components/Delegations.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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: {},
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -413,7 +413,7 @@ class Delegations extends React.Component {
)}
{this.authzSupport() &&
this.props.operators.length > 0 &&
this.state.isNanoLedger && (
!this.state.ledgerAuthzSupport && (
<>
<AlertMessage
variant="warning"
Expand Down
14 changes: 7 additions & 7 deletions src/components/GrantModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import { buildExecMessage } from '../utils/Helpers.mjs';

function GrantModal(props) {
const { show, network, address, wallet } = props
const isNanoLedger = wallet?.getIsNanoLedger()
const ledgerAuthzSupport = wallet?.ledgerAuthzSupport()
const defaultExpiry = moment().add(1, 'year')
const [loading, setLoading] = useState(false);
const [error, setError] = useState()
const [state, setState] = useState({ maxTokensValue: '', expiryDateValue: defaultExpiry.format('YYYY-MM-DD') });
const [showLedger, setShowLedger] = useState(isNanoLedger)
const [showLedger, setShowLedger] = useState(!ledgerAuthzSupport)

const { daemon_name, chain_id } = network.chain.data

Expand All @@ -37,7 +37,7 @@ function GrantModal(props) {
messageTypeValue: messageTypes[0],
customMessageTypeValue: '',
})
setShowLedger(wallet?.getIsNanoLedger())
setShowLedger(!ledgerAuthzSupport)
setError(null)
}, [address])

Expand All @@ -56,7 +56,7 @@ function GrantModal(props) {

function handleSubmit(event) {
event.preventDefault()
if(!address || isNanoLedger || !valid()) return
if(!address || !ledgerAuthzSupport || !valid()) return

showLoading(true)
const expiry = moment(state.expiryDateValue)
Expand Down Expand Up @@ -147,7 +147,7 @@ function GrantModal(props) {
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title className="text-truncate pe-4">
{address && !isNanoLedger ? 'New Grant' : 'CLI/Ledger instructions'}
{address && !!ledgerAuthzSupport ? 'New Grant' : 'CLI/Ledger instructions'}
</Modal.Title>
</Modal.Header>
<Modal.Body>
Expand All @@ -156,7 +156,7 @@ function GrantModal(props) {
{error}
</AlertMessage>
}
{!address || isNanoLedger && (
{!address || !ledgerAuthzSupport && (
<>
<p>Enter your grant details to generate the relevant CLI command.</p>
</>
Expand Down Expand Up @@ -214,7 +214,7 @@ function GrantModal(props) {
</>
)}
<p><strong>Incorrect use of Authz grants can be as dangerous as giving away your mnemonic.</strong> Make sure you trust the Grantee address and understand the permissions you are granting.</p>
{address && !isNanoLedger && (
{address && !!ledgerAuthzSupport && (
<p className="text-end">
<Button
variant="link"
Expand Down
6 changes: 3 additions & 3 deletions src/components/Grants.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function Grants(props) {
const [filter, setFilter] = useState({keywords: '', group: 'granter'})
const [results, setResults] = useState([])

const isNanoLedger = props.wallet?.getIsNanoLedger()
const ledgerAuthzSupport = props.wallet?.ledgerAuthzSupport()

useEffect(() => {
if(!grants) return
Expand Down Expand Up @@ -204,7 +204,7 @@ function Grants(props) {
{!props.grantQuerySupport && (
<AlertMessage variant="warning">This network doesn't fully support this feature just yet. <span role="button" className="text-decoration-underline" onClick={props.showFavouriteAddresses}>Save addresses</span> to see them here.</AlertMessage>
)}
{props.grantQuerySupport && isNanoLedger && (
{props.grantQuerySupport && !ledgerAuthzSupport && (
<AlertMessage
variant="warning"
dismissible={false}
Expand Down Expand Up @@ -260,7 +260,7 @@ function Grants(props) {
</div>
<div className="flex-fill d-flex justify-content-end">
<Button variant="primary" disabled={!wallet?.hasPermission(address, 'Grant')} onClick={() => setShowModal(true)}>
{address && !isNanoLedger ? 'New Grant' : 'CLI/Ledger Instructions'}
{address && !!ledgerAuthzSupport ? 'New Grant' : 'CLI/Ledger Instructions'}
</Button>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/RevokeGrant.js
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down
126 changes: 126 additions & 0 deletions src/converters/authz.mjs
Original file line number Diff line number Diff line change
@@ -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()
}
})
}),
},
};
}
4 changes: 4 additions & 0 deletions src/networks.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,9 @@
"average": 0.025,
"high": 0.04
}
},
{
"name": "pulsar",
"testnet": true
}
]
3 changes: 2 additions & 1 deletion src/utils/Chain.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/utils/Network.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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){
Expand Down
24 changes: 17 additions & 7 deletions src/utils/SigningClient.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
assertIsDeliverTxSuccess,
GasPrice,
AminoTypes,
createAuthzAminoConverters,
createBankAminoConverters,
createDistributionAminoConverters,
createFreegrantAminoConverters,
Expand All @@ -25,22 +24,25 @@ 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) {

const defaultGasPrice = network.gasPricePrefer || network.gasPrice
const { restUrl, gasModifier: defaultGasModifier, slip44: coinType, chainId } = network

const registry = new Registry(defaultStargateTypes);
const aminoTypes = new AminoTypes({
const defaultConverters = {
...createAuthzAminoConverters(),
...createBankAminoConverters(),
...createDistributionAminoConverters(),
...createGovAminoConverters(),
...createStakingAminoConverters(network.prefix),
...createIbcAminoConverters(),
...createFreegrantAminoConverters(),
})
}
let aminoTypes = new AminoTypes(defaultConverters)
aminoTypes = new AminoTypes({...defaultConverters, ...createAuthzExecAminoConverters(registry, aminoTypes)})

function getAccount(address) {
return axios
Expand Down Expand Up @@ -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()
};
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion src/utils/Wallet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()

Expand Down

0 comments on commit 97e7c5a

Please sign in to comment.