diff --git a/config/authz.ts b/config/authz.ts
index 309512f..63e3b94 100644
--- a/config/authz.ts
+++ b/config/authz.ts
@@ -1,4 +1,5 @@
/* eslint-disable eslint-comments/disable-enable-pair */
+/* eslint-disable no-nested-ternary */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable camelcase */
@@ -8,6 +9,14 @@ import { SendAuthorization } from 'cosmjs-types/cosmos/bank/v1beta1/authz'
import { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin'
import type { AuthorizationType } from 'cosmjs-types/cosmos/staking/v1beta1/authz'
import { StakeAuthorization, StakeAuthorization_Validators } from 'cosmjs-types/cosmos/staking/v1beta1/authz'
+import {
+ AcceptedMessageKeysFilter,
+ AllowAllMessagesFilter,
+ CombinedLimit,
+ ContractExecutionAuthorization,
+ MaxCallsLimit,
+ MaxFundsLimit,
+} from 'cosmjs-types/cosmwasm/wasm/v1/authz'
import type { AuthorizationMode, GenericAuthorizationType, GrantAuthorizationType } from 'pages/authz/grant'
export interface Msg {
@@ -82,6 +91,82 @@ export function AuthzSendGrantMsg(
}
}
+export function AuthzExecuteContractGrantMsg(
+ granter: string,
+ grantee: string,
+ contract: string,
+ expiration: number,
+ callsRemaining?: number,
+ amounts?: Coin[],
+ allowedMessages?: string[],
+): Msg {
+ const sendAuthValue = ContractExecutionAuthorization.encode(
+ ContractExecutionAuthorization.fromPartial({
+ grants: [
+ {
+ contract,
+ filter: {
+ typeUrl: allowedMessages
+ ? '/cosmwasm.wasm.v1.AcceptedMessageKeysFilter'
+ : '/cosmwasm.wasm.v1.AllowAllMessagesFilter',
+ value: allowedMessages
+ ? AcceptedMessageKeysFilter.encode({ keys: allowedMessages }).finish()
+ : AllowAllMessagesFilter.encode({}).finish(),
+ },
+ limit:
+ callsRemaining || amounts
+ ? {
+ typeUrl:
+ callsRemaining && amounts
+ ? '/cosmwasm.wasm.v1.CombinedLimit'
+ : callsRemaining
+ ? '/cosmwasm.wasm.v1.MaxCallsLimit'
+ : '/cosmwasm.wasm.v1.MaxFundsLimit',
+ value:
+ callsRemaining && amounts
+ ? CombinedLimit.encode({
+ callsRemaining: BigInt(callsRemaining),
+ amounts,
+ }).finish()
+ : callsRemaining
+ ? MaxCallsLimit.encode({
+ remaining: BigInt(callsRemaining),
+ }).finish()
+ : MaxFundsLimit.encode({
+ amounts: amounts || [],
+ }).finish(),
+ }
+ : {
+ // limit: undefined is not accepted
+ typeUrl: '/cosmwasm.wasm.v1.MaxCallsLimit',
+ value: MaxCallsLimit.encode({
+ remaining: BigInt(100000),
+ }).finish(),
+ },
+ },
+ ],
+ }),
+ ).finish()
+
+ const grantValue = MsgGrant.fromPartial({
+ grant: {
+ authorization: {
+ typeUrl: '/cosmwasm.wasm.v1.ContractExecutionAuthorization',
+ value: sendAuthValue,
+ },
+ // TODO: fix expiration issue
+ expiration: expiration ? { seconds: BigInt(expiration) } : undefined,
+ },
+ grantee,
+ granter,
+ })
+
+ return {
+ typeUrl: msgAuthzGrantTypeUrl,
+ value: grantValue,
+ }
+}
+
export function AuthzGenericGrantMsg(granter: string, grantee: string, typeURL: string, expiration: number): Msg {
return {
typeUrl: msgAuthzGrantTypeUrl,
diff --git a/pages/authz/grant.tsx b/pages/authz/grant.tsx
index a93506f..6355a94 100644
--- a/pages/authz/grant.tsx
+++ b/pages/authz/grant.tsx
@@ -6,19 +6,19 @@
/* eslint-disable tailwindcss/classnames-order */
/* eslint-disable react/button-has-type */
-import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'
+import { coins, GasPrice, SigningStargateClient } from '@cosmjs/stargate'
import { Alert } from 'components/Alert'
import { Conditional } from 'components/Conditional'
import { ContractPageHeader } from 'components/ContractPageHeader'
import { FormControl } from 'components/FormControl'
-import { NumberInput, TextInput } from 'components/forms/FormInput'
+import { AddressInput, NumberInput, TextInput } from 'components/forms/FormInput'
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
import { InputDateTime } from 'components/InputDateTime'
import { LinkTabs } from 'components/LinkTabs'
import { authzLinkTabs } from 'components/LinkTabs.data'
import { getConfig } from 'config'
import type { Msg } from 'config/authz'
-import { AuthzGenericGrantMsg, AuthzSendGrantMsg } from 'config/authz'
+import { AuthzExecuteContractGrantMsg, AuthzGenericGrantMsg, AuthzSendGrantMsg } from 'config/authz'
import { useGlobalSettings } from 'contexts/globalSettings'
import type { NextPage } from 'next'
import { NextSeo } from 'next-seo'
@@ -74,6 +74,22 @@ const Grant: NextPage = () => {
subtitle: 'The spend limit',
})
+ const maxFundsLimitDenomState = useInputState({
+ id: 'max-funds-limit-denom',
+ name: 'maxFundsLimitDenom',
+ title: 'Max Funds Limit Denom',
+ placeholder: `ustars`,
+ subtitle: 'The denom for max funds limit',
+ })
+
+ const maxFundsLimitState = useNumberInputState({
+ id: 'max-funds-limit',
+ name: 'maxFundsLimit',
+ title: 'Max Funds Limit',
+ placeholder: `1000000`,
+ subtitle: 'The max funds limit for contract execution (leave blank for no limit)',
+ })
+
const allowListState = useInputState({
id: 'allow-list',
name: 'allowList',
@@ -82,6 +98,30 @@ const Grant: NextPage = () => {
subtitle: 'Comma separated list of addresses to allow transactions to',
})
+ const contractAddressState = useInputState({
+ id: 'contract-address',
+ name: 'contractAddress',
+ title: 'Contract Address',
+ placeholder: `stars1...`,
+ subtitle: 'The contract address to authorize execution on',
+ })
+
+ const allowedMessageKeysState = useInputState({
+ id: 'allowed-message-keys',
+ name: 'allowedMessageKeys',
+ title: 'Allowed Message Keys',
+ placeholder: `mint_to, burn, transfer`,
+ subtitle: 'Comma separated list of allowed message keys (leave blank to allow all)',
+ })
+
+ const callsRemainingState = useNumberInputState({
+ id: 'calls-remaining',
+ name: 'callsRemaining',
+ title: 'Calls Remaining',
+ placeholder: `10`,
+ subtitle: 'The allowed number of contract execution calls (leave blank for no limit)',
+ })
+
const messageToSign = () => {
if (authType === 'Generic') {
if (genericAuthType === 'MsgSend') {
@@ -117,6 +157,16 @@ const Grant: NextPage = () => {
(expiration?.getTime() as number) / 1000 || 0,
allowListState.value ? allowListState.value.split(',').map((address) => address.trim()) : [],
)
+ } else if (authType === 'Execute Smart Contract') {
+ return AuthzExecuteContractGrantMsg(
+ wallet.address || '',
+ granteeAddressState.value,
+ contractAddressState.value,
+ (expiration?.getTime() as number) / 1000 || 0,
+ callsRemainingState.value ? callsRemainingState.value : undefined,
+ maxFundsLimitState.value > 0 ? coins(maxFundsLimitState.value, maxFundsLimitDenomState.value) : undefined,
+ allowedMessageKeysState.value ? allowedMessageKeysState.value.split(',').map((key) => key.trim()) : undefined,
+ )
}
}
const handleSendMessage = async () => {
@@ -156,9 +206,7 @@ const Grant: NextPage = () => {
>
-
+
@@ -199,6 +247,15 @@ const Grant: NextPage = () => {
{/* */}
+
+
+
+
+
+
+
+
+