diff --git a/components/LinkTabs.data.ts b/components/LinkTabs.data.ts index 8bed0bf..c9c7b39 100644 --- a/components/LinkTabs.data.ts +++ b/components/LinkTabs.data.ts @@ -11,6 +11,11 @@ export const sg721LinkTabs: LinkTabProps[] = [ description: `Execute SG721 contract actions`, href: '/contracts/sg721/execute', }, + { + title: 'Migrate', + description: `Migrate SG721 contract`, + href: '/contracts/sg721/migrate', + }, ] export const minterLinkTabs: LinkTabProps[] = [ @@ -29,6 +34,11 @@ export const minterLinkTabs: LinkTabProps[] = [ description: `Execute Minter contract actions`, href: '/contracts/minter/execute', }, + { + title: 'Migrate', + description: `Migrate Minter contract`, + href: '/contracts/minter/migrate', + }, ] export const whitelistLinkTabs: LinkTabProps[] = [ diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 5834d74..345d265 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -42,9 +42,11 @@ export const Sidebar = () => { { 'py-0 ml-2 text-sm font-bold': isChild }, { 'text-gray hover:text-white': - router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1) !== href && isChild, + !router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1).includes(href) && isChild, }, - { 'text-plumbus': router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1) === href && isChild }, // active route styling + { + 'text-plumbus': router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1).includes(href) && isChild, + }, // active route styling // { 'text-gray-500 pointer-events-none': disabled }, // disabled route styling )} href={href} diff --git a/contracts/minter/contract.ts b/contracts/minter/contract.ts index a69fca8..ca9b9e5 100644 --- a/contracts/minter/contract.ts +++ b/contracts/minter/contract.ts @@ -12,6 +12,11 @@ export interface InstantiateResponse { readonly logs: readonly logs.Log[] } +export interface MigrateResponse { + readonly transactionHash: string + readonly logs: readonly logs.Log[] +} + export interface RoyaltyInfo { payment_address: string share: string @@ -224,6 +229,13 @@ export interface MinterContract { funds?: Coin[], ) => Promise + migrate: ( + senderAddress: string, + contractAddress: string, + codeId: number, + migrateMsg: Record, + ) => Promise + use: (contractAddress: string) => MinterInstance messages: (contractAddress: string) => MinterMessages @@ -555,6 +567,19 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC } } + const migrate = async ( + senderAddress: string, + contractAddress: string, + codeId: number, + migrateMsg: Record, + ): Promise => { + const result = await client.migrate(senderAddress, contractAddress, codeId, migrateMsg, 'auto') + return { + transactionHash: result.transactionHash, + logs: result.logs, + } + } + const instantiate = async ( senderAddress: string, codeId: number, @@ -785,5 +810,5 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC } } - return { use, instantiate, messages } + return { use, instantiate, migrate, messages } } diff --git a/contracts/minter/useContract.ts b/contracts/minter/useContract.ts index afca5c5..1121ea2 100644 --- a/contracts/minter/useContract.ts +++ b/contracts/minter/useContract.ts @@ -3,7 +3,7 @@ import type { logs } from '@cosmjs/stargate' import { useWallet } from 'contexts/wallet' import { useCallback, useEffect, useState } from 'react' -import type { MinterContract, MinterInstance, MinterMessages } from './contract' +import type { MigrateResponse, MinterContract, MinterInstance, MinterMessages } from './contract' import { minter as initContract } from './contract' /*export interface InstantiateResponse { @@ -32,6 +32,7 @@ export interface UseMinterContractProps { admin?: string, funds?: Coin[], ) => Promise + migrate: (contractAddress: string, codeId: number, migrateMsg: Record) => Promise use: (customAddress: string) => MinterInstance | undefined updateContractAddress: (contractAddress: string) => void getContractAddress: () => string | undefined @@ -70,6 +71,20 @@ export function useMinterContract(): UseMinterContractProps { [minter, wallet], ) + const migrate = useCallback( + (contractAddress: string, codeId: number, migrateMsg: Record): Promise => { + return new Promise((resolve, reject) => { + if (!minter) { + reject(new Error('Contract is not initialized.')) + return + } + console.log(wallet.address, contractAddress, codeId) + minter.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject) + }) + }, + [minter, wallet], + ) + const use = useCallback( (customAddress = ''): MinterInstance | undefined => { return minter?.use(address || customAddress) @@ -94,5 +109,6 @@ export function useMinterContract(): UseMinterContractProps { updateContractAddress, getContractAddress, messages, + migrate, } } diff --git a/contracts/sg721/contract.ts b/contracts/sg721/contract.ts index 6163398..7183816 100644 --- a/contracts/sg721/contract.ts +++ b/contracts/sg721/contract.ts @@ -1,6 +1,6 @@ import type { MsgExecuteContractEncodeObject, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { toBase64, toUtf8 } from '@cosmjs/encoding' -import type { Coin } from '@cosmjs/stargate' +import type { Coin, logs } from '@cosmjs/stargate' import { coin } from '@cosmjs/stargate' import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx' @@ -11,6 +11,11 @@ export interface InstantiateResponse { readonly transactionHash: string } +export interface MigrateResponse { + readonly transactionHash: string + readonly logs: readonly logs.Log[] +} + export type Expiration = { at_height: number } | { at_time: string } | { never: Record } export interface CollectionInfo { @@ -237,6 +242,13 @@ export interface SG721Contract { admin?: string, ) => Promise + migrate: ( + senderAddress: string, + contractAddress: string, + codeId: number, + migrateMsg: Record, + ) => Promise + use: (contractAddress: string) => SG721Instance messages: (contractAddress: string) => Sg721Messages @@ -618,6 +630,19 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con } } + const migrate = async ( + senderAddress: string, + contractAddress: string, + codeId: number, + migrateMsg: Record, + ): Promise => { + const result = await client.migrate(senderAddress, contractAddress, codeId, migrateMsg, 'auto') + return { + transactionHash: result.transactionHash, + logs: result.logs, + } + } + const messages = (contractAddress: string) => { const transferNft = (recipient: string, tokenId: string) => { return { @@ -816,5 +841,5 @@ export const SG721 = (client: SigningCosmWasmClient, txSigner: string): SG721Con } } - return { use, instantiate, messages } + return { use, instantiate, migrate, messages } } diff --git a/contracts/sg721/useContract.ts b/contracts/sg721/useContract.ts index d5fee82..0b8a2af 100644 --- a/contracts/sg721/useContract.ts +++ b/contracts/sg721/useContract.ts @@ -2,7 +2,7 @@ import type { Coin } from '@cosmjs/proto-signing' import { useWallet } from 'contexts/wallet' import { useCallback, useEffect, useState } from 'react' -import type { SG721Contract, SG721Instance, Sg721Messages } from './contract' +import type { MigrateResponse, SG721Contract, SG721Instance, Sg721Messages } from './contract' import { SG721 as initContract } from './contract' interface InstantiateResponse { @@ -18,6 +18,7 @@ export interface UseSG721ContractProps { admin?: string, funds?: Coin[], ) => Promise + migrate: (contractAddress: string, codeId: number, migrateMsg: Record) => Promise use: (customAddress: string) => SG721Instance | undefined updateContractAddress: (contractAddress: string) => void messages: (contractAddress: string) => Sg721Messages | undefined @@ -55,6 +56,20 @@ export function useSG721Contract(): UseSG721ContractProps { [SG721, wallet], ) + const migrate = useCallback( + (contractAddress: string, codeId: number, migrateMsg: Record): Promise => { + return new Promise((resolve, reject) => { + if (!SG721) { + reject(new Error('Contract is not initialized.')) + return + } + console.log(wallet.address, contractAddress, codeId) + SG721.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject) + }) + }, + [SG721, wallet], + ) + const use = useCallback( (customAddress = ''): SG721Instance | undefined => { return SG721?.use(address || customAddress) @@ -71,6 +86,7 @@ export function useSG721Contract(): UseSG721ContractProps { return { instantiate, + migrate, use, updateContractAddress, messages, diff --git a/package.json b/package.json index 576d82c..1912e63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stargaze-studio", - "version": "0.2.8", + "version": "0.2.9", "workspaces": [ "packages/*" ], diff --git a/pages/contracts/minter/migrate.tsx b/pages/contracts/minter/migrate.tsx new file mode 100644 index 0000000..3389bf7 --- /dev/null +++ b/pages/contracts/minter/migrate.tsx @@ -0,0 +1,132 @@ +import { Button } from 'components/Button' +import { ContractPageHeader } from 'components/ContractPageHeader' +import { useExecuteComboboxState } from 'components/contracts/minter/ExecuteCombobox.hooks' +import { FormControl } from 'components/FormControl' +import { AddressInput, NumberInput } from 'components/forms/FormInput' +import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' +import { JsonPreview } from 'components/JsonPreview' +import { LinkTabs } from 'components/LinkTabs' +import { minterLinkTabs } from 'components/LinkTabs.data' +import { TransactionHash } from 'components/TransactionHash' +import { useContracts } from 'contexts/contracts' +import { useWallet } from 'contexts/wallet' +import type { MigrateResponse } from 'contracts/minter' +import type { NextPage } from 'next' +import { useRouter } from 'next/router' +import { NextSeo } from 'next-seo' +import type { FormEvent } from 'react' +import { useEffect, useState } from 'react' +import { toast } from 'react-hot-toast' +import { FaArrowRight } from 'react-icons/fa' +import { useMutation } from 'react-query' +import { withMetadata } from 'utils/layout' +import { links } from 'utils/links' + +const MinterMigratePage: NextPage = () => { + const { minter: contract } = useContracts() + const wallet = useWallet() + + const [lastTx, setLastTx] = useState('') + + const comboboxState = useExecuteComboboxState() + const type = comboboxState.value?.id + const codeIdState = useNumberInputState({ + id: 'code-id', + name: 'code-id', + title: 'Code ID', + subtitle: 'Code ID of the New Minter', + placeholder: '1', + }) + + const contractState = useInputState({ + id: 'contract-address', + name: 'contract-address', + title: 'Minter Address', + subtitle: 'Address of the Minter contract', + }) + const contractAddress = contractState.value + + const { data, isLoading, mutate } = useMutation( + async (event: FormEvent): Promise => { + event.preventDefault() + if (!contract) { + throw new Error('Smart contract connection failed') + } + if (!wallet.initialized) { + throw new Error('Please connect your wallet.') + } + + const migrateMsg = {} + return toast.promise(contract.migrate(contractAddress, codeIdState.value, migrateMsg), { + error: `Migration failed!`, + loading: 'Executing message...', + success: (tx) => { + if (tx) { + setLastTx(tx.transactionHash) + } + return `Transaction success!` + }, + }) + }, + { + onError: (error) => { + toast.error(String(error)) + }, + }, + ) + + const router = useRouter() + + useEffect(() => { + if (contractAddress.length > 0) { + void router.replace({ query: { contractAddress } }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contractAddress]) + useEffect(() => { + const initial = new URL(document.URL).searchParams.get('contractAddress') + if (initial && initial.length > 0) contractState.onChange(initial) + }, []) + + return ( +
+ + + + +
+
+ + +
+
+
+ + + + +
+ + + +
+
+
+ ) +} + +export default withMetadata(MinterMigratePage, { center: false }) diff --git a/pages/contracts/sg721/migrate.tsx b/pages/contracts/sg721/migrate.tsx new file mode 100644 index 0000000..2d0e733 --- /dev/null +++ b/pages/contracts/sg721/migrate.tsx @@ -0,0 +1,132 @@ +import { Button } from 'components/Button' +import { ContractPageHeader } from 'components/ContractPageHeader' +import { useExecuteComboboxState } from 'components/contracts/sg721/ExecuteCombobox.hooks' +import { FormControl } from 'components/FormControl' +import { AddressInput, NumberInput } from 'components/forms/FormInput' +import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' +import { JsonPreview } from 'components/JsonPreview' +import { LinkTabs } from 'components/LinkTabs' +import { sg721LinkTabs } from 'components/LinkTabs.data' +import { TransactionHash } from 'components/TransactionHash' +import { useContracts } from 'contexts/contracts' +import { useWallet } from 'contexts/wallet' +import type { MigrateResponse } from 'contracts/sg721' +import type { NextPage } from 'next' +import { useRouter } from 'next/router' +import { NextSeo } from 'next-seo' +import type { FormEvent } from 'react' +import { useEffect, useState } from 'react' +import { toast } from 'react-hot-toast' +import { FaArrowRight } from 'react-icons/fa' +import { useMutation } from 'react-query' +import { withMetadata } from 'utils/layout' +import { links } from 'utils/links' + +const Sg721MigratePage: NextPage = () => { + const { sg721: contract } = useContracts() + const wallet = useWallet() + + const [lastTx, setLastTx] = useState('') + + const comboboxState = useExecuteComboboxState() + const type = comboboxState.value?.id + const codeIdState = useNumberInputState({ + id: 'code-id', + name: 'code-id', + title: 'Code ID', + subtitle: 'Code ID of the New Sg721 contract', + placeholder: '1', + }) + + const contractState = useInputState({ + id: 'contract-address', + name: 'contract-address', + title: 'Sg721 Address', + subtitle: 'Address of the Sg721 contract', + }) + const contractAddress = contractState.value + + const { data, isLoading, mutate } = useMutation( + async (event: FormEvent): Promise => { + event.preventDefault() + if (!contract) { + throw new Error('Smart contract connection failed') + } + if (!wallet.initialized) { + throw new Error('Please connect your wallet.') + } + + const migrateMsg = {} + return toast.promise(contract.migrate(contractAddress, codeIdState.value, migrateMsg), { + error: `Migration failed!`, + loading: 'Executing message...', + success: (tx) => { + if (tx) { + setLastTx(tx.transactionHash) + } + return `Transaction success!` + }, + }) + }, + { + onError: (error) => { + toast.error(String(error)) + }, + }, + ) + + const router = useRouter() + + useEffect(() => { + if (contractAddress.length > 0) { + void router.replace({ query: { contractAddress } }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contractAddress]) + useEffect(() => { + const initial = new URL(document.URL).searchParams.get('contractAddress') + if (initial && initial.length > 0) contractState.onChange(initial) + }, []) + + return ( +
+ + + + +
+
+ + +
+
+
+ + + + +
+ + + +
+
+
+ ) +} + +export default withMetadata(Sg721MigratePage, { center: false }) diff --git a/pages/contracts/whitelist/execute.tsx b/pages/contracts/whitelist/execute.tsx index 7e1b3ed..005c8db 100644 --- a/pages/contracts/whitelist/execute.tsx +++ b/pages/contracts/whitelist/execute.tsx @@ -116,7 +116,7 @@ const WhitelistExecutePage: NextPage = () => { link={links.Documentation} title="Whitelist Contract" /> - +