diff --git a/.env.example b/.env.example
index 54d70c4..9b264f6 100644
--- a/.env.example
+++ b/.env.example
@@ -1,4 +1,4 @@
-APP_VERSION=0.7.9
+APP_VERSION=0.7.10
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
NEXT_PUBLIC_SG721_CODE_ID=2595
@@ -39,6 +39,7 @@ NEXT_PUBLIC_OPEN_EDITION_IBC_FRNZ_FACTORY_ADDRESS="stars1vzffawsjhvspstu5lvtzz2x
NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS="stars1tc09vlgdg8rqyapcxwm9qdq8naj4gym9px4ntue9cs0kse5rvess0nee3a"
NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr"
+NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS="stars1crgx0f70fzksa57hq87wtl8f04h0qyk5la0hk0fu8dyhl67ju80qaxzr5z"
NEXT_PUBLIC_WHITELIST_CODE_ID=2602
NEXT_PUBLIC_WHITELIST_FLEX_CODE_ID=2603
NEXT_PUBLIC_BADGE_HUB_CODE_ID=1336
diff --git a/components/LinkTabs.data.ts b/components/LinkTabs.data.ts
index 381cb93..cfc268a 100644
--- a/components/LinkTabs.data.ts
+++ b/components/LinkTabs.data.ts
@@ -145,3 +145,16 @@ export const splitsLinkTabs: LinkTabProps[] = [
href: '/contracts/splits/migrate',
},
]
+
+export const royaltyRegistryLinkTabs: LinkTabProps[] = [
+ {
+ title: 'Query',
+ description: `Dispatch queries for your Royalty Registry contract`,
+ href: '/contracts/royaltyRegistry/query',
+ },
+ {
+ title: 'Execute',
+ description: `Execute Royalty Registry contract actions`,
+ href: '/contracts/royaltyRegistry/execute',
+ },
+]
diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx
index e240af3..f5e228e 100644
--- a/components/Sidebar.tsx
+++ b/components/Sidebar.tsx
@@ -233,6 +233,15 @@ export const Sidebar = () => {
>
Splits Contract
+
+ Royalty Registry
+
diff --git a/components/contracts/royaltyRegistry/ExecuteCombobox.hooks.ts b/components/contracts/royaltyRegistry/ExecuteCombobox.hooks.ts
new file mode 100644
index 0000000..2bd614a
--- /dev/null
+++ b/components/contracts/royaltyRegistry/ExecuteCombobox.hooks.ts
@@ -0,0 +1,7 @@
+import type { ExecuteListItem } from 'contracts/royaltyRegistry/messages/execute'
+import { useState } from 'react'
+
+export const useExecuteComboboxState = () => {
+ const [value, setValue] = useState(null)
+ return { value, onChange: (item: ExecuteListItem) => setValue(item) }
+}
diff --git a/components/contracts/royaltyRegistry/ExecuteCombobox.tsx b/components/contracts/royaltyRegistry/ExecuteCombobox.tsx
new file mode 100644
index 0000000..e28a2d6
--- /dev/null
+++ b/components/contracts/royaltyRegistry/ExecuteCombobox.tsx
@@ -0,0 +1,92 @@
+import { Combobox, Transition } from '@headlessui/react'
+import clsx from 'clsx'
+import { FormControl } from 'components/FormControl'
+import type { ExecuteListItem } from 'contracts/royaltyRegistry/messages/execute'
+import { EXECUTE_LIST } from 'contracts/royaltyRegistry/messages/execute'
+import { matchSorter } from 'match-sorter'
+import { Fragment, useState } from 'react'
+import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
+
+export interface ExecuteComboboxProps {
+ value: ExecuteListItem | null
+ onChange: (item: ExecuteListItem) => void
+}
+
+export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => {
+ const [search, setSearch] = useState('')
+
+ const filtered =
+ search === '' ? EXECUTE_LIST : matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
+
+ return (
+
+
+ val?.name ?? ''}
+ id="message-type"
+ onChange={(event) => setSearch(event.target.value)}
+ placeholder="Select message type"
+ />
+
+
+ {({ open }) => }
+
+
+ setSearch('')} as={Fragment}>
+
+ {filtered.length < 1 && (
+
+ Message type not found.
+
+ )}
+ {filtered.map((entry) => (
+
+ clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-stargaze-80': active })
+ }
+ value={entry}
+ >
+ {entry.name}
+ {entry.description}
+
+ ))}
+
+
+
+
+ {value && (
+
+
+
+
+
{value.description}
+
+ )}
+
+ )
+}
diff --git a/contexts/contracts.tsx b/contexts/contracts.tsx
index b498185..13790ff 100644
--- a/contexts/contracts.tsx
+++ b/contexts/contracts.tsx
@@ -6,6 +6,8 @@ import type { UseBaseMinterContractProps } from 'contracts/baseMinter'
import { useBaseMinterContract } from 'contracts/baseMinter'
import { type UseOpenEditionFactoryContractProps, useOpenEditionFactoryContract } from 'contracts/openEditionFactory'
import { type UseOpenEditionMinterContractProps, useOpenEditionMinterContract } from 'contracts/openEditionMinter'
+import type { UseRoyaltyRegistryContractProps } from 'contracts/royaltyRegistry'
+import { useRoyaltyRegistryContract } from 'contracts/royaltyRegistry'
import type { UseSG721ContractProps } from 'contracts/sg721'
import { useSG721Contract } from 'contracts/sg721'
import type { UseVendingFactoryContractProps } from 'contracts/vendingFactory'
@@ -36,6 +38,7 @@ export interface ContractsStore extends State {
openEditionFactory: UseOpenEditionFactoryContractProps | null
badgeHub: UseBadgeHubContractProps | null
splits: UseSplitsContractProps | null
+ royaltyRegistry: UseRoyaltyRegistryContractProps | null
}
/**
@@ -52,6 +55,7 @@ export const defaultValues: ContractsStore = {
openEditionFactory: null,
badgeHub: null,
splits: null,
+ royaltyRegistry: null,
}
/**
@@ -85,6 +89,7 @@ const ContractsSubscription: VFC = () => {
const openEditionFactory = useOpenEditionFactoryContract()
const badgeHub = useBadgeHubContract()
const splits = useSplitsContract()
+ const royaltyRegistry = useRoyaltyRegistryContract()
useEffect(() => {
useContracts.setState({
@@ -98,8 +103,9 @@ const ContractsSubscription: VFC = () => {
openEditionFactory,
badgeHub,
splits,
+ royaltyRegistry,
})
- }, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory, badgeHub, splits])
+ }, [sg721, vendingMinter, baseMinter, whitelist, vendingFactory, baseFactory, badgeHub, splits, royaltyRegistry])
return null
}
diff --git a/contracts/royaltyRegistry/contract.ts b/contracts/royaltyRegistry/contract.ts
new file mode 100644
index 0000000..25e21cf
--- /dev/null
+++ b/contracts/royaltyRegistry/contract.ts
@@ -0,0 +1,407 @@
+import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
+import type { Coin } from '@cosmjs/proto-signing'
+import type { logs } from '@cosmjs/stargate'
+
+export interface InstantiateResponse {
+ readonly contractAddress: string
+ readonly transactionHash: string
+}
+
+export interface MigrateResponse {
+ readonly transactionHash: string
+ readonly logs: readonly logs.Log[]
+}
+
+export interface RoyaltyRegistryInstance {
+ readonly contractAddress: string
+ //Query
+ config: () => Promise
+ collectionRoyaltyDefault: (collection: string) => Promise
+ collectionRoyaltyProtocol: (collection: string, protocol: string) => Promise
+ // RoyaltyProtocolByCollection: (collection: string, queryOptions: QqueryOptions) => Promise
+ royaltyPayment: (collection: string, protocol?: string) => Promise
+
+ //Execute
+ initializeCollectionRoyalty: (collection: string) => Promise
+ setCollectionRoyaltyDefault: (collection: string, recipient: string, share: number) => Promise
+ updateCollectionRoyaltyDefault: (
+ collection: string,
+ recipient?: string,
+ shareDelta?: number,
+ decrement?: boolean,
+ ) => Promise
+ setCollectionRoyaltyProtocol: (
+ collection: string,
+ protocol: string,
+ recipient: string,
+ share: number,
+ ) => Promise
+ updateCollectionRoyaltyProtocol: (
+ collection: string,
+ protocol?: string,
+ recipient?: string,
+ shareDelta?: number,
+ decrement?: boolean,
+ ) => Promise
+}
+
+export interface RoyaltyRegistryMessages {
+ initializeCollectionRoyalty: (collection: string) => InitializeCollectionRoyaltyMessage
+ setCollectionRoyaltyDefault: (
+ collection: string,
+ recipient: string,
+ share: number,
+ ) => SetCollectionRoyaltyDefaultMessage
+ updateCollectionRoyaltyDefault: (
+ collection: string,
+ recipient?: string,
+ shareDelta?: number,
+ decrement?: boolean,
+ ) => UpdateCollectionRoyaltyDefaultMessage
+ setCollectionRoyaltyProtocol: (
+ collection: string,
+ protocol: string,
+ recipient: string,
+ share: number,
+ ) => SetCollectionRoyaltyProtocolMessage
+ updateCollectionRoyaltyProtocol: (
+ collection: string,
+ protocol?: string,
+ recipient?: string,
+ shareDelta?: number,
+ decrement?: boolean,
+ ) => UpdateCollectionRoyaltyProtocolMessage
+}
+
+export interface InitializeCollectionRoyaltyMessage {
+ sender: string
+ contract: string
+ msg: {
+ initialize_collection_royalty: { collection: string }
+ }
+ funds: Coin[]
+}
+
+export interface SetCollectionRoyaltyDefaultMessage {
+ sender: string
+ contract: string
+ msg: {
+ set_collection_royalty_default: { collection: string; recipient: string; share: number }
+ }
+ funds: Coin[]
+}
+
+export interface UpdateCollectionRoyaltyDefaultMessage {
+ sender: string
+ contract: string
+ msg: {
+ update_collection_royalty_default: {
+ collection: string
+ recipient?: string
+ share_delta?: number
+ decrement?: boolean
+ }
+ }
+ funds: Coin[]
+}
+
+export interface SetCollectionRoyaltyProtocolMessage {
+ sender: string
+ contract: string
+ msg: {
+ set_collection_royalty_protocol: {
+ collection: string
+ protocol: string
+ recipient: string
+ share: number
+ }
+ }
+ funds: Coin[]
+}
+
+export interface UpdateCollectionRoyaltyProtocolMessage {
+ sender: string
+ contract: string
+ msg: {
+ update_collection_royalty_protocol: {
+ collection: string
+ protocol?: string
+ recipient?: string
+ share_delta?: number
+ decrement?: boolean
+ }
+ }
+ funds: Coin[]
+}
+
+export interface RoyaltyRegistryContract {
+ instantiate: (
+ codeId: number,
+ initMsg: Record,
+ label: string,
+ admin?: string,
+ ) => Promise
+
+ use: (contractAddress: string) => RoyaltyRegistryInstance
+
+ migrate: (
+ senderAddress: string,
+ contractAddress: string,
+ codeId: number,
+ migrateMsg: Record,
+ ) => Promise
+
+ messages: (contractAddress: string) => RoyaltyRegistryMessages
+}
+
+export const RoyaltyRegistry = (client: SigningCosmWasmClient, txSigner: string): RoyaltyRegistryContract => {
+ const use = (contractAddress: string): RoyaltyRegistryInstance => {
+ ///QUERY
+ const config = async (): Promise => {
+ return client.queryContractSmart(contractAddress, {
+ config: {},
+ })
+ }
+
+ const collectionRoyaltyDefault = async (collection: string): Promise => {
+ return client.queryContractSmart(contractAddress, {
+ collection_royalty_default: { collection },
+ })
+ }
+
+ const collectionRoyaltyProtocol = async (collection: string, protocol: string): Promise => {
+ return client.queryContractSmart(contractAddress, {
+ collection_royalty_protocol: { collection, protocol },
+ })
+ }
+
+ const royaltyPayment = async (collection: string, protocol?: string): Promise => {
+ return client.queryContractSmart(contractAddress, {
+ royalty_payment: { collection, protocol },
+ })
+ }
+
+ /// EXECUTE
+ const initializeCollectionRoyalty = async (collection: string): Promise => {
+ const res = await client.execute(
+ txSigner,
+ contractAddress,
+ {
+ initialize_collection_royalty: { collection },
+ },
+ 'auto',
+ )
+ return res.transactionHash
+ }
+
+ const setCollectionRoyaltyDefault = async (
+ collection: string,
+ recipient: string,
+ share: number,
+ ): Promise => {
+ const res = await client.execute(
+ txSigner,
+ contractAddress,
+ {
+ set_collection_royalty_default: { collection, recipient, share: (share / 100).toString() },
+ },
+ 'auto',
+ )
+ return res.transactionHash
+ }
+
+ const updateCollectionRoyaltyDefault = async (
+ collection: string,
+ recipient?: string,
+ shareDelta?: number,
+ decrement?: boolean,
+ ): Promise => {
+ const res = await client.execute(
+ txSigner,
+ contractAddress,
+ {
+ update_collection_royalty_default: {
+ collection,
+ recipient,
+ share_delta: shareDelta ? (shareDelta / 100).toString() : undefined,
+ decrement,
+ },
+ },
+ 'auto',
+ )
+ return res.transactionHash
+ }
+
+ const setCollectionRoyaltyProtocol = async (
+ collection: string,
+ protocol: string,
+ recipient: string,
+ share: number,
+ ): Promise => {
+ const res = await client.execute(
+ txSigner,
+ contractAddress,
+ {
+ set_collection_royalty_protocol: { collection, protocol, recipient, share: (share / 100).toString() },
+ },
+ 'auto',
+ )
+ return res.transactionHash
+ }
+
+ const updateCollectionRoyaltyProtocol = async (
+ collection: string,
+ protocol?: string,
+ recipient?: string,
+ shareDelta?: number,
+ decrement?: boolean,
+ ): Promise => {
+ const res = await client.execute(
+ txSigner,
+ contractAddress,
+ {
+ update_collection_royalty_protocol: {
+ collection,
+ protocol,
+ recipient,
+ share_delta: shareDelta ? (shareDelta / 100).toString() : undefined,
+ decrement,
+ },
+ },
+ 'auto',
+ )
+ return res.transactionHash
+ }
+
+ return {
+ contractAddress,
+ config,
+ collectionRoyaltyDefault,
+ collectionRoyaltyProtocol,
+ royaltyPayment,
+ initializeCollectionRoyalty,
+ setCollectionRoyaltyDefault,
+ updateCollectionRoyaltyDefault,
+ setCollectionRoyaltyProtocol,
+ updateCollectionRoyaltyProtocol,
+ }
+ }
+
+ const instantiate = async (
+ codeId: number,
+ initMsg: Record,
+ label: string,
+ admin?: string,
+ ): Promise => {
+ const result = await client.instantiate(txSigner, codeId, initMsg, label, 'auto', {
+ admin,
+ })
+
+ return {
+ contractAddress: result.contractAddress,
+ transactionHash: result.transactionHash,
+ }
+ }
+
+ 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 initializeCollectionRoyalty = (collection: string) => {
+ return {
+ sender: txSigner,
+ contract: contractAddress,
+ msg: {
+ initialize_collection_royalty: { collection },
+ },
+ funds: [],
+ }
+ }
+
+ const setCollectionRoyaltyDefault = (collection: string, recipient: string, share: number) => {
+ return {
+ sender: txSigner,
+ contract: contractAddress,
+ msg: {
+ set_collection_royalty_default: { collection, recipient, share: share / 100 },
+ },
+ funds: [],
+ }
+ }
+
+ const updateCollectionRoyaltyDefault = (
+ collection: string,
+ recipient?: string,
+ shareDelta?: number,
+ decrement?: boolean,
+ ) => {
+ return {
+ sender: txSigner,
+ contract: contractAddress,
+ msg: {
+ update_collection_royalty_default: {
+ collection,
+ recipient,
+ share_delta: shareDelta ? shareDelta / 100 : undefined,
+ decrement,
+ },
+ },
+ funds: [],
+ }
+ }
+
+ const setCollectionRoyaltyProtocol = (collection: string, protocol: string, recipient: string, share: number) => {
+ return {
+ sender: txSigner,
+ contract: contractAddress,
+ msg: {
+ set_collection_royalty_protocol: { collection, protocol, recipient, share: share / 100 },
+ },
+ funds: [],
+ }
+ }
+
+ const updateCollectionRoyaltyProtocol = (
+ collection: string,
+ protocol?: string,
+ recipient?: string,
+ shareDelta?: number,
+ decrement?: boolean,
+ ) => {
+ return {
+ sender: txSigner,
+ contract: contractAddress,
+ msg: {
+ update_collection_royalty_protocol: {
+ collection,
+ protocol,
+ recipient,
+ share_delta: shareDelta ? shareDelta / 100 : undefined,
+ decrement,
+ },
+ },
+ funds: [],
+ }
+ }
+
+ return {
+ initializeCollectionRoyalty,
+ setCollectionRoyaltyDefault,
+ updateCollectionRoyaltyDefault,
+ setCollectionRoyaltyProtocol,
+ updateCollectionRoyaltyProtocol,
+ }
+ }
+
+ return { use, instantiate, migrate, messages }
+}
diff --git a/contracts/royaltyRegistry/index.ts b/contracts/royaltyRegistry/index.ts
new file mode 100644
index 0000000..6dc6461
--- /dev/null
+++ b/contracts/royaltyRegistry/index.ts
@@ -0,0 +1,2 @@
+export * from './contract'
+export * from './useContract'
diff --git a/contracts/royaltyRegistry/messages/execute.ts b/contracts/royaltyRegistry/messages/execute.ts
new file mode 100644
index 0000000..4d37d3b
--- /dev/null
+++ b/contracts/royaltyRegistry/messages/execute.ts
@@ -0,0 +1,144 @@
+import type { RoyaltyRegistryInstance } from '../index'
+import { useRoyaltyRegistryContract } from '../index'
+
+export type ExecuteType = typeof EXECUTE_TYPES[number]
+
+export const EXECUTE_TYPES = [
+ 'initialize_collection_royalty',
+ 'set_collection_royalty_default',
+ 'update_collection_royalty_default',
+ 'set_collection_royalty_protocol',
+ 'update_collection_royalty_protocol',
+] as const
+
+export interface ExecuteListItem {
+ id: ExecuteType
+ name: string
+ description?: string
+}
+
+export const EXECUTE_LIST: ExecuteListItem[] = [
+ {
+ id: 'initialize_collection_royalty',
+ name: 'Initialize Collection Royalty',
+ description: 'Initialize collection royalty',
+ },
+ {
+ id: 'set_collection_royalty_default',
+ name: 'Set Collection Royalty Default',
+ description: 'Set collection royalty default',
+ },
+ {
+ id: 'update_collection_royalty_default',
+ name: 'Update Collection Royalty Default',
+ description: 'Update collection royalty default',
+ },
+ {
+ id: 'set_collection_royalty_protocol',
+ name: 'Set Collection Royalty Protocol',
+ description: 'Set collection royalty protocol',
+ },
+ {
+ id: 'update_collection_royalty_protocol',
+ name: 'Update Collection Royalty Protocol',
+ description: 'Update collection royalty protocol',
+ },
+]
+
+export interface DispatchExecuteProps {
+ type: ExecuteType
+ [k: string]: unknown
+}
+
+type Select = T
+/** @see {@link RoyaltyRegistryInstance} */
+export interface DispatchExecuteArgs {
+ contract: string
+ collection: string
+ protocol: string
+ recipient: string
+ share: number
+ shareDelta: number
+ decrement: boolean
+ messages?: RoyaltyRegistryInstance
+ type: string | undefined
+}
+
+export const dispatchExecute = async (args: DispatchExecuteArgs) => {
+ const { messages } = args
+ if (!messages) {
+ throw new Error('Cannot dispatch execute, messages are not defined')
+ }
+ switch (args.type) {
+ case 'initialize_collection_royalty': {
+ return messages.initializeCollectionRoyalty(args.collection)
+ }
+ case 'set_collection_royalty_default': {
+ return messages.setCollectionRoyaltyDefault(args.collection, args.recipient, args.share)
+ }
+ case 'update_collection_royalty_default': {
+ return messages.updateCollectionRoyaltyDefault(args.collection, args.recipient, args.shareDelta, args.decrement)
+ }
+ case 'set_collection_royalty_protocol': {
+ return messages.setCollectionRoyaltyProtocol(args.collection, args.protocol, args.recipient, args.share)
+ }
+ case 'update_collection_royalty_protocol': {
+ return messages.updateCollectionRoyaltyProtocol(
+ args.collection,
+ args.protocol,
+ args.recipient,
+ args.shareDelta,
+ args.decrement,
+ )
+ }
+ default: {
+ throw new Error('Unknown execution type')
+ }
+ }
+}
+
+export const previewExecutePayload = (args: DispatchExecuteArgs) => {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const { messages } = useRoyaltyRegistryContract()
+ const { contract } = args
+ switch (args.type) {
+ case 'initialize_collection_royalty': {
+ return messages(contract)?.initializeCollectionRoyalty(args.collection)
+ }
+ case 'set_collection_royalty_default': {
+ return messages(contract)?.setCollectionRoyaltyDefault(args.collection, args.recipient, args.share)
+ }
+ case 'update_collection_royalty_default': {
+ return messages(contract)?.updateCollectionRoyaltyDefault(
+ args.collection,
+ args.recipient,
+ args.shareDelta,
+ args.decrement,
+ )
+ }
+ case 'set_collection_royalty_protocol': {
+ return messages(contract)?.setCollectionRoyaltyProtocol(
+ args.collection,
+ args.protocol,
+ args.recipient,
+ args.share,
+ )
+ }
+ case 'update_collection_royalty_protocol': {
+ return messages(contract)?.updateCollectionRoyaltyProtocol(
+ args.collection,
+ args.protocol,
+ args.recipient,
+ args.shareDelta,
+ args.decrement,
+ )
+ }
+ default: {
+ return {}
+ }
+ }
+}
+
+export const isEitherType = (type: unknown, arr: T[]): type is T => {
+ return arr.some((val) => type === val)
+}
diff --git a/contracts/royaltyRegistry/messages/query.ts b/contracts/royaltyRegistry/messages/query.ts
new file mode 100644
index 0000000..07d6909
--- /dev/null
+++ b/contracts/royaltyRegistry/messages/query.ts
@@ -0,0 +1,64 @@
+import type { RoyaltyRegistryInstance } from '../contract'
+
+export type QueryType = typeof QUERY_TYPES[number]
+
+export const QUERY_TYPES = [
+ 'config',
+ 'collection_royalty_default',
+ 'collection_royalty_protocol',
+ 'royalty_payment',
+] as const
+export interface QueryListItem {
+ id: QueryType
+ name: string
+ description?: string
+}
+
+export const QUERY_LIST: QueryListItem[] = [
+ { id: 'config', name: 'Query Config', description: 'View the contract config' },
+ {
+ id: 'collection_royalty_default',
+ name: 'Query Collection Royalty Details',
+ description: 'View the collection royalty details',
+ },
+ {
+ id: 'collection_royalty_protocol',
+ name: 'Query Collection Royalty Protocol',
+ description: 'View the collection royalty protocol',
+ },
+ {
+ id: 'royalty_payment',
+ name: 'Query Royalty Payment',
+ description: 'View the royalty payment',
+ },
+]
+/*
+ //Query
+ config: () => Promise
+ collectionRoyaltyDefault: (collection: string) => Promise
+ collectionRoyaltyProtocol: (collection: string, protocol: string) => Promise
+ // RoyaltyProtocolByCollection: (collection: string, queryOptions: QqueryOptions) => Promise
+ royaltyPayment: (collection: string, protocol?: string) => Promise
+ */
+
+export interface DispatchQueryProps {
+ messages: RoyaltyRegistryInstance | undefined
+ type: QueryType
+ collection: string
+ protocol: string
+}
+
+export const dispatchQuery = (props: DispatchQueryProps) => {
+ const { messages, type, collection, protocol } = props
+ switch (type) {
+ case 'config':
+ return messages?.config()
+ case 'collection_royalty_default':
+ return messages?.collectionRoyaltyDefault(collection)
+ case 'collection_royalty_protocol':
+ return messages?.collectionRoyaltyProtocol(collection, protocol)
+ default: {
+ throw new Error('unknown query type')
+ }
+ }
+}
diff --git a/contracts/royaltyRegistry/useContract.ts b/contracts/royaltyRegistry/useContract.ts
new file mode 100644
index 0000000..7d73394
--- /dev/null
+++ b/contracts/royaltyRegistry/useContract.ts
@@ -0,0 +1,99 @@
+/* eslint-disable eslint-comments/disable-enable-pair */
+
+import { useWallet } from 'contexts/wallet'
+import { useCallback, useEffect, useState } from 'react'
+
+import type {
+ InstantiateResponse,
+ MigrateResponse,
+ RoyaltyRegistryContract,
+ RoyaltyRegistryInstance,
+ RoyaltyRegistryMessages,
+} from './contract'
+import { RoyaltyRegistry as initContract } from './contract'
+
+export interface UseRoyaltyRegistryContractProps {
+ instantiate: (
+ codeId: number,
+ initMsg: Record,
+ label: string,
+ admin?: string,
+ ) => Promise
+
+ migrate: (contractAddress: string, codeId: number, migrateMsg: Record) => Promise
+
+ use: (customAddress?: string) => RoyaltyRegistryInstance | undefined
+
+ updateContractAddress: (contractAddress: string) => void
+
+ messages: (contractAddress: string) => RoyaltyRegistryMessages | undefined
+}
+
+export function useRoyaltyRegistryContract(): UseRoyaltyRegistryContractProps {
+ const wallet = useWallet()
+
+ const [address, setAddress] = useState('')
+ const [royaltyRegistry, setRoyaltyRegistry] = useState()
+
+ useEffect(() => {
+ setAddress(localStorage.getItem('contract_address') || '')
+ }, [])
+
+ useEffect(() => {
+ const royaltyRegistryContract = initContract(wallet.getClient(), wallet.address)
+ setRoyaltyRegistry(royaltyRegistryContract)
+ }, [wallet])
+
+ const updateContractAddress = (contractAddress: string) => {
+ setAddress(contractAddress)
+ }
+
+ const instantiate = useCallback(
+ (codeId: number, initMsg: Record, label: string, admin?: string): Promise => {
+ return new Promise((resolve, reject) => {
+ if (!royaltyRegistry) {
+ reject(new Error('Contract is not initialized.'))
+ return
+ }
+ royaltyRegistry.instantiate(codeId, initMsg, label, admin).then(resolve).catch(reject)
+ })
+ },
+ [royaltyRegistry],
+ )
+
+ const migrate = useCallback(
+ (contractAddress: string, codeId: number, migrateMsg: Record): Promise => {
+ return new Promise((resolve, reject) => {
+ if (!royaltyRegistry) {
+ reject(new Error('Contract is not initialized.'))
+ return
+ }
+ console.log(wallet.address, contractAddress, codeId)
+ royaltyRegistry.migrate(wallet.address, contractAddress, codeId, migrateMsg).then(resolve).catch(reject)
+ })
+ },
+ [royaltyRegistry, wallet],
+ )
+
+ const use = useCallback(
+ (customAddress = ''): RoyaltyRegistryInstance | undefined => {
+ return royaltyRegistry?.use(address || customAddress)
+ },
+ [royaltyRegistry, address],
+ )
+
+ const messages = useCallback(
+ (customAddress = ''): RoyaltyRegistryMessages | undefined => {
+ return royaltyRegistry?.messages(address || customAddress)
+ },
+ [royaltyRegistry, address],
+ )
+
+ return {
+ instantiate,
+ migrate,
+ use,
+ updateContractAddress,
+ messages,
+ }
+}
diff --git a/env.d.ts b/env.d.ts
index 3d4984a..ac2bfb4 100644
--- a/env.d.ts
+++ b/env.d.ts
@@ -46,6 +46,7 @@ declare namespace NodeJS {
readonly NEXT_PUBLIC_BASE_FACTORY_ADDRESS: string
readonly NEXT_PUBLIC_BASE_FACTORY_UPDATABLE_ADDRESS: string
readonly NEXT_PUBLIC_SG721_NAME_ADDRESS: string
+ readonly NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS: string
readonly NEXT_PUBLIC_BASE_MINTER_CODE_ID: string
readonly NEXT_PUBLIC_BADGE_HUB_CODE_ID: string
readonly NEXT_PUBLIC_BADGE_HUB_ADDRESS: string
diff --git a/package.json b/package.json
index 85365c0..0253cb6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "stargaze-studio",
- "version": "0.7.9",
+ "version": "0.7.10",
"workspaces": [
"packages/*"
],
diff --git a/pages/contracts/royaltyRegistry/execute.tsx b/pages/contracts/royaltyRegistry/execute.tsx
new file mode 100644
index 0000000..7c316d1
--- /dev/null
+++ b/pages/contracts/royaltyRegistry/execute.tsx
@@ -0,0 +1,211 @@
+import clsx from 'clsx'
+import { Button } from 'components/Button'
+import { Conditional } from 'components/Conditional'
+import { ContractPageHeader } from 'components/ContractPageHeader'
+import { ExecuteCombobox } from 'components/contracts/royaltyRegistry/ExecuteCombobox'
+import { useExecuteComboboxState } from 'components/contracts/royaltyRegistry/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 { royaltyRegistryLinkTabs } from 'components/LinkTabs.data'
+import { TransactionHash } from 'components/TransactionHash'
+import { useContracts } from 'contexts/contracts'
+import { useWallet } from 'contexts/wallet'
+import type { DispatchExecuteArgs } from 'contracts/royaltyRegistry/messages/execute'
+import { dispatchExecute, isEitherType, previewExecutePayload } from 'contracts/royaltyRegistry/messages/execute'
+import type { NextPage } from 'next'
+import { useRouter } from 'next/router'
+import { NextSeo } from 'next-seo'
+import type { FormEvent } from 'react'
+import { useEffect, useMemo, useState } from 'react'
+import { toast } from 'react-hot-toast'
+import { FaArrowRight } from 'react-icons/fa'
+import { useMutation } from 'react-query'
+import { ROYALTY_REGISTRY_ADDRESS } from 'utils/constants'
+import { withMetadata } from 'utils/layout'
+import { links } from 'utils/links'
+
+const RoyaltyRegistryExecutePage: NextPage = () => {
+ const { royaltyRegistry: contract } = useContracts()
+ const wallet = useWallet()
+
+ const [lastTx, setLastTx] = useState('')
+
+ const comboboxState = useExecuteComboboxState()
+ const type = comboboxState.value?.id
+
+ const contractState = useInputState({
+ id: 'contract-address',
+ name: 'contract-address',
+ title: 'Royalty Registry Address',
+ subtitle: 'Address of the Royalty Registry contract',
+ defaultValue: ROYALTY_REGISTRY_ADDRESS,
+ })
+ const contractAddress = contractState.value
+
+ const collectionAddressState = useInputState({
+ id: 'collection-address',
+ name: 'collection-address',
+ title: 'Collection Address',
+ subtitle: 'Address of the collection',
+ })
+
+ const protocolAddressState = useInputState({
+ id: 'protocol-address',
+ name: 'protocol-address',
+ title: 'Protocol Address',
+ subtitle: 'Address of the protocol',
+ })
+
+ const recipientAddressState = useInputState({
+ id: 'recipient-address',
+ name: 'recipient-address',
+ title: 'Recipient Address',
+ subtitle: 'Address of the recipient',
+ })
+
+ const shareState = useNumberInputState({
+ id: 'share',
+ name: 'share',
+ title: 'Share',
+ subtitle: 'Share percentage',
+ placeholder: '4%',
+ })
+
+ const shareDeltaState = useNumberInputState({
+ id: 'share-delta',
+ name: 'share-delta',
+ title: 'Share Delta',
+ subtitle: 'The change of share percentage',
+ placeholder: '1%',
+ })
+
+ const [decrement, setDecrement] = useState(false)
+
+ const showRecipientAddress = isEitherType(type, [
+ 'set_collection_royalty_default',
+ 'set_collection_royalty_protocol',
+ 'update_collection_royalty_default',
+ 'update_collection_royalty_protocol',
+ ])
+
+ const messages = useMemo(() => contract?.use(contractState.value), [contract, contractState.value])
+ const payload: DispatchExecuteArgs = {
+ contract: contractState.value,
+ messages,
+ type,
+ collection: collectionAddressState.value,
+ protocol: protocolAddressState.value,
+ recipient: recipientAddressState.value,
+ share: shareState.value,
+ shareDelta: shareDeltaState.value,
+ decrement,
+ }
+ const { isLoading, mutate } = useMutation(
+ async (event: FormEvent) => {
+ event.preventDefault()
+ if (!type) {
+ throw new Error('Please select message type!')
+ }
+ if (!wallet.initialized) {
+ throw new Error('Please connect your wallet.')
+ }
+ const txHash = await toast.promise(dispatchExecute(payload), {
+ error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`,
+ loading: 'Executing message...',
+ success: (tx) => `Transaction ${tx} success!`,
+ })
+ if (txHash) {
+ setLastTx(txHash)
+ }
+ },
+ {
+ onError: (error) => {
+ toast.error(String(error), { style: { maxWidth: 'none' } })
+ },
+ },
+ )
+
+ 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(RoyaltyRegistryExecutePage, { center: false })
diff --git a/pages/contracts/royaltyRegistry/index.tsx b/pages/contracts/royaltyRegistry/index.tsx
new file mode 100644
index 0000000..6d40435
--- /dev/null
+++ b/pages/contracts/royaltyRegistry/index.tsx
@@ -0,0 +1 @@
+export { default } from './execute'
diff --git a/pages/contracts/royaltyRegistry/query.tsx b/pages/contracts/royaltyRegistry/query.tsx
new file mode 100644
index 0000000..b59a009
--- /dev/null
+++ b/pages/contracts/royaltyRegistry/query.tsx
@@ -0,0 +1,146 @@
+import clsx from 'clsx'
+import { Conditional } from 'components/Conditional'
+import { ContractPageHeader } from 'components/ContractPageHeader'
+import { FormControl } from 'components/FormControl'
+import { AddressInput } from 'components/forms/FormInput'
+import { useInputState } from 'components/forms/FormInput.hooks'
+import { JsonPreview } from 'components/JsonPreview'
+import { LinkTabs } from 'components/LinkTabs'
+import { royaltyRegistryLinkTabs } from 'components/LinkTabs.data'
+import { useContracts } from 'contexts/contracts'
+import { useWallet } from 'contexts/wallet'
+import type { QueryType } from 'contracts/royaltyRegistry/messages/query'
+import { dispatchQuery, QUERY_LIST } from 'contracts/royaltyRegistry/messages/query'
+import type { NextPage } from 'next'
+import { useRouter } from 'next/router'
+import { NextSeo } from 'next-seo'
+import { useEffect, useState } from 'react'
+import { toast } from 'react-hot-toast'
+import { useQuery } from 'react-query'
+import { ROYALTY_REGISTRY_ADDRESS } from 'utils/constants'
+import { withMetadata } from 'utils/layout'
+import { links } from 'utils/links'
+import { resolveAddress } from 'utils/resolveAddress'
+
+const RoyaltyRegistryQueryPage: NextPage = () => {
+ const { royaltyRegistry: contract } = useContracts()
+ const wallet = useWallet()
+
+ const contractState = useInputState({
+ id: 'contract-address',
+ name: 'contract-address',
+ title: 'Royalty Registry Address',
+ subtitle: 'Address of the Royalty Registry contract',
+ defaultValue: ROYALTY_REGISTRY_ADDRESS,
+ })
+ const contractAddress = contractState.value
+
+ const collectionAddressState = useInputState({
+ id: 'collection-address',
+ name: 'collection-address',
+ title: 'Collection Address',
+ subtitle: 'Address of the collection',
+ })
+
+ const protocolAddressState = useInputState({
+ id: 'protocol-address',
+ name: 'protocol-address',
+ title: 'Protocol Address',
+ subtitle: 'Address of the protocol',
+ })
+
+ const collectionAddress = collectionAddressState.value
+ const protocolAddress = protocolAddressState.value
+
+ const [type, setType] = useState('config')
+
+ const { data: response } = useQuery(
+ [contractAddress, type, contract, wallet, collectionAddress, protocolAddress] as const,
+ async ({ queryKey }) => {
+ const [_contractAddress, _type, _contract, _wallet, _collectionAddress, _protocolAddress] = queryKey
+ const messages = contract?.use(contractAddress)
+ const res = await resolveAddress(_collectionAddress, wallet).then(async (resolvedAddress) => {
+ const result = await dispatchQuery({
+ messages,
+ type,
+ collection: resolvedAddress,
+ protocol: _protocolAddress,
+ })
+ return result
+ })
+ return res
+ },
+ {
+ placeholderData: null,
+ onError: (error: any) => {
+ toast.error(error.message, { style: { maxWidth: 'none' } })
+ },
+ enabled: Boolean(contractAddress && contract && wallet),
+ },
+ )
+
+ 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(RoyaltyRegistryQueryPage, { center: false })
diff --git a/utils/constants.ts b/utils/constants.ts
index 4911df1..a115208 100644
--- a/utils/constants.ts
+++ b/utils/constants.ts
@@ -40,6 +40,7 @@ export const OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS =
process.env.NEXT_PUBLIC_OPEN_EDITION_UPDATABLE_IBC_FRNZ_FACTORY_ADDRESS
export const OPEN_EDITION_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_OPEN_EDITION_MINTER_CODE_ID, 10)
export const SG721_NAME_ADDRESS = process.env.NEXT_PUBLIC_SG721_NAME_ADDRESS
+export const ROYALTY_REGISTRY_ADDRESS = process.env.NEXT_PUBLIC_ROYALTY_REGISTRY_ADDRESS
export const BASE_MINTER_CODE_ID = parseInt(process.env.NEXT_PUBLIC_VENDING_MINTER_CODE_ID, 10)
export const BADGE_HUB_CODE_ID = parseInt(process.env.NEXT_PUBLIC_BADGE_HUB_CODE_ID, 10)
export const BADGE_HUB_ADDRESS = process.env.NEXT_PUBLIC_BADGE_HUB_ADDRESS