diff --git a/.changeset/twelve-poets-chew.md b/.changeset/twelve-poets-chew.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/twelve-poets-chew.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/apps/rwa-demo/src/app/(app)/(isAgent)/layout.tsx b/packages/apps/rwa-demo/src/app/(app)/(isAgent)/layout.tsx new file mode 100644 index 0000000000..cc39ec57a6 --- /dev/null +++ b/packages/apps/rwa-demo/src/app/(app)/(isAgent)/layout.tsx @@ -0,0 +1,15 @@ +'use client'; +import { useAccount } from '@/hooks/account'; +import type React from 'react'; + +const AgentLayout = ({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) => { + const { isAgent, account } = useAccount(); + if (!account && !isAgent) return null; + return children; +}; + +export default AgentLayout; diff --git a/packages/apps/rwa-demo/src/app/(app)/page.tsx b/packages/apps/rwa-demo/src/app/(app)/page.tsx index 9d1d45835d..5371b74110 100644 --- a/packages/apps/rwa-demo/src/app/(app)/page.tsx +++ b/packages/apps/rwa-demo/src/app/(app)/page.tsx @@ -1,62 +1,17 @@ 'use client'; -import { AddAgentForm } from '@/components/AddAgentForm/AddAgentForm'; -import { AgentsList } from '@/components/AgentsList/AgentsList'; -import { InitTokenForm } from '@/components/InitTokenForm/InitTokenForm'; -import { SideBarBreadcrumbs } from '@/components/SideBarBreadcrumbs/SideBarBreadcrumbs'; -import { MonoAdd } from '@kadena/kode-icons'; -import { Button, Stack } from '@kadena/kode-ui'; -import { SideBarBreadcrumbsItem, useLayout } from '@kadena/kode-ui/patterns'; -import { useState } from 'react'; -const Home = () => { - const { setIsRightAsideExpanded, isRightAsideExpanded } = useLayout(); - const [hasOpenInitForm, setHasOpenInitForm] = useState(false); - const [hasOpenAgentForm, setHasOpenAgentForm] = useState(false); - - const handleAddAgent = () => { - setIsRightAsideExpanded(true); - setHasOpenAgentForm(true); - }; +import { AgentRootPage } from '@/components/HomePage/AgentRootPage'; +import { OwnerRootPage } from '@/components/HomePage/OwnerRootPage'; +import { useAccount } from '@/hooks/account'; - const handleInitToken = () => { - setIsRightAsideExpanded(true); - setHasOpenInitForm(true); - }; +const Home = () => { + const { isAgent } = useAccount(); return ( - - - Tokens - - {isRightAsideExpanded && hasOpenAgentForm && ( - { - setIsRightAsideExpanded(false); - setHasOpenAgentForm(false); - }} - /> - )} - {isRightAsideExpanded && hasOpenInitForm && ( - { - setIsRightAsideExpanded(false); - setHasOpenInitForm(false); - }} - /> - )} - - - - - - - - - + <> + {!isAgent && } + {isAgent && } + ); }; diff --git a/packages/apps/rwa-demo/src/app/Providers.tsx b/packages/apps/rwa-demo/src/app/Providers.tsx index e0ed01ff47..46c682d70f 100644 --- a/packages/apps/rwa-demo/src/app/Providers.tsx +++ b/packages/apps/rwa-demo/src/app/Providers.tsx @@ -1,6 +1,7 @@ import { AccountProvider } from '@/components/AccountProvider/AccountProvider'; import { AssetProvider } from '@/components/AssetProvider/AssetProvider'; import { NetworkProvider } from '@/components/NetworkProvider/NetworkProvider'; +import { TransactionsProvider } from '@/components/TransactionsProvider/TransactionsProvider'; import { MediaContextProvider } from '@kadena/kode-ui'; import { LayoutProvider } from '@kadena/kode-ui/patterns'; import { darkThemeClass } from '@kadena/kode-ui/styles'; @@ -22,7 +23,9 @@ export const Providers: FC = ({ children }) => { - {children} + + {children} + diff --git a/packages/apps/rwa-demo/src/components/AccountProvider/AccountProvider.tsx b/packages/apps/rwa-demo/src/components/AccountProvider/AccountProvider.tsx index 9d414540d2..0a580c4570 100644 --- a/packages/apps/rwa-demo/src/components/AccountProvider/AccountProvider.tsx +++ b/packages/apps/rwa-demo/src/components/AccountProvider/AccountProvider.tsx @@ -1,7 +1,8 @@ 'use client'; +import { useNetwork } from '@/hooks/networks'; +import { isAgent } from '@/services/isAgent'; import { getAccountCookieName } from '@/utils/getAccountCookieName'; import type { ICommand, IUnsignedCommand } from '@kadena/client'; -import type { ConnectedAccount } from '@kadena/spirekey-sdk'; import { useRouter } from 'next/navigation'; import type { FC, PropsWithChildren } from 'react'; import { createContext, useCallback, useEffect, useState } from 'react'; @@ -13,12 +14,13 @@ interface IAccountError { } export interface IAccountContext { - account?: ConnectedAccount; + account?: IWalletAccount; error?: IAccountError; isMounted: boolean; login: () => void; logout: () => void; sign: (tx: IUnsignedCommand) => Promise; + isAgent: boolean; } export const AccountContext = createContext({ @@ -27,13 +29,26 @@ export const AccountContext = createContext({ login: () => {}, logout: () => {}, sign: async () => undefined, + isAgent: false, }); export const AccountProvider: FC = ({ children }) => { - const [account, setAccount] = useState(); + const [account, setAccount] = useState(); const [isMounted, setIsMounted] = useState(false); + const [isAgentState, setIsAgentState] = useState(false); + + const { activeNetwork } = useNetwork(); const router = useRouter(); + const checkIsAgent = async (account: IWalletAccount) => { + const resIsAgent = await isAgent( + { agent: account.address }, + activeNetwork, + account, + ); + setIsAgentState(!!resIsAgent); + }; + const login = useCallback(async () => { const { message, focus, close } = await getWalletConnection(); focus(); @@ -73,6 +88,16 @@ export const AccountProvider: FC = ({ children }) => { setIsMounted(true); }, []); + useEffect(() => { + if (!account) { + setIsAgentState(false); + return; + } + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + checkIsAgent(account); + }, [account]); + const sign = async (tx: IUnsignedCommand): Promise => { const { message, close } = await getWalletConnection(); const response = await message('SIGN_REQUEST', tx as any); @@ -87,7 +112,7 @@ export const AccountProvider: FC = ({ children }) => { return ( {children} diff --git a/packages/apps/rwa-demo/src/components/AddAgentForm/AddAgentForm.tsx b/packages/apps/rwa-demo/src/components/AddAgentForm/AddAgentForm.tsx index afa05f9dda..f137abbcc7 100644 --- a/packages/apps/rwa-demo/src/components/AddAgentForm/AddAgentForm.tsx +++ b/packages/apps/rwa-demo/src/components/AddAgentForm/AddAgentForm.tsx @@ -1,5 +1,6 @@ import { useAccount } from '@/hooks/account'; import { useNetwork } from '@/hooks/networks'; +import { useTransactions } from '@/hooks/transactions'; import type { IAddAgentProps } from '@/services/addAgent'; import { addAgent } from '@/services/addAgent'; import { getClient } from '@/utils/client'; @@ -27,6 +28,7 @@ interface IProps { export const AddAgentForm: FC = ({ onClose }) => { const { activeNetwork } = useNetwork(); const { account, sign } = useAccount(); + const { addTransaction } = useTransactions(); const [openModal, setOpenModal] = useState(false); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, setError] = useState(null); @@ -46,9 +48,12 @@ export const AddAgentForm: FC = ({ onClose }) => { const client = getClient(); const res = await client.submit(signedTransaction); - console.log(res); - await client.listen(res); + addTransaction({ + ...res, + type: 'ADDAGENT', + data: { ...res, ...data }, + }); console.log('DONE'); } catch (e: any) { setError(e?.message || e); diff --git a/packages/apps/rwa-demo/src/components/AddInvestorForm/AddInvestorForm.tsx b/packages/apps/rwa-demo/src/components/AddInvestorForm/AddInvestorForm.tsx new file mode 100644 index 0000000000..a490491571 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/AddInvestorForm/AddInvestorForm.tsx @@ -0,0 +1,75 @@ +import { useAccount } from '@/hooks/account'; +import { useNetwork } from '@/hooks/networks'; +import type { IRegisterIdentityProps } from '@/services/registerIdentity'; +import { registerIdentity } from '@/services/registerIdentity'; +import { getClient } from '@/utils/client'; +import { Button, TextField } from '@kadena/kode-ui'; +import { + RightAside, + RightAsideContent, + RightAsideFooter, + RightAsideHeader, +} from '@kadena/kode-ui/patterns'; +import type { FC } from 'react'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; + +interface IProps { + onClose: () => void; +} + +export const AddInvestorForm: FC = ({ onClose }) => { + const { activeNetwork } = useNetwork(); + const { account, sign } = useAccount(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, setError] = useState(null); + const { register, handleSubmit } = useForm({ + defaultValues: { + investor: '', + }, + }); + + const onSubmit = async (data: IRegisterIdentityProps) => { + const newData: IRegisterIdentityProps = { ...data, agent: account! }; + setError(null); + try { + const tx = await registerIdentity(newData, activeNetwork); + + const signedTransaction = await sign(tx); + if (!signedTransaction) return; + + const client = getClient(); + const res = await client.submit(signedTransaction); + console.log(res); + + await client.listen(res); + console.log('DONE'); + } catch (e: any) { + setError(e?.message || e); + } + + onClose(); + }; + + return ( + <> + +
+ + + + + + + + + +
+ + ); +}; diff --git a/packages/apps/rwa-demo/src/components/AgentsList/AgentsList.tsx b/packages/apps/rwa-demo/src/components/AgentsList/AgentsList.tsx index a7d461182d..4a1672a3f4 100644 --- a/packages/apps/rwa-demo/src/components/AgentsList/AgentsList.tsx +++ b/packages/apps/rwa-demo/src/components/AgentsList/AgentsList.tsx @@ -1,17 +1,59 @@ +import { useAccount } from '@/hooks/account'; import { useGetAgents } from '@/hooks/getAgents'; -import { CompactTable } from '@kadena/kode-ui/patterns'; +import { useNetwork } from '@/hooks/networks'; +import { removeAgent } from '@/services/removeAgent'; +import { getClient } from '@/utils/client'; +import { MonoDelete } from '@kadena/kode-icons'; +import { Button } from '@kadena/kode-ui'; +import { CompactTable, CompactTableFormatters } from '@kadena/kode-ui/patterns'; import type { FC } from 'react'; export const AgentsList: FC = () => { const { data } = useGetAgents(); + const { account, sign } = useAccount(); + const { activeNetwork } = useNetwork(); - console.log(data); + const handleDelete = async (accountName: any) => { + try { + const tx = await removeAgent( + { agent: accountName }, + activeNetwork, + account!, + ); + const signedTransaction = await sign(tx); + if (!signedTransaction) return; + + const client = getClient(); + const res = await client.submit(signedTransaction); + + await client.listen(res); + console.log('DONE'); + } catch (e: any) {} + }; + + console.log({ data }); return ( } onPress={handleDelete} /> + ), + }), + }, ]} data={data} /> diff --git a/packages/apps/rwa-demo/src/components/AssetProvider/AssetProvider.tsx b/packages/apps/rwa-demo/src/components/AssetProvider/AssetProvider.tsx index 904b3e11b6..6db362792e 100644 --- a/packages/apps/rwa-demo/src/components/AssetProvider/AssetProvider.tsx +++ b/packages/apps/rwa-demo/src/components/AssetProvider/AssetProvider.tsx @@ -1,6 +1,8 @@ 'use client'; +import { useNetwork } from '@/hooks/networks'; +import { paused } from '@/services/paused'; import type { FC, PropsWithChildren } from 'react'; -import { createContext, useState } from 'react'; +import { createContext, useEffect, useState } from 'react'; interface IAsset { name: string; @@ -8,15 +10,37 @@ interface IAsset { export interface IAssetContext { asset?: IAsset; + isPaused: boolean; } -export const AssetContext = createContext({}); +export const AssetContext = createContext({ + isPaused: false, +}); export const AssetProvider: FC = ({ children }) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [asset, setAsset] = useState(); + const { activeNetwork } = useNetwork(); + const [isPaused, setIsPaused] = useState(false); + + const checkIsPaused = async () => { + const res = await paused(activeNetwork); + setIsPaused(!!res); + }; + + useEffect(() => { + if (!asset) { + setIsPaused(false); + return; + } + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + checkIsPaused(); + }, [asset]); return ( - {children} + + {children} + ); }; diff --git a/packages/apps/rwa-demo/src/components/HomePage/AgentRootPage.tsx b/packages/apps/rwa-demo/src/components/HomePage/AgentRootPage.tsx new file mode 100644 index 0000000000..014b7c9395 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/HomePage/AgentRootPage.tsx @@ -0,0 +1,41 @@ +import { SideBarBreadcrumbs } from '@/components/SideBarBreadcrumbs/SideBarBreadcrumbs'; +import { useAccount } from '@/hooks/account'; +import { MonoAdd } from '@kadena/kode-icons'; +import { Button, Stack } from '@kadena/kode-ui'; +import { useLayout } from '@kadena/kode-ui/patterns'; +import type { FC } from 'react'; +import { useState } from 'react'; +import { AddInvestorForm } from '../AddInvestorForm/AddInvestorForm'; + +export const AgentRootPage: FC = () => { + const { isAgent, account } = useAccount(); + const { setIsRightAsideExpanded, isRightAsideExpanded } = useLayout(); + const [hasOpenInvestorForm, setHasOpenInvestorForm] = useState(false); + + const handleAddInvestor = () => { + setIsRightAsideExpanded(true); + setHasOpenInvestorForm(true); + }; + + if (!isAgent) return null; + + return ( + + agent: {account?.address} + + {isRightAsideExpanded && hasOpenInvestorForm && ( + { + setIsRightAsideExpanded(false); + setHasOpenInvestorForm(false); + }} + /> + )} + + + + + ); +}; diff --git a/packages/apps/rwa-demo/src/components/HomePage/OwnerRootPage.tsx b/packages/apps/rwa-demo/src/components/HomePage/OwnerRootPage.tsx new file mode 100644 index 0000000000..2612e10a89 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/HomePage/OwnerRootPage.tsx @@ -0,0 +1,89 @@ +'use client'; +import { AddAgentForm } from '@/components/AddAgentForm/AddAgentForm'; +import { AgentsList } from '@/components/AgentsList/AgentsList'; +import { InitTokenForm } from '@/components/InitTokenForm/InitTokenForm'; +import { SetComplianceForm } from '@/components/SetComplianceForm/SetComplianceForm'; +import { SideBarBreadcrumbs } from '@/components/SideBarBreadcrumbs/SideBarBreadcrumbs'; +import { MonoAdd } from '@kadena/kode-icons'; +import { Button, Stack } from '@kadena/kode-ui'; +import { useLayout } from '@kadena/kode-ui/patterns'; +import { useState } from 'react'; +import { PauseForm } from '../PauseForm/PauseForm'; + +export const OwnerRootPage = () => { + const { setIsRightAsideExpanded, isRightAsideExpanded } = useLayout(); + + const [hasOpenInitForm, setHasOpenInitForm] = useState(false); + const [hasOpenAgentForm, setHasOpenAgentForm] = useState(false); + const [hasOpenComplianceForm, setHasOpenComplianceForm] = useState(false); + + const handleAddAgent = () => { + setIsRightAsideExpanded(true); + setHasOpenInitForm(false); + setHasOpenAgentForm(true); + setHasOpenComplianceForm(false); + }; + + const handleInitToken = () => { + setIsRightAsideExpanded(true); + setHasOpenInitForm(true); + setHasOpenComplianceForm(false); + setHasOpenAgentForm(false); + }; + + const handleComplianceForm = () => { + setIsRightAsideExpanded(true); + setHasOpenComplianceForm(true); + setHasOpenInitForm(false); + setHasOpenAgentForm(false); + }; + + return ( + + + + {isRightAsideExpanded && hasOpenComplianceForm && ( + { + setIsRightAsideExpanded(false); + setHasOpenAgentForm(false); + }} + /> + )} + {isRightAsideExpanded && hasOpenAgentForm && ( + { + setIsRightAsideExpanded(false); + setHasOpenAgentForm(false); + }} + /> + )} + {isRightAsideExpanded && hasOpenInitForm && ( + { + setIsRightAsideExpanded(false); + setHasOpenInitForm(false); + }} + /> + )} + + + + + + + + + + + + + + ); +}; diff --git a/packages/apps/rwa-demo/src/components/PauseForm/PauseForm.tsx b/packages/apps/rwa-demo/src/components/PauseForm/PauseForm.tsx new file mode 100644 index 0000000000..758fd02647 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/PauseForm/PauseForm.tsx @@ -0,0 +1,39 @@ +import { useAccount } from '@/hooks/account'; +import { useAsset } from '@/hooks/asset'; +import { useNetwork } from '@/hooks/networks'; +import { togglePause } from '@/services/togglePause'; +import { getClient } from '@/utils/client'; +import { MonoPause, MonoPlayArrow } from '@kadena/kode-icons'; +import { Button } from '@kadena/kode-ui'; +import type { FC } from 'react'; + +export const PauseForm: FC = () => { + const { isPaused } = useAsset(); + const { account, sign } = useAccount(); + const { activeNetwork } = useNetwork(); + + const handlePauseToggle = async () => { + try { + const transaction = await togglePause(isPaused, activeNetwork, account!); + + const signedTransaction = await sign(transaction); + if (!signedTransaction) return; + + const client = getClient(); + const res = await client.submit(signedTransaction); + console.log(res); + + await client.listen(res); + console.log('DONE'); + } catch (e: any) {} + }; + + return ( + + ); +}; diff --git a/packages/apps/rwa-demo/src/components/SetComplianceForm/SetComplianceForm.tsx b/packages/apps/rwa-demo/src/components/SetComplianceForm/SetComplianceForm.tsx new file mode 100644 index 0000000000..fee02fe964 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/SetComplianceForm/SetComplianceForm.tsx @@ -0,0 +1,77 @@ +import { useAccount } from '@/hooks/account'; +import { useNetwork } from '@/hooks/networks'; +import type { ISetComplianceProps } from '@/services/setCompliance'; +import { setCompliance } from '@/services/setCompliance'; +import { getClient } from '@/utils/client'; +import { Button, NumberField } from '@kadena/kode-ui'; +import { + RightAside, + RightAsideContent, + RightAsideFooter, + RightAsideHeader, +} from '@kadena/kode-ui/patterns'; +import type { FC } from 'react'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; + +interface IProps { + onClose: () => void; +} + +export const SetComplianceForm: FC = ({ onClose }) => { + const { activeNetwork } = useNetwork(); + const { account, sign } = useAccount(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, setError] = useState(null); + const { register, handleSubmit } = useForm({ + defaultValues: { + maxBalance: 0, + maxSupply: 0, + }, + }); + + const onSubmit = async (data: ISetComplianceProps) => { + setError(null); + try { + const tx = await setCompliance(data, activeNetwork, account!); + + const signedTransaction = await sign(tx); + if (!signedTransaction) return; + + const client = getClient(); + const res = await client.submit(signedTransaction); + + await client.listen(res); + console.log('DONE'); + } catch (e: any) { + setError(e?.message || e); + } + onClose(); + }; + + return ( + <> + +
+ + + + + + + + + + +
+ + ); +}; diff --git a/packages/apps/rwa-demo/src/components/TransactionsProvider/TransactionsProvider.tsx b/packages/apps/rwa-demo/src/components/TransactionsProvider/TransactionsProvider.tsx new file mode 100644 index 0000000000..83986340ff --- /dev/null +++ b/packages/apps/rwa-demo/src/components/TransactionsProvider/TransactionsProvider.tsx @@ -0,0 +1,71 @@ +'use client'; +import { useNetwork } from '@/hooks/networks'; +import { getClient } from '@/utils/client'; +import type { ICommandResult } from '@kadena/client'; +import type { FC, PropsWithChildren } from 'react'; +import { createContext, useCallback, useState } from 'react'; + +export interface ITransaction { + requestKey: string; + type: string; + data: Record; + listener?: Promise; + result?: boolean; +} + +export interface ITransactionsContext { + transactions: Record; + addTransaction: (request: ITransaction) => void; + getTransactions: (type: string) => ITransaction[]; +} + +export const TransactionsContext = createContext({ + transactions: {}, + addTransaction: (request) => {}, + getTransactions: () => [], +}); + +export const TransactionsProvider: FC = ({ children }) => { + const [transactions, setTransactions] = useState< + Record + >({}); + + const { activeNetwork } = useNetwork(); + + const addListener = useCallback((requestKey: string) => { + return getClient() + .listen({ + requestKey, + chainId: activeNetwork.chainId, + networkId: activeNetwork.networkId, + }) + .catch(console.log); + }, []); + + const getTransactions = (type: string) => { + return Object.entries(transactions) + .map(([key, val]) => val) + .filter((val) => val.type === type); + }; + + const addTransaction = (request: ITransaction) => { + if (transactions[request.requestKey]) { + console.error('requestKey already exists', request.requestKey); + return; + } + + const data = { ...request }; + data.listener = addListener(data.requestKey); + setTransactions((v) => { + return { ...v, [request.requestKey]: { ...data } }; + }); + }; + + return ( + + {children} + + ); +}; diff --git a/packages/apps/rwa-demo/src/components/TransactionsProvider/utils.ts b/packages/apps/rwa-demo/src/components/TransactionsProvider/utils.ts new file mode 100644 index 0000000000..c3a79ba1e2 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/TransactionsProvider/utils.ts @@ -0,0 +1,115 @@ +import type { ChainId } from '@kadena/client'; + +interface ResponseType { + id: string; + type: string; + payload: unknown; + error: unknown; +} + +export interface IWalletAccount { + address: string; + keyset: { + guard: { keys: string[]; pred: 'keys-all' | 'keys-any' | 'keys-2' }; + }; + alias: string; + contract: string; + chains: Array<{ chainId: ChainId; balance: string }>; + overallBalance: string; +} + +export interface IState { + profile: { + name: string; + accentColor: string; + uuid: string; + }; + accounts: IWalletAccount[]; +} + +const walletOrigin = () => + (window as any).walletUrl || 'https://wallet.kadena.io'; +const walletUrl = () => `${walletOrigin()}`; +const walletName = 'Dev-Wallet'; +const appName = 'Dev Wallet Example'; + +export const sleep = (time: number) => + new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, time); + }); + +const communicate = + (client: Window, server: Window) => + (type: string, payload: Record): Promise => { + const id = crypto.randomUUID(); + return new Promise((resolve) => { + const handler = (event: MessageEvent) => { + if (event.data && event.data.id === id) { + client.removeEventListener('message', handler); + resolve(event.data); + server.blur(); + window.focus(); + } + }; + client.addEventListener('message', handler); + server.postMessage({ payload, id, type }, walletOrigin()); + }); + }; + +let walletGlobal: Window | null = null; +export async function getWalletConnection(page: string = '') { + if (walletGlobal && !walletGlobal.closed) { + return { + message: communicate(window, walletGlobal), + focus: () => walletGlobal?.focus(), + close: () => walletGlobal?.close(), + }; + } + + const wallet = window.open('', walletName, 'width=800,height=800'); + + if (!wallet) { + throw new Error('POPUP_BLOCKED'); + } + const message = communicate(window, wallet); + const waitForWallet = async () => { + for (let i = 0; i < 50; i++) { + try { + await Promise.race([ + message('GET_STATUS', { + name: appName, + }), + sleep(300).then(() => { + throw new Error('TIMEOUT'); + }), + ]); + } catch (e) { + console.log('error', e); + continue; + } + console.log('wallet is ready'); + break; + } + }; + await Promise.race([ + message('GET_STATUS', { + name: appName, + }), + sleep(300).then(() => { + throw new Error('TIMEOUT'); + }), + ]).catch(async () => { + wallet.location.href = walletUrl(); + // todo: replace this by a better way to know when the wallet is ready + return waitForWallet(); + }); + // eslint-disable-next-line require-atomic-updates + walletGlobal = wallet; + return { + message, + focus: () => walletGlobal?.focus(), + close: () => walletGlobal?.close(), + }; +} diff --git a/packages/apps/rwa-demo/src/hooks/getAgents.ts b/packages/apps/rwa-demo/src/hooks/getAgents.ts index 60b1ab1bae..ead143bbfb 100644 --- a/packages/apps/rwa-demo/src/hooks/getAgents.ts +++ b/packages/apps/rwa-demo/src/hooks/getAgents.ts @@ -1,7 +1,10 @@ import type { Exact, Scalars } from '@/__generated__/sdk'; import { useEventsQuery } from '@/__generated__/sdk'; +import type { ITransaction } from '@/components/TransactionsProvider/TransactionsProvider'; import { coreEvents } from '@/services/graph/agent.graph'; import type * as Apollo from '@apollo/client'; +import { useEffect, useState } from 'react'; +import { useTransactions } from './transactions'; export type EventQueryVariables = Exact<{ qualifiedName: Scalars['String']['input']; @@ -14,24 +17,49 @@ export const getEventsDocument = ( ): Apollo.DocumentNode => coreEvents; export const useGetAgents = () => { + const [innerData, setInnerData] = useState([]); + const { getTransactions, transactions } = useTransactions(); const { loading, data, error } = useEventsQuery({ variables: { qualifiedName: 'RWA.agent-role.AGENT-ADDED', }, }); - console.log(data?.events.edges); - const agents = - data?.events.edges.map((edge: any) => { - console.log(edge); - + const initInnerData = async (transactions: ITransaction[]) => { + const promises = transactions.map(async (t) => { + const result = await t.listener; return { - blockHeight: edge.node.block.height, - chainId: edge.node.chainId, - requestKey: edge.node.requestKey, - accountName: JSON.parse(edge.node.parameters)[0], + blockHeight: result?.metaData?.blockHeight, + chainId: t.data.chainId, + requestKey: t.requestKey, + accountName: t.data.agent, + result: result?.result.status === 'success', }; - }) ?? []; + }); + + const promiseResults = await Promise.all(promises); + + const agents = + data?.events.edges.map((edge: any) => { + return { + blockHeight: edge.node.block.height, + chainId: edge.node.chainId, + requestKey: edge.node.requestKey, + accountName: JSON.parse(edge.node.parameters)[0], + result: true, + }; + }) ?? []; + + console.log({ agents }); + + setInnerData([...agents, ...promiseResults]); + }; + + useEffect(() => { + const tx = getTransactions('ADDAGENT'); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + initInnerData(tx); + }, [transactions, data]); - return { loading, data: agents, error }; + return { loading, data: innerData, error }; }; diff --git a/packages/apps/rwa-demo/src/hooks/transactions.ts b/packages/apps/rwa-demo/src/hooks/transactions.ts new file mode 100644 index 0000000000..0c9756fae6 --- /dev/null +++ b/packages/apps/rwa-demo/src/hooks/transactions.ts @@ -0,0 +1,7 @@ +import type { ITransactionsContext } from '@/components/TransactionsProvider/TransactionsProvider'; +import { TransactionsContext } from '@/components/TransactionsProvider/TransactionsProvider'; + +import { useContext } from 'react'; + +export const useTransactions = (): ITransactionsContext => + useContext(TransactionsContext); diff --git a/packages/apps/rwa-demo/src/services/addAgent.ts b/packages/apps/rwa-demo/src/services/addAgent.ts index 53b7a52007..49063a0211 100644 --- a/packages/apps/rwa-demo/src/services/addAgent.ts +++ b/packages/apps/rwa-demo/src/services/addAgent.ts @@ -1,7 +1,6 @@ +import type { IWalletAccount } from '@/components/AccountProvider/utils'; import type { INetwork } from '@/components/NetworkProvider/NetworkProvider'; -import { ADMIN } from '@/constants'; import { Pact } from '@kadena/client'; -import type { ConnectedAccount } from '@kadena/spirekey-sdk'; export interface IAddAgentProps { agent: string; @@ -11,33 +10,20 @@ const createPubKeyFromAccount = (account: string): string => { return account.replace('k:', '').replace('r:', ''); }; -// const doSubmit = async (txArg: any) => { -// const client = getClient(); - -// // try { -// console.log(1); -// const res = await client.submit(txArg); -// console.log({ res }); -// // return; -// // } catch (err: any) { -// // console.log(err); -// // } -// }; - export const addAgent = async ( data: IAddAgentProps, network: INetwork, - account: ConnectedAccount, + account: IWalletAccount, ) => { return Pact.builder .execution( `(RWA.agent-role.add-agent (read-string 'agent) (read-keyset 'agent_guard))`, ) .setMeta({ - senderAccount: ADMIN.account, + senderAccount: account.address, chainId: network.chainId, }) - .addSigner(ADMIN.publicKey, (withCap) => [ + .addSigner(account.keyset.guard.keys[0], (withCap) => [ withCap(`RWA.agent-role.ONLY-OWNER`), withCap(`coin.GAS`), ]) diff --git a/packages/apps/rwa-demo/src/services/fetchAgents.ts b/packages/apps/rwa-demo/src/services/fetchAgents.ts deleted file mode 100644 index ebe79b956a..0000000000 --- a/packages/apps/rwa-demo/src/services/fetchAgents.ts +++ /dev/null @@ -1 +0,0 @@ -export const fetchAgents = () => {}; diff --git a/packages/apps/rwa-demo/src/services/initToken.ts b/packages/apps/rwa-demo/src/services/initToken.ts index f8fe514052..4c5fa994a3 100644 --- a/packages/apps/rwa-demo/src/services/initToken.ts +++ b/packages/apps/rwa-demo/src/services/initToken.ts @@ -1,9 +1,7 @@ +import type { IWalletAccount } from '@/components/AccountProvider/utils'; import type { INetwork } from '@/components/NetworkProvider/NetworkProvider'; import { ADMIN } from '@/constants'; -import { getPubKey } from '@/utils/getPubKey'; import { Pact } from '@kadena/client'; -import type { ConnectedAccount } from '@kadena/spirekey-sdk'; -import { sign } from '@kadena/spirekey-sdk'; export interface IInitTokenProps { name: string; @@ -11,27 +9,12 @@ export interface IInitTokenProps { kadenaId: string; } -// const doSubmit = async (txArg: any) => { -// const client = getClient(); - -// // try { -// console.log(1); - -// const res = await client.submit(txArg); -// console.log({ res }); -// // return; -// // } catch (err: any) { -// // console.log(err); -// // } -// }; - export const initToken = async ( data: IInitTokenProps, network: INetwork, - owner: ConnectedAccount, + owner: IWalletAccount, ) => { - console.log({ owner }); - const transaction = Pact.builder + return Pact.builder .execution( ` (RWA.agent-role.init (read-keyset 'owner_guard)) @@ -41,12 +24,12 @@ export const initToken = async ( `, ) .setMeta({ - senderAccount: owner.accountName, + senderAccount: owner.address, chainId: network.chainId, }) .addSigner( { - pubKey: getPubKey(owner!), + pubKey: owner.keyset.guard.keys[0], scheme: 'WebAuthn', }, (withCap) => [], @@ -58,16 +41,4 @@ export const initToken = async ( }) .setNetworkId(network.networkId) .createTransaction(); - - console.log({ transaction }); - console.log({ cmd: JSON.parse(transaction.cmd) }); - const { transactions } = await sign([transaction], [owner]); - // console.log(res); - console.log({ transactions }); - // transactions.map(async (t) => { - // // should perform check to see if all sigs are present - - // console.log({ t }); - // await doSubmit(t); - // }); }; diff --git a/packages/apps/rwa-demo/src/services/isAgent.ts b/packages/apps/rwa-demo/src/services/isAgent.ts new file mode 100644 index 0000000000..e7c8550b18 --- /dev/null +++ b/packages/apps/rwa-demo/src/services/isAgent.ts @@ -0,0 +1,34 @@ +import type { IWalletAccount } from '@/components/AccountProvider/utils'; +import type { INetwork } from '@/components/NetworkProvider/NetworkProvider'; +import { ADMIN } from '@/constants'; +import { getClient } from '@/utils/client'; +import { Pact } from '@kadena/client'; + +export interface IIsAgentProps { + agent: string; +} + +export const isAgent = async ( + data: IIsAgentProps, + network: INetwork, + account: IWalletAccount, +) => { + const client = getClient(); + + const transaction = Pact.builder + .execution(`(RWA.agent-role.is-agent (read-string 'agent))`) + .setMeta({ + senderAccount: ADMIN.account, + chainId: network.chainId, + }) + .addData('agent', data.agent) + .setNetworkId(network.networkId) + .createTransaction(); + + const { result } = await client.local(transaction, { + preflight: false, + signatureVerification: false, + }); + + return result.status === 'success' ? result.data : undefined; +}; diff --git a/packages/apps/rwa-demo/src/services/paused.ts b/packages/apps/rwa-demo/src/services/paused.ts new file mode 100644 index 0000000000..86aaf7b508 --- /dev/null +++ b/packages/apps/rwa-demo/src/services/paused.ts @@ -0,0 +1,28 @@ +import type { INetwork } from '@/components/NetworkProvider/NetworkProvider'; +import { ADMIN } from '@/constants'; +import { getClient } from '@/utils/client'; +import { Pact } from '@kadena/client'; + +export interface IIsAgentProps { + agent: string; +} + +export const paused = async (network: INetwork) => { + const client = getClient(); + + const transaction = Pact.builder + .execution(`(RWA.mvp-token.paused)`) + .setMeta({ + senderAccount: ADMIN.account, + chainId: network.chainId, + }) + .setNetworkId(network.networkId) + .createTransaction(); + + const { result } = await client.local(transaction, { + preflight: false, + signatureVerification: false, + }); + + return result.status === 'success' ? result.data : undefined; +}; diff --git a/packages/apps/rwa-demo/src/services/registerIdentity.ts b/packages/apps/rwa-demo/src/services/registerIdentity.ts new file mode 100644 index 0000000000..1ae7885d42 --- /dev/null +++ b/packages/apps/rwa-demo/src/services/registerIdentity.ts @@ -0,0 +1,31 @@ +import type { IWalletAccount } from '@/components/AccountProvider/utils'; +import type { INetwork } from '@/components/NetworkProvider/NetworkProvider'; +import { Pact } from '@kadena/client'; + +export interface IRegisterIdentityProps { + investor: string; + agent: IWalletAccount; +} + +export const registerIdentity = async ( + data: IRegisterIdentityProps, + network: INetwork, +) => { + return Pact.builder + .execution( + `(RWA.identity-registry.register-identity (read-string 'investor) "" 1 (read-string 'agent))`, + ) + .setMeta({ + senderAccount: data.agent.address, + chainId: network.chainId, + }) + .addSigner(data.agent.keyset.guard.keys[0], (withCap) => [ + withCap(`RWA.agent-role.ONLY-AGENT`, [data.agent.address]), + withCap(`coin.GAS`), + ]) + .addData('investor', data.investor) + .addData('agent', data.agent.address) + + .setNetworkId(network.networkId) + .createTransaction(); +}; diff --git a/packages/apps/rwa-demo/src/services/removeAgent.ts b/packages/apps/rwa-demo/src/services/removeAgent.ts new file mode 100644 index 0000000000..69551603b9 --- /dev/null +++ b/packages/apps/rwa-demo/src/services/removeAgent.ts @@ -0,0 +1,28 @@ +import type { IWalletAccount } from '@/components/AccountProvider/utils'; +import type { INetwork } from '@/components/NetworkProvider/NetworkProvider'; +import { ADMIN } from '@/constants'; +import { Pact } from '@kadena/client'; + +export interface IRemoveAgentProps { + agent: string; +} + +export const removeAgent = async ( + data: IRemoveAgentProps, + network: INetwork, + account: IWalletAccount, +) => { + return Pact.builder + .execution(`(RWA.agent-role.remove-agent (read-string 'agent))`) + .setMeta({ + senderAccount: ADMIN.account, + chainId: network.chainId, + }) + .addSigner(ADMIN.publicKey, (withCap) => [ + withCap(`RWA.agent-role.ONLY-OWNER`), + withCap(`coin.GAS`), + ]) + .addData('agent', data.agent) + .setNetworkId(network.networkId) + .createTransaction(); +}; diff --git a/packages/apps/rwa-demo/src/services/setCompliance.ts b/packages/apps/rwa-demo/src/services/setCompliance.ts new file mode 100644 index 0000000000..4f805caf3b --- /dev/null +++ b/packages/apps/rwa-demo/src/services/setCompliance.ts @@ -0,0 +1,29 @@ +import type { IWalletAccount } from '@/components/AccountProvider/utils'; +import type { INetwork } from '@/components/NetworkProvider/NetworkProvider'; +import { ADMIN } from '@/constants'; +import { Pact } from '@kadena/client'; + +export interface ISetComplianceProps { + maxBalance: number; + maxSupply: number; +} + +export const setCompliance = async ( + data: ISetComplianceProps, + network: INetwork, + account: IWalletAccount, +) => { + return Pact.builder + .execution( + ` (RWA.max-balance-compliance.set-max-balance 60.0) + (RWA.max-balance-compliance.set-max-supply 100.0)`, + ) + .setMeta({ + senderAccount: ADMIN.account, + chainId: network.chainId, + }) + .addSigner(ADMIN.publicKey) + + .setNetworkId(network.networkId) + .createTransaction(); +}; diff --git a/packages/apps/rwa-demo/src/services/togglePause.ts b/packages/apps/rwa-demo/src/services/togglePause.ts new file mode 100644 index 0000000000..fe72fabbfb --- /dev/null +++ b/packages/apps/rwa-demo/src/services/togglePause.ts @@ -0,0 +1,38 @@ +import type { IWalletAccount } from '@/components/AccountProvider/utils'; +import type { INetwork } from '@/components/NetworkProvider/NetworkProvider'; +import { ADMIN } from '@/constants'; +import { Pact } from '@kadena/client'; + +export interface IRemoveAgentProps { + agent: string; +} + +export const togglePause = async ( + isPaused: boolean, + network: INetwork, + account: IWalletAccount, +) => { + if (isPaused) { + return Pact.builder + .execution(`(RWA.mvp-token.pause (read-string 'agent))`) + .setMeta({ + senderAccount: ADMIN.account, + chainId: network.chainId, + }) + .addSigner(ADMIN.publicKey, (withCap) => [withCap(`coin.GAS`)]) + .addData('agent', ADMIN.account) + .setNetworkId(network.networkId) + .createTransaction(); + } else { + return Pact.builder + .execution(`(RWA.mvp-token.unpause (read-string 'agent))`) + .setMeta({ + senderAccount: ADMIN.account, + chainId: network.chainId, + }) + .addSigner(ADMIN.publicKey, (withCap) => [withCap(`coin.GAS`)]) + .addData('agent', ADMIN.account) + .setNetworkId(network.networkId) + .createTransaction(); + } +}; diff --git a/packages/libs/kode-ui/src/components/Breadcrumbs/Breadcrumbs.tsx b/packages/libs/kode-ui/src/components/Breadcrumbs/Breadcrumbs.tsx index 3d7a5e0007..321ad4e00e 100644 --- a/packages/libs/kode-ui/src/components/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/libs/kode-ui/src/components/Breadcrumbs/Breadcrumbs.tsx @@ -7,7 +7,7 @@ import { containerClass, navClass } from './Breadcrumbs.css'; import type { IBreadcrumbItemProps } from './BreadcrumbsItem'; export interface IBreadcrumbsProps extends AriaBreadcrumbsProps { - children: + children?: | FunctionComponentElement | FunctionComponentElement[]; icon?: React.ReactElement; diff --git a/packages/libs/kode-ui/src/components/Table/Table.tsx b/packages/libs/kode-ui/src/components/Table/Table.tsx index 88a3f83d77..b902c567b2 100644 --- a/packages/libs/kode-ui/src/components/Table/Table.tsx +++ b/packages/libs/kode-ui/src/components/Table/Table.tsx @@ -48,7 +48,7 @@ export function Table(props: ITableProps) { const alternateRow = index % 2 !== 0; return ( - <> + (props: ITableProps) { - + ); })} diff --git a/packages/libs/kode-ui/src/patterns/CompactTable/CompactTable.stories.tsx b/packages/libs/kode-ui/src/patterns/CompactTable/CompactTable.stories.tsx index ca5bea9d9d..32ce797d31 100644 --- a/packages/libs/kode-ui/src/patterns/CompactTable/CompactTable.stories.tsx +++ b/packages/libs/kode-ui/src/patterns/CompactTable/CompactTable.stories.tsx @@ -1,10 +1,12 @@ +import { MonoDelete } from '@kadena/kode-icons/system'; import type { Meta, StoryObj } from '@storybook/react'; import React from 'react'; import { onLayer2 } from '../../storyDecorators'; -import { MediaContextProvider } from './../../components'; +import { Button, MediaContextProvider } from './../../components'; import type { ICompactTableProps } from './CompactTable'; import { CompactTable } from './CompactTable'; import { CompactTableFormatters } from './TableFormatters'; +import { FormatActions } from './TableFormatters/FormatActions'; const meta: Meta = { title: 'Patterns/CompactTable', @@ -303,3 +305,48 @@ export const FormatWithkeyArray: Story = { ); }, }; + +export const FormatWithAction: Story = { + name: 'Format Action trigger', + args: { + isLoading: false, + }, + render: ({ isLoading }) => { + const callAction = (value: any) => { + alert(value); + }; + return ( + + } onPress={callAction} /> + ), + }), + }, + ]} + data={data} + /> + + ); + }, +}; diff --git a/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableDesktop/CompactTableDesktop.tsx b/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableDesktop/CompactTableDesktop.tsx index 91da507123..fcaac262bc 100644 --- a/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableDesktop/CompactTableDesktop.tsx +++ b/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableDesktop/CompactTableDesktop.tsx @@ -35,8 +35,8 @@ export const CompactTableDesktop: FC = ({ > - {fields.map((field) => ( - + {fields.map((field, idx) => ( + = ({ {data.map((item, idx) => ( - - {fields.map((field) => ( - + + {fields.map((field, innerIdx) => ( + ))} diff --git a/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableDesktop/styles.css.ts b/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableDesktop/styles.css.ts index cca87fce91..0f3a423682 100644 --- a/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableDesktop/styles.css.ts +++ b/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableDesktop/styles.css.ts @@ -5,7 +5,7 @@ export const tableClass = style({ width: '100%', }); -globalStyle(`${tableClass} td span`, { +globalStyle(`${tableClass} td > span`, { display: 'block', alignItems: 'center', contain: 'inline-size', diff --git a/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableMobile/CompactTableMobile.tsx b/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableMobile/CompactTableMobile.tsx index 816275edc6..1046857b4d 100644 --- a/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableMobile/CompactTableMobile.tsx +++ b/packages/libs/kode-ui/src/patterns/CompactTable/CompactTableMobile/CompactTableMobile.tsx @@ -16,8 +16,8 @@ export const CompactTableMobile: FC = ({ }) => { return data.map((item, idx) => (
- {fields.map((field) => ( -
+ {fields.map((field, idx) => ( +
{field.label}
diff --git a/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/FormatActions.tsx b/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/FormatActions.tsx new file mode 100644 index 0000000000..2f64cae463 --- /dev/null +++ b/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/FormatActions.tsx @@ -0,0 +1,17 @@ +import type { ReactElement } from 'react'; +import React from 'react'; +import type { ICompactTableFormatterProps } from './types'; + +export interface IActionProps { + trigger: ReactElement; +} + +export const FormatActions = ({ trigger }: IActionProps) => { + const Component = ({ value }: ICompactTableFormatterProps) => { + return React.cloneElement(trigger, { + ...trigger.props, + onPress: () => trigger.props.onPress(value), + }); + }; + return Component; +}; diff --git a/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/FormatStatus.tsx b/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/FormatStatus.tsx index 15e9664fec..131fbf8b2c 100644 --- a/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/FormatStatus.tsx +++ b/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/FormatStatus.tsx @@ -1,10 +1,20 @@ -import { MonoCheck, MonoClear } from '@kadena/kode-icons/system'; +import { MonoCheck, MonoClear, MonoLoading } from '@kadena/kode-icons/system'; import React from 'react'; +import { Stack } from './../../../components'; +import { loaderClass } from './styles.css'; import type { ICompactTableFormatterProps } from './types'; import { valueToString } from './utils'; export const FormatStatus = () => { - const Component = ({ value }: ICompactTableFormatterProps) => - valueToString(value) ? : ; + const Component = ({ value }: ICompactTableFormatterProps) => { + if (valueToString(value) === undefined) { + return ( + + + + ); + } + return valueToString(value) ? : ; + }; return Component; }; diff --git a/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/index.tsx b/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/index.tsx index b2b62e8cf6..e2f547c4f4 100644 --- a/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/index.tsx +++ b/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/index.tsx @@ -1,4 +1,5 @@ import { FormatAccount } from './FormatAccount'; +import { FormatActions } from './FormatActions'; import { FormatAmount } from './FormatAmount'; import { FormatDefault } from './FormatDefault'; import { FormatJsonParse } from './FormatJsonParse'; @@ -13,6 +14,7 @@ import type { export const CompactTableFormatters = { FormatAccount, + FormatActions, FormatAmount, FormatDefault, FormatLink, diff --git a/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/styles.css.ts b/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/styles.css.ts new file mode 100644 index 0000000000..6634ae2d67 --- /dev/null +++ b/packages/libs/kode-ui/src/patterns/CompactTable/TableFormatters/styles.css.ts @@ -0,0 +1,13 @@ +import { keyframes } from '@vanilla-extract/css'; +import { style } from './../../../styles'; + +const rotate = keyframes({ + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(360deg)' }, +}); + +export const loaderClass = style([ + { + animation: `${rotate} 2s infinite linear `, + }, +]);