Merge pull request #386 from public-awesome/contract-upload-authorization
Enable contract uploads with authorization
This commit is contained in:
commit
a96c80462d
@ -298,17 +298,16 @@ export const Sidebar = () => {
|
|||||||
>
|
>
|
||||||
<Link href="/contracts/royaltyRegistry/">Royalty Registry</Link>
|
<Link href="/contracts/royaltyRegistry/">Royalty Registry</Link>
|
||||||
</li>
|
</li>
|
||||||
<Conditional test={NETWORK === 'testnet'}>
|
|
||||||
<li
|
<li
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
|
'text-lg font-bold hover:text-white hover:bg-stargaze-80 rounded',
|
||||||
router.asPath.includes('/contracts/upload/') ? 'text-white' : 'text-gray',
|
router.asPath.includes('/contracts/upload/') ? 'text-white' : 'text-gray',
|
||||||
)}
|
)}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<Link href="/contracts/upload/">Upload Contract</Link>
|
<Link href="/contracts/upload/">Upload Contract</Link>
|
||||||
</li>
|
</li>
|
||||||
</Conditional>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
18257
package-lock.json
generated
Normal file
18257
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@ -18,17 +18,16 @@
|
|||||||
"@cosmjs/math": "0.32.3",
|
"@cosmjs/math": "0.32.3",
|
||||||
"@cosmjs/proto-signing": "0.32.3",
|
"@cosmjs/proto-signing": "0.32.3",
|
||||||
"@cosmjs/stargate": "0.32.3",
|
"@cosmjs/stargate": "0.32.3",
|
||||||
"cosmjs-types": "0.9.0",
|
|
||||||
"@cosmos-kit/keplr": "2.8.0",
|
"@cosmos-kit/keplr": "2.8.0",
|
||||||
"@cosmos-kit/leap": "2.8.0",
|
"@cosmos-kit/leap": "2.8.0",
|
||||||
"@cosmos-kit/leap-metamask-cosmos-snap": "0.8.0",
|
"@cosmos-kit/leap-metamask-cosmos-snap": "0.8.0",
|
||||||
"@cosmos-kit/react": "2.12.0",
|
"@cosmos-kit/react": "2.12.0",
|
||||||
"@interchain-ui/react": "1.23.11",
|
|
||||||
"crypto-js": "4.1.1",
|
|
||||||
"@types/crypto-js": "4.2.1",
|
|
||||||
"@fontsource/jetbrains-mono": "^4",
|
"@fontsource/jetbrains-mono": "^4",
|
||||||
"@fontsource/roboto": "^4",
|
"@fontsource/roboto": "^4",
|
||||||
"merkletreejs": "0.3.11",
|
"@headlessui/react": "1.6.0",
|
||||||
|
"@headlessui/tailwindcss": "0.2.0",
|
||||||
|
"@heroicons/react": "2.0.18",
|
||||||
|
"@interchain-ui/react": "1.23.11",
|
||||||
"@leapwallet/cosmos-snap-provider": "0.1.24",
|
"@leapwallet/cosmos-snap-provider": "0.1.24",
|
||||||
"@pinata/sdk": "^1.1.26",
|
"@pinata/sdk": "^1.1.26",
|
||||||
"@popperjs/core": "^2",
|
"@popperjs/core": "^2",
|
||||||
@ -36,20 +35,23 @@
|
|||||||
"@tailwindcss/forms": "^0",
|
"@tailwindcss/forms": "^0",
|
||||||
"@tailwindcss/line-clamp": "^0",
|
"@tailwindcss/line-clamp": "^0",
|
||||||
"@typeform/embed-react": "2.21.0",
|
"@typeform/embed-react": "2.21.0",
|
||||||
|
"@types/crypto-js": "4.2.1",
|
||||||
|
"@types/pako": "^2.0.3",
|
||||||
"axios": "^0",
|
"axios": "^0",
|
||||||
"chain-registry": "^1.20.0",
|
"chain-registry": "^1.20.0",
|
||||||
"clsx": "^1",
|
"clsx": "^1",
|
||||||
"compare-versions": "^4",
|
"compare-versions": "^4",
|
||||||
|
"cosmjs-types": "0.9.0",
|
||||||
|
"crypto-js": "4.1.1",
|
||||||
"daisyui": "^2.19.0",
|
"daisyui": "^2.19.0",
|
||||||
"html-to-image": "1.11.11",
|
"html-to-image": "1.11.11",
|
||||||
"@headlessui/react": "1.6.0",
|
|
||||||
"@headlessui/tailwindcss": "0.2.0",
|
|
||||||
"@heroicons/react": "2.0.18",
|
|
||||||
"jscrypto": "^1.0.3",
|
"jscrypto": "^1.0.3",
|
||||||
"match-sorter": "^6",
|
"match-sorter": "^6",
|
||||||
|
"merkletreejs": "0.3.11",
|
||||||
"next": "^12",
|
"next": "^12",
|
||||||
"next-seo": "^4",
|
"next-seo": "^4",
|
||||||
"nft.storage": "^6.3.0",
|
"nft.storage": "^6.3.0",
|
||||||
|
"pako": "^2.0.2",
|
||||||
"qrcode.react": "3.1.0",
|
"qrcode.react": "3.1.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-datetime-picker": "^3",
|
"react-datetime-picker": "^3",
|
||||||
@ -76,8 +78,8 @@
|
|||||||
"lint-staged": "^12",
|
"lint-staged": "^12",
|
||||||
"object-sizeof": "^1.6.0",
|
"object-sizeof": "^1.6.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3",
|
|
||||||
"tailwind-merge": "1.14.0",
|
"tailwind-merge": "1.14.0",
|
||||||
|
"tailwindcss": "^3",
|
||||||
"typescript": "^4"
|
"typescript": "^4"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
@ -1,14 +1,28 @@
|
|||||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
|
||||||
|
import type { EncodeObject } from '@cosmjs/proto-signing'
|
||||||
|
import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Alert } from 'components/Alert'
|
import { Alert } from 'components/Alert'
|
||||||
import { Button } from 'components/Button'
|
import { Button } from 'components/Button'
|
||||||
import { Conditional } from 'components/Conditional'
|
import { Conditional } from 'components/Conditional'
|
||||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||||
|
import { AddressList } from 'components/forms/AddressList'
|
||||||
|
import { useAddressListState } from 'components/forms/AddressList.hooks'
|
||||||
|
import { TextInput } from 'components/forms/FormInput'
|
||||||
|
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||||
import { JsonPreview } from 'components/JsonPreview'
|
import { JsonPreview } from 'components/JsonPreview'
|
||||||
|
import { getConfig } from 'config'
|
||||||
|
import { MsgExec } from 'cosmjs-types/cosmos/authz/v1beta1/tx'
|
||||||
|
import { MsgStoreCode } from 'cosmjs-types/cosmwasm/wasm/v1/tx'
|
||||||
|
import { AccessConfig, AccessType } from 'cosmjs-types/cosmwasm/wasm/v1/types'
|
||||||
import type { NextPage } from 'next'
|
import type { NextPage } from 'next'
|
||||||
import { NextSeo } from 'next-seo'
|
import { NextSeo } from 'next-seo'
|
||||||
|
import pako from 'pako'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { FaAsterisk } from 'react-icons/fa'
|
import { FaAsterisk } from 'react-icons/fa'
|
||||||
@ -23,9 +37,37 @@ const UploadContract: NextPage = () => {
|
|||||||
const [transactionResult, setTransactionResult] = useState<any>()
|
const [transactionResult, setTransactionResult] = useState<any>()
|
||||||
const [wasmFile, setWasmFile] = useState<File | null>(null)
|
const [wasmFile, setWasmFile] = useState<File | null>(null)
|
||||||
const [wasmByteArray, setWasmByteArray] = useState<Uint8Array | null>(null)
|
const [wasmByteArray, setWasmByteArray] = useState<Uint8Array | null>(null)
|
||||||
|
const [accessType, setAccessType] = useState<
|
||||||
|
'ACCESS_TYPE_UNSPECIFIED' | 'ACCESS_TYPE_EVERYBODY' | 'ACCESS_TYPE_ANY_OF_ADDRESSES' | 'ACCESS_TYPE_NOBODY'
|
||||||
|
>('ACCESS_TYPE_UNSPECIFIED')
|
||||||
|
const [accessConfig, setAccessConfig] = useState<AccessConfig | undefined>(undefined)
|
||||||
|
const [isAuthzUpload, setIsAuthzUpload] = useState(true)
|
||||||
|
|
||||||
|
const granterAddressState = useInputState({
|
||||||
|
id: 'address',
|
||||||
|
name: 'Granter Address',
|
||||||
|
title: 'The address that granted the authorization for contract upload',
|
||||||
|
defaultValue: '',
|
||||||
|
placeholder: 'stars1...',
|
||||||
|
})
|
||||||
|
|
||||||
|
const memoState = useInputState({
|
||||||
|
id: 'memo',
|
||||||
|
name: 'Memo',
|
||||||
|
title: 'The memo to attach to the transaction',
|
||||||
|
defaultValue: '',
|
||||||
|
placeholder: 'My first contract',
|
||||||
|
})
|
||||||
|
|
||||||
|
const permittedAddressListState = useAddressListState()
|
||||||
|
|
||||||
const inputFile = useRef<HTMLInputElement>(null)
|
const inputFile = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
interface MsgExecAllowanceEncodeObject extends EncodeObject {
|
||||||
|
readonly typeUrl: '/cosmos.authz.v1beta1.MsgExec'
|
||||||
|
readonly value: Partial<MsgExec>
|
||||||
|
}
|
||||||
|
|
||||||
const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!e.target.files) return
|
if (!e.target.files) return
|
||||||
setWasmFile(e.target.files[0])
|
setWasmFile(e.target.files[0])
|
||||||
@ -51,20 +93,70 @@ const UploadContract: NextPage = () => {
|
|||||||
try {
|
try {
|
||||||
if (!wallet.isWalletConnected) return toast.error('Please connect your wallet.')
|
if (!wallet.isWalletConnected) return toast.error('Please connect your wallet.')
|
||||||
if (!wasmFile || !wasmByteArray) return toast.error('No file selected.')
|
if (!wasmFile || !wasmByteArray) return toast.error('No file selected.')
|
||||||
|
if (accessType === 'ACCESS_TYPE_UNSPECIFIED')
|
||||||
|
return toast.error('Please select an instantiation permission type.', { style: { maxWidth: 'none' } })
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
const client = await wallet.getSigningCosmWasmClient()
|
const client = await wallet.getSigningCosmWasmClient()
|
||||||
|
|
||||||
const result = await client.upload(wallet.address as string, wasmByteArray, 'auto')
|
if (!isAuthzUpload) {
|
||||||
|
const result = await client.upload(
|
||||||
|
wallet.address as string,
|
||||||
|
wasmByteArray,
|
||||||
|
'auto',
|
||||||
|
memoState.value ?? undefined,
|
||||||
|
accessConfig,
|
||||||
|
)
|
||||||
|
setTransactionResult({
|
||||||
|
transactionHash: result.transactionHash,
|
||||||
|
codeId: result.codeId,
|
||||||
|
originalSize: result.originalSize,
|
||||||
|
compressedSize: result.compressedSize,
|
||||||
|
originalChecksum: result.checksum,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (!granterAddressState.value) {
|
||||||
|
setLoading(false)
|
||||||
|
return toast.error('Please enter the authorization granter address.', { style: { maxWidth: 'none' } })
|
||||||
|
}
|
||||||
|
const compressed = pako.gzip(wasmByteArray, { level: 9 })
|
||||||
|
|
||||||
setTransactionResult({
|
const authzExecuteContractMsg: MsgExecAllowanceEncodeObject = {
|
||||||
transactionHash: result.transactionHash,
|
typeUrl: '/cosmos.authz.v1beta1.MsgExec',
|
||||||
codeId: result.codeId,
|
value: MsgExec.fromPartial({
|
||||||
originalSize: result.originalSize,
|
grantee: wallet.address as string,
|
||||||
compressedSize: result.compressedSize,
|
msgs: [
|
||||||
originalChecksum: result.checksum,
|
{
|
||||||
})
|
typeUrl: '/cosmwasm.wasm.v1.MsgStoreCode',
|
||||||
|
value: MsgStoreCode.encode({
|
||||||
|
sender: granterAddressState.value,
|
||||||
|
wasmByteCode: compressed,
|
||||||
|
instantiatePermission: accessConfig,
|
||||||
|
}).finish(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const offlineSigner = wallet.getOfflineSignerDirect()
|
||||||
|
const stargateClient = await SigningStargateClient.connectWithSigner(getConfig(NETWORK).rpcUrl, offlineSigner, {
|
||||||
|
gasPrice: GasPrice.fromString('0.025ustars'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await stargateClient.signAndBroadcast(
|
||||||
|
wallet.address || '',
|
||||||
|
[authzExecuteContractMsg],
|
||||||
|
'auto',
|
||||||
|
memoState.value ?? undefined,
|
||||||
|
)
|
||||||
|
|
||||||
|
setTransactionResult({
|
||||||
|
transactionHash: result.transactionHash,
|
||||||
|
codeId: result.events.filter((event) => event.type === 'store_code')[0].attributes[1].value,
|
||||||
|
originalChecksum: result.events.filter((event) => event.type === 'store_code')[0].attributes[0].value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -73,6 +165,27 @@ const UploadContract: NextPage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
if (accessType === 'ACCESS_TYPE_ANY_OF_ADDRESSES') {
|
||||||
|
setAccessConfig(
|
||||||
|
AccessConfig.fromPartial({
|
||||||
|
permission: AccessType.ACCESS_TYPE_ANY_OF_ADDRESSES,
|
||||||
|
addresses: permittedAddressListState.entries.map((entry) => entry[1].address).filter(Boolean),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else if (accessType === 'ACCESS_TYPE_NOBODY') {
|
||||||
|
setAccessConfig(AccessConfig.fromPartial({ permission: AccessType.ACCESS_TYPE_NOBODY }))
|
||||||
|
} else if (accessType === 'ACCESS_TYPE_EVERYBODY') {
|
||||||
|
setAccessConfig(AccessConfig.fromPartial({ permission: AccessType.ACCESS_TYPE_EVERYBODY }))
|
||||||
|
} else if (accessType === 'ACCESS_TYPE_UNSPECIFIED') {
|
||||||
|
setAccessConfig(undefined)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error(error.message, { style: { maxWidth: 'none' } })
|
||||||
|
}
|
||||||
|
}, [accessType, permittedAddressListState.entries])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-6 px-12 space-y-4">
|
<section className="py-6 px-12 space-y-4">
|
||||||
<Conditional test={NETWORK === 'testnet'}>
|
<Conditional test={NETWORK === 'testnet'}>
|
||||||
@ -84,6 +197,58 @@ const UploadContract: NextPage = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="inset-x-0 bottom-0 border-b-2 border-white/25" />
|
<div className="inset-x-0 bottom-0 border-b-2 border-white/25" />
|
||||||
|
|
||||||
|
<div className="flex flex-col w-1/2">
|
||||||
|
<span className="text-xl font-bold text-white">Authorization Type for Contract Instantiation</span>
|
||||||
|
<select
|
||||||
|
className="px-4 pt-2 pb-2 mt-2 w-1/2 placeholder:text-white/50 bg-white/10 rounded border-2 border-white/20 focus:ring focus:ring-plumbus-20"
|
||||||
|
onChange={(e) => setAccessType(e.target.value as any)}
|
||||||
|
value={accessType}
|
||||||
|
>
|
||||||
|
<option disabled value="ACCESS_TYPE_UNSPECIFIED">
|
||||||
|
Select Authorization Type
|
||||||
|
</option>
|
||||||
|
<option value="ACCESS_TYPE_EVERYBODY">Everybody</option>
|
||||||
|
<option value="ACCESS_TYPE_ANY_OF_ADDRESSES">Any of Addresses</option>
|
||||||
|
<option value="ACCESS_TYPE_NOBODY">Nobody</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="my-2 w-1/2">
|
||||||
|
<TextInput {...memoState} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row justify-start">
|
||||||
|
<h1 className="mt-2 font-bold text-md">Authz Upload?</h1>
|
||||||
|
<label className="justify-start ml-6 cursor-pointer label">
|
||||||
|
<input
|
||||||
|
checked={isAuthzUpload}
|
||||||
|
className={`${isAuthzUpload ? `bg-stargaze` : `bg-gray-600`} checkbox`}
|
||||||
|
onClick={() => {
|
||||||
|
setIsAuthzUpload(!isAuthzUpload)
|
||||||
|
}}
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Conditional test={isAuthzUpload}>
|
||||||
|
<div className="my-2 w-3/4">
|
||||||
|
<TextInput {...granterAddressState} />
|
||||||
|
</div>
|
||||||
|
</Conditional>
|
||||||
|
|
||||||
|
<Conditional test={accessType === 'ACCESS_TYPE_ANY_OF_ADDRESSES'}>
|
||||||
|
<div className="my-2 w-3/4">
|
||||||
|
<AddressList
|
||||||
|
entries={permittedAddressListState.entries}
|
||||||
|
onAdd={permittedAddressListState.add}
|
||||||
|
onChange={permittedAddressListState.update}
|
||||||
|
onRemove={permittedAddressListState.remove}
|
||||||
|
subtitle="The list of addresses permitted to instantiate the contract"
|
||||||
|
title="Permitted Addresses"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Conditional>
|
||||||
|
|
||||||
<Conditional test={Boolean(transactionResult)}>
|
<Conditional test={Boolean(transactionResult)}>
|
||||||
<Alert type="info">
|
<Alert type="info">
|
||||||
<b>Upload success!</b> Here is the transaction result containing the code ID, transaction hash and other
|
<b>Upload success!</b> Here is the transaction result containing the code ID, transaction hash and other
|
||||||
|
Loading…
Reference in New Issue
Block a user