From 7d0cbcc4e08437c120af6f0bc1c15b27f4be42b7 Mon Sep 17 00:00:00 2001 From: Steven Date: Mon, 18 Nov 2024 09:13:17 +0100 Subject: [PATCH] feat(rwa): login with devwallet (#2664) --- .changeset/rich-wombats-shop.md | 2 + .changeset/sour-dolphins-push.md | 2 + packages/apps/rwa-demo/codegen-sdk.yml | 10 + packages/apps/rwa-demo/package.json | 10 + .../apps/rwa-demo/src/__generated__/sdk.ts | 1022 +++++++++++++++++ packages/apps/rwa-demo/src/app/(app)/page.tsx | 26 +- .../AccountProvider/AccountProvider.tsx | 46 +- .../src/components/AccountProvider/utils.ts | 115 ++ .../components/AddAgentForm/AddAgentForm.tsx | 73 +- .../src/components/AgentsList/AgentsList.tsx | 19 + .../NetworkProvider/NetworkProvider.tsx | 95 +- packages/apps/rwa-demo/src/hooks/getAgents.ts | 37 + .../apps/rwa-demo/src/services/addAgent.ts | 15 +- .../apps/rwa-demo/src/services/fetchAgents.ts | 1 + .../src/services/graph/agent.graph.ts | 17 + .../services/graph/fragments/events.graph.ts | 14 + .../apps/rwa-demo/src/utils/checkNetwork.ts | 24 + packages/apps/rwa-demo/src/utils/env.ts | 3 + .../kode-ui/src/components/Modal/Modal.css.ts | 4 +- .../SideBarLayout/SideBarLayout.stories.tsx | 24 +- .../src/patterns/SideBarLayout/aside.css.ts | 2 +- pnpm-lock.yaml | 266 ++++- 22 files changed, 1724 insertions(+), 103 deletions(-) create mode 100644 .changeset/rich-wombats-shop.md create mode 100644 .changeset/sour-dolphins-push.md create mode 100644 packages/apps/rwa-demo/codegen-sdk.yml create mode 100644 packages/apps/rwa-demo/src/__generated__/sdk.ts create mode 100644 packages/apps/rwa-demo/src/components/AccountProvider/utils.ts create mode 100644 packages/apps/rwa-demo/src/components/AgentsList/AgentsList.tsx create mode 100644 packages/apps/rwa-demo/src/hooks/getAgents.ts create mode 100644 packages/apps/rwa-demo/src/services/fetchAgents.ts create mode 100644 packages/apps/rwa-demo/src/services/graph/agent.graph.ts create mode 100644 packages/apps/rwa-demo/src/services/graph/fragments/events.graph.ts create mode 100644 packages/apps/rwa-demo/src/utils/checkNetwork.ts diff --git a/.changeset/rich-wombats-shop.md b/.changeset/rich-wombats-shop.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/rich-wombats-shop.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/sour-dolphins-push.md b/.changeset/sour-dolphins-push.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/sour-dolphins-push.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/apps/rwa-demo/codegen-sdk.yml b/packages/apps/rwa-demo/codegen-sdk.yml new file mode 100644 index 0000000000..3f0b027fbe --- /dev/null +++ b/packages/apps/rwa-demo/codegen-sdk.yml @@ -0,0 +1,10 @@ +overwrite: true +# schema: ./node_modules/@kadena/graph/generated-schema.graphql +schema: https://graph.kadena.network/graphql +generates: + ./src/__generated__/sdk.ts: + documents: 'src/**/*.graph.(ts|tsx)' + plugins: + - typescript + - typescript-operations + - typescript-react-apollo diff --git a/packages/apps/rwa-demo/package.json b/packages/apps/rwa-demo/package.json index 620b04e6e8..15c9ce98e1 100644 --- a/packages/apps/rwa-demo/package.json +++ b/packages/apps/rwa-demo/package.json @@ -6,6 +6,7 @@ "build": "next build", "dev": "next dev", "generate": "pactjs contract-generate --contract \"RWA.agent-role\" --chain 0 --network development --api \"http://localhost:8080/chainweb/0.0/development/chain/0/pact\"", + "generate:sdk": "cache-sh -i \"{codegen-sdk.yml,./node_modules/@kadena/graph/generated-schema.graphql,src/**/*.graph.ts,src/__generated__/**}\" -- graphql-codegen --config codegen-sdk.yml", "lint": "pnpm run /^lint:.*/", "lint:fmt": "prettier . --cache --check", "lint:src": "eslint src", @@ -15,9 +16,12 @@ "test:watch": "vitest --coverage" }, "dependencies": { + "@apollo/client": "~3.9.11", + "@graphql-yoga/apollo-link": "~3.3.0", "@hookform/resolvers": "^3.2.0", "@kadena/client": "^1.15.0", "@kadena/cryptography-utils": "workspace:*", + "@kadena/graph": "workspace:*", "@kadena/kode-icons": "workspace:*", "@kadena/kode-ui": "workspace:*", "@kadena/pactjs": "workspace:*", @@ -27,6 +31,9 @@ "@vanilla-extract/next-plugin": "2.4.0", "@vanilla-extract/recipes": "0.5.1", "@vanilla-extract/sprinkles": "1.6.1", + "cache-sh": "^1.2.1", + "graphql": "~16.8.1", + "graphql-ws": "^5.16.0", "next": "14.2.2", "next-themes": "^0.2.1", "react": "^18.2.0", @@ -34,6 +41,9 @@ "react-hook-form": "^7.45.4" }, "devDependencies": { + "@graphql-codegen/cli": "5.0.2", + "@graphql-codegen/typescript-operations": "~4.2.0", + "@graphql-codegen/typescript-react-apollo": "~4.3.0", "@kadena-dev/eslint-config": "workspace:*", "@kadena-dev/lint-package": "workspace:*", "@kadena-dev/shared-config": "workspace:*", diff --git a/packages/apps/rwa-demo/src/__generated__/sdk.ts b/packages/apps/rwa-demo/src/__generated__/sdk.ts new file mode 100644 index 0000000000..850b83ce1e --- /dev/null +++ b/packages/apps/rwa-demo/src/__generated__/sdk.ts @@ -0,0 +1,1022 @@ +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +export type Maybe = T | null; +export type InputMaybe = Maybe; +export type Exact = { [K in keyof T]: T[K] }; +export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; +export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; +export type MakeEmpty = { [_ in K]?: never }; +export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; +const defaultOptions = {} as const; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: { input: string; output: string; } + String: { input: string; output: string; } + Boolean: { input: boolean; output: boolean; } + Int: { input: number; output: number; } + Float: { input: number; output: number; } + BigInt: { input: any; output: any; } + DateTime: { input: any; output: any; } + Decimal: { input: any; output: any; } + PositiveFloat: { input: any; output: any; } +}; + +/** A unit of information that stores a set of verified transactions. */ +export type Block = Node & { + __typename?: 'Block'; + chainId: Scalars['BigInt']['output']; + creationTime: Scalars['DateTime']['output']; + /** The difficulty of the block. */ + difficulty: Scalars['BigInt']['output']; + /** The moment the difficulty is adjusted to maintain a block validation time of 30 seconds. */ + epoch: Scalars['DateTime']['output']; + /** Default page size is 20. */ + events: BlockEventsConnection; + flags: Scalars['Decimal']['output']; + hash: Scalars['String']['output']; + height: Scalars['BigInt']['output']; + id: Scalars['ID']['output']; + minerAccount: FungibleChainAccount; + neighbors: Array; + nonce: Scalars['Decimal']['output']; + parent?: Maybe; + payloadHash: Scalars['String']['output']; + /** The proof of work hash. */ + powHash: Scalars['String']['output']; + target: Scalars['Decimal']['output']; + /** Default page size is 20. */ + transactions: BlockTransactionsConnection; + weight: Scalars['Decimal']['output']; +}; + + +/** A unit of information that stores a set of verified transactions. */ +export type BlockEventsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A unit of information that stores a set of verified transactions. */ +export type BlockTransactionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type BlockEventsConnection = { + __typename?: 'BlockEventsConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type BlockEventsConnectionEdge = { + __typename?: 'BlockEventsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Event; +}; + +/** The neighbor of a block. */ +export type BlockNeighbor = { + __typename?: 'BlockNeighbor'; + chainId: Scalars['String']['output']; + hash: Scalars['String']['output']; +}; + +export type BlockTransactionsConnection = { + __typename?: 'BlockTransactionsConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type BlockTransactionsConnectionEdge = { + __typename?: 'BlockTransactionsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transaction; +}; + +/** The payload of an cont transaction. */ +export type ContinuationPayload = { + __typename?: 'ContinuationPayload'; + /** The environment data made available to the transaction. Formatted as raw JSON. */ + data: Scalars['String']['output']; + /** A unique id when a pact (defpact) is initiated. See the "Pact execution scope and pact-id" explanation in the docs for more information. */ + pactId?: Maybe; + /** The proof provided to continue the cross-chain transaction. */ + proof?: Maybe; + /** Whether or not this transaction can be rolled back. */ + rollback?: Maybe; + /** The step-number when this is an execution of a `defpact`, aka multi-step transaction. */ + step?: Maybe; +}; + +/** An event emitted by the execution of a smart-contract function. */ +export type Event = Node & { + __typename?: 'Event'; + block: Block; + chainId: Scalars['BigInt']['output']; + /** The height of the block where the event was emitted. */ + height: Scalars['BigInt']['output']; + id: Scalars['ID']['output']; + incrementedId: Scalars['Int']['output']; + moduleName: Scalars['String']['output']; + name: Scalars['String']['output']; + /** The order index of this event, in the case that there are multiple events in one transaction. */ + orderIndex: Scalars['BigInt']['output']; + parameterText: Scalars['String']['output']; + parameters?: Maybe; + /** The full eventname, containing module and eventname, e.g. coin.TRANSFER */ + qualifiedName: Scalars['String']['output']; + requestKey: Scalars['String']['output']; + transaction?: Maybe; +}; + +/** The payload of an exec transaction. */ +export type ExecutionPayload = { + __typename?: 'ExecutionPayload'; + /** The Pact expressions executed in this transaction when it is an `exec` transaction. */ + code?: Maybe; + /** The environment data made available to the transaction. Formatted as raw JSON. */ + data: Scalars['String']['output']; +}; + +/** A fungible-specific account. */ +export type FungibleAccount = Node & { + __typename?: 'FungibleAccount'; + accountName: Scalars['String']['output']; + chainAccounts: Array; + fungibleName: Scalars['String']['output']; + id: Scalars['ID']['output']; + totalBalance: Scalars['Decimal']['output']; + /** Default page size is 20. */ + transactions: FungibleAccountTransactionsConnection; + /** Default page size is 20. */ + transfers: FungibleAccountTransfersConnection; +}; + + +/** A fungible-specific account. */ +export type FungibleAccountTransactionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A fungible-specific account. */ +export type FungibleAccountTransfersArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type FungibleAccountTransactionsConnection = { + __typename?: 'FungibleAccountTransactionsConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type FungibleAccountTransactionsConnectionEdge = { + __typename?: 'FungibleAccountTransactionsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transaction; +}; + +export type FungibleAccountTransfersConnection = { + __typename?: 'FungibleAccountTransfersConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type FungibleAccountTransfersConnectionEdge = { + __typename?: 'FungibleAccountTransfersConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transfer; +}; + +/** A fungible specific chain-account. */ +export type FungibleChainAccount = Node & { + __typename?: 'FungibleChainAccount'; + accountName: Scalars['String']['output']; + balance: Scalars['Float']['output']; + chainId: Scalars['String']['output']; + fungibleName: Scalars['String']['output']; + guard: Guard; + id: Scalars['ID']['output']; + /** Transactions that the current account is sender of. Default page size is 20. */ + transactions: FungibleChainAccountTransactionsConnection; + /** Default page size is 20. */ + transfers: FungibleChainAccountTransfersConnection; +}; + + +/** A fungible specific chain-account. */ +export type FungibleChainAccountTransactionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** A fungible specific chain-account. */ +export type FungibleChainAccountTransfersArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type FungibleChainAccountTransactionsConnection = { + __typename?: 'FungibleChainAccountTransactionsConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type FungibleChainAccountTransactionsConnectionEdge = { + __typename?: 'FungibleChainAccountTransactionsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transaction; +}; + +export type FungibleChainAccountTransfersConnection = { + __typename?: 'FungibleChainAccountTransfersConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type FungibleChainAccountTransfersConnectionEdge = { + __typename?: 'FungibleChainAccountTransfersConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transfer; +}; + +export type GasLimitEstimation = { + __typename?: 'GasLimitEstimation'; + amount: Scalars['Int']['output']; + inputType: Scalars['String']['output']; + transaction: Scalars['String']['output']; + usedPreflight: Scalars['Boolean']['output']; + usedSignatureVerification: Scalars['Boolean']['output']; +}; + +/** General information about the graph and chainweb-data. */ +export type GraphConfiguration = { + __typename?: 'GraphConfiguration'; + /** The lowest block-height that is indexed in this endpoint. */ + minimumBlockHeight?: Maybe; +}; + +export type Guard = IGuard & { + __typename?: 'Guard'; + keys: Array; + predicate: Scalars['String']['output']; + raw: Scalars['String']['output']; +}; + +/** A guard. Has values `keys`, `predicate` to provide backwards compatibility for `KeysetGuard`. */ +export type IGuard = { + /** @deprecated Use `... on KeysetGuard { keys predicate }` instead when working with Keysets */ + keys: Array; + /** @deprecated Use `... on KeysetGuard { keys predicate }` instead when working with Keysets */ + predicate: Scalars['String']['output']; + raw: Scalars['String']['output']; +}; + +/** The account of the miner that solved a block. */ +export type MinerKey = Node & { + __typename?: 'MinerKey'; + block: Block; + blockHash: Scalars['String']['output']; + id: Scalars['ID']['output']; + key: Scalars['String']['output']; +}; + +/** Information about the network. */ +export type NetworkInfo = { + __typename?: 'NetworkInfo'; + /** The version of the API. */ + apiVersion: Scalars['String']['output']; + /** The number of circulating coins. */ + coinsInCirculation: Scalars['Float']['output']; + /** The network hash rate. */ + networkHashRate: Scalars['Float']['output']; + /** The host of the network. */ + networkHost: Scalars['String']['output']; + /** The ID of the network. */ + networkId: Scalars['String']['output']; + /** The total difficulty. */ + totalDifficulty: Scalars['Float']['output']; + /** The total number of transactions. */ + transactionCount: Scalars['Int']['output']; +}; + +export type Node = { + id: Scalars['ID']['output']; +}; + +/** A non-fungible-specific account. */ +export type NonFungibleAccount = Node & { + __typename?: 'NonFungibleAccount'; + accountName: Scalars['String']['output']; + chainAccounts: Array; + id: Scalars['ID']['output']; + nonFungibleTokenBalances: Array; + /** Default page size is 20. Note that custom token related transactions are not included. */ + transactions: NonFungibleAccountTransactionsConnection; +}; + + +/** A non-fungible-specific account. */ +export type NonFungibleAccountTransactionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type NonFungibleAccountTransactionsConnection = { + __typename?: 'NonFungibleAccountTransactionsConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type NonFungibleAccountTransactionsConnectionEdge = { + __typename?: 'NonFungibleAccountTransactionsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transaction; +}; + +/** A chain and non-fungible-specific account. */ +export type NonFungibleChainAccount = Node & { + __typename?: 'NonFungibleChainAccount'; + accountName: Scalars['String']['output']; + chainId: Scalars['String']['output']; + id: Scalars['ID']['output']; + nonFungibleTokenBalances: Array; + /** Default page size is 20. Note that custom token related transactions are not included. */ + transactions: NonFungibleChainAccountTransactionsConnection; +}; + + +/** A chain and non-fungible-specific account. */ +export type NonFungibleChainAccountTransactionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type NonFungibleChainAccountTransactionsConnection = { + __typename?: 'NonFungibleChainAccountTransactionsConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type NonFungibleChainAccountTransactionsConnectionEdge = { + __typename?: 'NonFungibleChainAccountTransactionsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transaction; +}; + +/** Information related to a token. */ +export type NonFungibleToken = { + __typename?: 'NonFungibleToken'; + precision: Scalars['Int']['output']; + supply: Scalars['Int']['output']; + uri: Scalars['String']['output']; +}; + +/** The token identifier and its balance. */ +export type NonFungibleTokenBalance = Node & { + __typename?: 'NonFungibleTokenBalance'; + accountName: Scalars['String']['output']; + balance: Scalars['Int']['output']; + chainId: Scalars['String']['output']; + guard: Guard; + id: Scalars['ID']['output']; + info?: Maybe; + tokenId: Scalars['String']['output']; + version: Scalars['String']['output']; +}; + +export type PactQuery = { + chainId: Scalars['String']['input']; + code: Scalars['String']['input']; + data?: InputMaybe>; +}; + +export type PactQueryData = { + key: Scalars['String']['input']; + value: Scalars['String']['input']; +}; + +/** Information related to a token. */ +export type PactQueryResponse = { + __typename?: 'PactQueryResponse'; + chainId: Scalars['String']['output']; + code: Scalars['String']['output']; + error?: Maybe; + result?: Maybe; + status: Scalars['String']['output']; +}; + +export type PageInfo = { + __typename?: 'PageInfo'; + endCursor?: Maybe; + hasNextPage: Scalars['Boolean']['output']; + hasPreviousPage: Scalars['Boolean']['output']; + startCursor?: Maybe; +}; + +export type Query = { + __typename?: 'Query'; + /** Retrieve a block by hash. */ + block?: Maybe; + /** Retrieve blocks by chain and minimal depth. Default page size is 20. */ + blocksFromDepth?: Maybe; + /** Retrieve blocks by chain and minimal height. Default page size is 20. */ + blocksFromHeight: QueryBlocksFromHeightConnection; + /** Retrieve all completed blocks from a given height. Default page size is 20. */ + completedBlockHeights: QueryCompletedBlockHeightsConnection; + /** + * Retrieve events by qualifiedName (e.g. `coin.TRANSFER`). Default page size is 20. + *   + * The parametersFilter is a stringified JSON object that matches the [JSON object property filters](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields#filter-on-object-property) from Prisma. + *   + * An example of such a filter parameter value: `events(parametersFilter: "{\"array_starts_with\": \"k:abcdefg\"}")` + */ + events: QueryEventsConnection; + /** Retrieve an fungible specific account by its name and fungible, such as coin. */ + fungibleAccount?: Maybe; + /** Retrieve an account by public key. */ + fungibleAccountsByPublicKey: Array; + /** Retrieve an account by its name and fungible, such as coin, on a specific chain. */ + fungibleChainAccount?: Maybe; + /** Retrieve a chain account by public key. */ + fungibleChainAccountsByPublicKey: Array; + /** + * Estimate the gas limit for one or more transactions. Throws an error when the transaction fails or is invalid. The input accepts a JSON object and based on the parameters passed it will determine what type of format it is and return the gas limit estimation. The following types are supported: + *   + * - `full-transaction`: A complete transaction object. Required parameters: `cmd`, `hash` and `sigs`. + * - `stringified-command`: A JSON stringified command. Required parameters: `cmd`. It also optionally accepts `sigs`. + * - `full-command`: A full command. Required parameters: `payload`, `meta` and `signers`. + * - `partial-command`: A partial command. Required parameters: `payload` and either `meta` or `signers`. In case `meta` is not given, but `signers` is given, you can also add `chainId` as a parameter. + * - `payload`: A just the payload of a command. Required parameters: `payload` and `chainId`. + * - `code`: The code of an execution. Required parameters: `code` and `chainId`. + *   + * Every type accepts an optional parameter called `networkId` to override the default value from the environment variables. + *   + * Example of the input needed for a type `code` query: `gasLimitEstimate(input: "{\"code\":\"(coin.details \\\"k:1234\\\")\",\"chainId\":\"3\"}")` + */ + gasLimitEstimate: Array; + /** Get the configuration of the graph. */ + graphConfiguration: GraphConfiguration; + /** Get the height of the block with the highest height. */ + lastBlockHeight?: Maybe; + /** Get information about the network. */ + networkInfo?: Maybe; + node?: Maybe; + nodes: Array>; + /** Retrieve a non-fungible specific account by its name. */ + nonFungibleAccount?: Maybe; + /** Retrieve an account by its name on a specific chain. */ + nonFungibleChainAccount?: Maybe; + /** Execute arbitrary Pact code via a local call without gas-estimation or signature-verification (e.g. (+ 1 2) or (coin.get-details )). */ + pactQuery: Array; + /** Retrieve one transaction by its unique key. Throws an error if multiple transactions are found. */ + transaction?: Maybe; + /** + * Retrieve transactions. Default page size is 20. + * At least one of accountName, fungibleName, blockHash, or requestKey must be provided. + */ + transactions: QueryTransactionsConnection; + /** Retrieve all transactions by a given public key. */ + transactionsByPublicKey: QueryTransactionsByPublicKeyConnection; + /** Retrieve transfers. Default page size is 20. */ + transfers: QueryTransfersConnection; +}; + + +export type QueryBlockArgs = { + hash: Scalars['String']['input']; +}; + + +export type QueryBlocksFromDepthArgs = { + after?: InputMaybe; + before?: InputMaybe; + chainIds?: InputMaybe>; + first?: InputMaybe; + last?: InputMaybe; + minimumDepth: Scalars['Int']['input']; +}; + + +export type QueryBlocksFromHeightArgs = { + after?: InputMaybe; + before?: InputMaybe; + chainIds?: InputMaybe>; + endHeight?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + startHeight: Scalars['Int']['input']; +}; + + +export type QueryCompletedBlockHeightsArgs = { + after?: InputMaybe; + before?: InputMaybe; + chainIds?: InputMaybe>; + completedHeights?: InputMaybe; + first?: InputMaybe; + heightCount?: InputMaybe; + last?: InputMaybe; +}; + + +export type QueryEventsArgs = { + after?: InputMaybe; + before?: InputMaybe; + blockHash?: InputMaybe; + chainId?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + maxHeight?: InputMaybe; + minHeight?: InputMaybe; + minimumDepth?: InputMaybe; + orderIndex?: InputMaybe; + parametersFilter?: InputMaybe; + qualifiedEventName: Scalars['String']['input']; + requestKey?: InputMaybe; +}; + + +export type QueryFungibleAccountArgs = { + accountName: Scalars['String']['input']; + fungibleName?: InputMaybe; +}; + + +export type QueryFungibleAccountsByPublicKeyArgs = { + fungibleName?: InputMaybe; + publicKey: Scalars['String']['input']; +}; + + +export type QueryFungibleChainAccountArgs = { + accountName: Scalars['String']['input']; + chainId: Scalars['String']['input']; + fungibleName?: InputMaybe; +}; + + +export type QueryFungibleChainAccountsByPublicKeyArgs = { + chainId: Scalars['String']['input']; + fungibleName?: InputMaybe; + publicKey: Scalars['String']['input']; +}; + + +export type QueryGasLimitEstimateArgs = { + input: Array; +}; + + +export type QueryNodeArgs = { + id: Scalars['ID']['input']; +}; + + +export type QueryNodesArgs = { + ids: Array; +}; + + +export type QueryNonFungibleAccountArgs = { + accountName: Scalars['String']['input']; +}; + + +export type QueryNonFungibleChainAccountArgs = { + accountName: Scalars['String']['input']; + chainId: Scalars['String']['input']; +}; + + +export type QueryPactQueryArgs = { + pactQuery: Array; +}; + + +export type QueryTransactionArgs = { + blockHash?: InputMaybe; + minimumDepth?: InputMaybe; + requestKey: Scalars['String']['input']; +}; + + +export type QueryTransactionsArgs = { + accountName?: InputMaybe; + after?: InputMaybe; + before?: InputMaybe; + blockHash?: InputMaybe; + chainId?: InputMaybe; + first?: InputMaybe; + fungibleName?: InputMaybe; + last?: InputMaybe; + maxHeight?: InputMaybe; + minHeight?: InputMaybe; + minimumDepth?: InputMaybe; + requestKey?: InputMaybe; +}; + + +export type QueryTransactionsByPublicKeyArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + publicKey: Scalars['String']['input']; +}; + + +export type QueryTransfersArgs = { + accountName?: InputMaybe; + after?: InputMaybe; + before?: InputMaybe; + blockHash?: InputMaybe; + chainId?: InputMaybe; + first?: InputMaybe; + fungibleName?: InputMaybe; + last?: InputMaybe; + requestKey?: InputMaybe; +}; + +export type QueryBlocksFromDepthConnection = { + __typename?: 'QueryBlocksFromDepthConnection'; + edges: Array>; + pageInfo: PageInfo; +}; + +export type QueryBlocksFromDepthConnectionEdge = { + __typename?: 'QueryBlocksFromDepthConnectionEdge'; + cursor: Scalars['String']['output']; + node: Block; +}; + +export type QueryBlocksFromHeightConnection = { + __typename?: 'QueryBlocksFromHeightConnection'; + edges: Array>; + pageInfo: PageInfo; +}; + +export type QueryBlocksFromHeightConnectionEdge = { + __typename?: 'QueryBlocksFromHeightConnectionEdge'; + cursor: Scalars['String']['output']; + node: Block; +}; + +export type QueryCompletedBlockHeightsConnection = { + __typename?: 'QueryCompletedBlockHeightsConnection'; + edges: Array>; + pageInfo: PageInfo; +}; + +export type QueryCompletedBlockHeightsConnectionEdge = { + __typename?: 'QueryCompletedBlockHeightsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Block; +}; + +export type QueryEventsConnection = { + __typename?: 'QueryEventsConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type QueryEventsConnectionEdge = { + __typename?: 'QueryEventsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Event; +}; + +export type QueryTransactionsByPublicKeyConnection = { + __typename?: 'QueryTransactionsByPublicKeyConnection'; + edges: Array>; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type QueryTransactionsByPublicKeyConnectionEdge = { + __typename?: 'QueryTransactionsByPublicKeyConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transaction; +}; + +export type QueryTransactionsConnection = { + __typename?: 'QueryTransactionsConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type QueryTransactionsConnectionEdge = { + __typename?: 'QueryTransactionsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transaction; +}; + +export type QueryTransfersConnection = { + __typename?: 'QueryTransfersConnection'; + edges: Array; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type QueryTransfersConnectionEdge = { + __typename?: 'QueryTransfersConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transfer; +}; + +/** A signer for a specific transaction. */ +export type Signer = Node & { + __typename?: 'Signer'; + /** The signer for the gas. */ + address?: Maybe; + clist: Array; + id: Scalars['ID']['output']; + orderIndex?: Maybe; + pubkey: Scalars['String']['output']; + /** The signature scheme that was used to sign. */ + scheme?: Maybe; +}; + +export type Subscription = { + __typename?: 'Subscription'; + /** + * Listen for events by qualifiedName (e.g. `coin.TRANSFER`). + *   + * The parametersFilter is a stringified JSON object that matches the [JSON object property filters](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types/working-with-json-fields#filter-on-object-property) from Prisma. + *   + * An example of such a filter parameter value: `events(parametersFilter: "{\"array_starts_with\": \"k:abcdefg\"}")` + */ + events?: Maybe>; + /** Subscribe to new blocks. */ + newBlocks?: Maybe>; + /** Subscribe to new blocks from a specific depth. */ + newBlocksFromDepth?: Maybe>; + /** Listen for a transaction by request key. */ + transaction?: Maybe; +}; + + +export type SubscriptionEventsArgs = { + chainId?: InputMaybe; + minimumDepth?: InputMaybe; + parametersFilter?: InputMaybe; + qualifiedEventName: Scalars['String']['input']; +}; + + +export type SubscriptionNewBlocksArgs = { + chainIds?: InputMaybe>; +}; + + +export type SubscriptionNewBlocksFromDepthArgs = { + chainIds: Array; + minimumDepth: Scalars['Int']['input']; +}; + + +export type SubscriptionTransactionArgs = { + chainId?: InputMaybe; + requestKey: Scalars['String']['input']; +}; + +/** A transaction. */ +export type Transaction = Node & { + __typename?: 'Transaction'; + cmd: TransactionCommand; + hash: Scalars['String']['output']; + id: Scalars['ID']['output']; + result: TransactionInfo; + sigs: Array; +}; + +/** List of capabilities associated with/installed by this signer. */ +export type TransactionCapability = { + __typename?: 'TransactionCapability'; + args: Scalars['String']['output']; + name: Scalars['String']['output']; +}; + +/** A transaction command. */ +export type TransactionCommand = { + __typename?: 'TransactionCommand'; + meta: TransactionMeta; + /** The network id of the environment. */ + networkId: Scalars['String']['output']; + nonce: Scalars['String']['output']; + payload: TransactionPayload; + signers: Array; +}; + +/** The result of a transaction. */ +export type TransactionInfo = TransactionMempoolInfo | TransactionResult; + +/** The mempool information. */ +export type TransactionMempoolInfo = { + __typename?: 'TransactionMempoolInfo'; + /** The status of the mempool. */ + status?: Maybe; +}; + +/** The metadata of a transaction. */ +export type TransactionMeta = { + __typename?: 'TransactionMeta'; + chainId: Scalars['BigInt']['output']; + creationTime: Scalars['DateTime']['output']; + gasLimit: Scalars['BigInt']['output']; + gasPrice: Scalars['Float']['output']; + sender: Scalars['String']['output']; + ttl: Scalars['BigInt']['output']; +}; + +/** The payload of a transaction. */ +export type TransactionPayload = ContinuationPayload | ExecutionPayload; + +/** The result of a transaction. */ +export type TransactionResult = { + __typename?: 'TransactionResult'; + /** The transaction result when it was successful. Formatted as raw JSON. */ + badResult?: Maybe; + block: Block; + /** The JSON stringified continuation in the case that it is a continuation. */ + continuation?: Maybe; + eventCount?: Maybe; + events: TransactionResultEventsConnection; + gas: Scalars['BigInt']['output']; + /** The transaction result when it was successful. Formatted as raw JSON. */ + goodResult?: Maybe; + /** The height of the block this transaction belongs to. */ + height: Scalars['BigInt']['output']; + /** Identifier to retrieve the logs for the execution of the transaction. */ + logs?: Maybe; + metadata?: Maybe; + transactionId?: Maybe; + transfers: TransactionResultTransfersConnection; +}; + + +/** The result of a transaction. */ +export type TransactionResultEventsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** The result of a transaction. */ +export type TransactionResultTransfersArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + +export type TransactionResultEventsConnection = { + __typename?: 'TransactionResultEventsConnection'; + edges: Array>; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type TransactionResultEventsConnectionEdge = { + __typename?: 'TransactionResultEventsConnectionEdge'; + cursor: Scalars['String']['output']; + node: Event; +}; + +export type TransactionResultTransfersConnection = { + __typename?: 'TransactionResultTransfersConnection'; + edges: Array>; + pageInfo: PageInfo; + totalCount: Scalars['Int']['output']; +}; + +export type TransactionResultTransfersConnectionEdge = { + __typename?: 'TransactionResultTransfersConnectionEdge'; + cursor: Scalars['String']['output']; + node: Transfer; +}; + +/** List of capabilities associated with/installed by this signer. */ +export type TransactionSignature = { + __typename?: 'TransactionSignature'; + sig: Scalars['String']['output']; +}; + +/** A transfer of funds from a fungible between two accounts. */ +export type Transfer = Node & { + __typename?: 'Transfer'; + amount: Scalars['Decimal']['output']; + block: Block; + blockHash: Scalars['String']['output']; + chainId: Scalars['BigInt']['output']; + creationTime: Scalars['DateTime']['output']; + /** The counterpart of the crosschain-transfer. `null` when it is not a cross-chain-transfer. */ + crossChainTransfer?: Maybe; + height: Scalars['BigInt']['output']; + id: Scalars['ID']['output']; + moduleHash: Scalars['String']['output']; + moduleName: Scalars['String']['output']; + /** The order of the transfer when it is a `defpact` (multi-step transaction) execution. */ + orderIndex: Scalars['BigInt']['output']; + receiverAccount: Scalars['String']['output']; + requestKey: Scalars['String']['output']; + senderAccount: Scalars['String']['output']; + /** The transaction that initiated this transfer. */ + transaction?: Maybe; +}; + +export type EventsQueryVariables = Exact<{ + qualifiedName: Scalars['String']['input']; +}>; + + +export type EventsQuery = { __typename?: 'Query', events: { __typename?: 'QueryEventsConnection', edges: Array<{ __typename?: 'QueryEventsConnectionEdge', node: { __typename?: 'Event', chainId: any, requestKey: string, parameters?: string | null, block: { __typename?: 'Block', height: any } } }> } }; + +export type CoreEventsFieldsFragment = { __typename?: 'Event', chainId: any, requestKey: string, parameters?: string | null, block: { __typename?: 'Block', height: any } }; + +export const CoreEventsFieldsFragmentDoc = gql` + fragment CoreEventsFields on Event { + chainId + block { + height + } + requestKey + parameters +} + `; +export const EventsDocument = gql` + query events($qualifiedName: String!) { + events(qualifiedEventName: $qualifiedName) { + edges { + node { + ...CoreEventsFields + } + } + } +} + ${CoreEventsFieldsFragmentDoc}`; + +/** + * __useEventsQuery__ + * + * To run a query within a React component, call `useEventsQuery` and pass it any options that fit your needs. + * When your component renders, `useEventsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useEventsQuery({ + * variables: { + * qualifiedName: // value for 'qualifiedName' + * }, + * }); + */ +export function useEventsQuery(baseOptions: Apollo.QueryHookOptions & ({ variables: EventsQueryVariables; skip?: boolean; } | { skip: boolean; }) ) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(EventsDocument, options); + } +export function useEventsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(EventsDocument, options); + } +export function useEventsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useSuspenseQuery(EventsDocument, options); + } +export type EventsQueryHookResult = ReturnType; +export type EventsLazyQueryHookResult = ReturnType; +export type EventsSuspenseQueryHookResult = ReturnType; +export type EventsQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/packages/apps/rwa-demo/src/app/(app)/page.tsx b/packages/apps/rwa-demo/src/app/(app)/page.tsx index 4f4af003c8..9d1d45835d 100644 --- a/packages/apps/rwa-demo/src/app/(app)/page.tsx +++ b/packages/apps/rwa-demo/src/app/(app)/page.tsx @@ -1,9 +1,10 @@ '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 } from '@kadena/kode-ui'; +import { Button, Stack } from '@kadena/kode-ui'; import { SideBarBreadcrumbsItem, useLayout } from '@kadena/kode-ui/patterns'; import { useState } from 'react'; @@ -23,7 +24,7 @@ const Home = () => { }; return ( -
+ Tokens @@ -43,14 +44,19 @@ const Home = () => { }} /> )} - - - -
+ + + + + + + + + ); }; diff --git a/packages/apps/rwa-demo/src/components/AccountProvider/AccountProvider.tsx b/packages/apps/rwa-demo/src/components/AccountProvider/AccountProvider.tsx index ed84ebfde1..9d414540d2 100644 --- a/packages/apps/rwa-demo/src/components/AccountProvider/AccountProvider.tsx +++ b/packages/apps/rwa-demo/src/components/AccountProvider/AccountProvider.tsx @@ -1,12 +1,12 @@ 'use client'; -import { env } from '@/utils/env'; import { getAccountCookieName } from '@/utils/getAccountCookieName'; - +import type { ICommand, IUnsignedCommand } from '@kadena/client'; import type { ConnectedAccount } from '@kadena/spirekey-sdk'; -import { connect } from '@kadena/spirekey-sdk'; import { useRouter } from 'next/navigation'; import type { FC, PropsWithChildren } from 'react'; import { createContext, useCallback, useEffect, useState } from 'react'; +import type { IState, IWalletAccount } from './utils'; +import { getWalletConnection } from './utils'; interface IAccountError { message: string; @@ -18,6 +18,7 @@ export interface IAccountContext { isMounted: boolean; login: () => void; logout: () => void; + sign: (tx: IUnsignedCommand) => Promise; } export const AccountContext = createContext({ @@ -25,6 +26,7 @@ export const AccountContext = createContext({ isMounted: false, login: () => {}, logout: () => {}, + sign: async () => undefined, }); export const AccountProvider: FC = ({ children }) => { @@ -33,14 +35,22 @@ export const AccountProvider: FC = ({ children }) => { const router = useRouter(); const login = useCallback(async () => { - try { - setIsMounted(true); - const acc = await connect(env.NETWORKID, env.CHAINID); - setAccount(acc); - localStorage.setItem(getAccountCookieName(), JSON.stringify(acc)); - } catch (e) { - localStorage.removeItem(getAccountCookieName()); + const { message, focus, close } = await getWalletConnection(); + focus(); + const response = await message('CONNECTION_REQUEST', { + name: 'RWA-demo', + }); + + if ((response.payload as any).status !== 'accepted') { + return; } + const { payload } = await message('GET_STATUS', { + name: 'RWA-demo', + }); + + const account = (payload as IState).accounts[0] as IWalletAccount; + localStorage.setItem(getAccountCookieName(), JSON.stringify(account)!); + close(); }, [router]); const logout = useCallback(() => { @@ -63,8 +73,22 @@ export const AccountProvider: FC = ({ children }) => { setIsMounted(true); }, []); + const sign = async (tx: IUnsignedCommand): Promise => { + const { message, close } = await getWalletConnection(); + const response = await message('SIGN_REQUEST', tx as any); + const payload: { + status: 'signed' | 'rejected'; + transaction?: ICommand; + } = response.payload as any; + + close(); + return payload.transaction; + }; + return ( - + {children} ); diff --git a/packages/apps/rwa-demo/src/components/AccountProvider/utils.ts b/packages/apps/rwa-demo/src/components/AccountProvider/utils.ts new file mode 100644 index 0000000000..c3a79ba1e2 --- /dev/null +++ b/packages/apps/rwa-demo/src/components/AccountProvider/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/components/AddAgentForm/AddAgentForm.tsx b/packages/apps/rwa-demo/src/components/AddAgentForm/AddAgentForm.tsx index 7bfe82ddf5..afa05f9dda 100644 --- a/packages/apps/rwa-demo/src/components/AddAgentForm/AddAgentForm.tsx +++ b/packages/apps/rwa-demo/src/components/AddAgentForm/AddAgentForm.tsx @@ -2,7 +2,14 @@ import { useAccount } from '@/hooks/account'; import { useNetwork } from '@/hooks/networks'; import type { IAddAgentProps } from '@/services/addAgent'; import { addAgent } from '@/services/addAgent'; -import { Button, TextField } from '@kadena/kode-ui'; +import { getClient } from '@/utils/client'; +import { + Button, + Dialog, + DialogContent, + DialogHeader, + TextField, +} from '@kadena/kode-ui'; import { RightAside, RightAsideContent, @@ -19,7 +26,8 @@ interface IProps { export const AddAgentForm: FC = ({ onClose }) => { const { activeNetwork } = useNetwork(); - const { account } = useAccount(); + const { account, sign } = useAccount(); + const [openModal, setOpenModal] = useState(false); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, setError] = useState(null); const { register, handleSubmit } = useForm({ @@ -29,36 +37,55 @@ export const AddAgentForm: FC = ({ onClose }) => { }); const onSubmit = async (data: IAddAgentProps) => { - console.log({ data }); setError(null); try { - await addAgent(data, activeNetwork, account!); + const tx = await addAgent(data, activeNetwork, account!); + + const signedTransaction = await sign(tx); + if (!signedTransaction) return; - // setIsRightAsideExpanded(false); + 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(); + onClose(); }; return ( - -
- - - - - - - - - -
+ <> + { + setOpenModal(false); + }} + > + Transaction + df + + + +
+ + + + + + + + + +
+ ); }; diff --git a/packages/apps/rwa-demo/src/components/AgentsList/AgentsList.tsx b/packages/apps/rwa-demo/src/components/AgentsList/AgentsList.tsx new file mode 100644 index 0000000000..a7d461182d --- /dev/null +++ b/packages/apps/rwa-demo/src/components/AgentsList/AgentsList.tsx @@ -0,0 +1,19 @@ +import { useGetAgents } from '@/hooks/getAgents'; +import { CompactTable } from '@kadena/kode-ui/patterns'; +import type { FC } from 'react'; + +export const AgentsList: FC = () => { + const { data } = useGetAgents(); + + console.log(data); + + return ( + + ); +}; diff --git a/packages/apps/rwa-demo/src/components/NetworkProvider/NetworkProvider.tsx b/packages/apps/rwa-demo/src/components/NetworkProvider/NetworkProvider.tsx index 9ad2b1322e..a094adc82e 100644 --- a/packages/apps/rwa-demo/src/components/NetworkProvider/NetworkProvider.tsx +++ b/packages/apps/rwa-demo/src/components/NetworkProvider/NetworkProvider.tsx @@ -1,14 +1,33 @@ 'use client'; +import { checkNetwork } from '@/utils/checkNetwork'; import { env } from '@/utils/env'; +import type { NormalizedCacheObject } from '@apollo/client'; +import { + ApolloClient, + ApolloProvider, + InMemoryCache, + split, +} from '@apollo/client'; +import { GraphQLWsLink } from '@apollo/client/link/subscriptions'; +import { getMainDefinition } from '@apollo/client/utilities'; import type { ChainId } from '@kadena/client'; +import { createClient } from 'graphql-ws'; import type { FC, PropsWithChildren } from 'react'; -import { createContext, useState } from 'react'; +import { createContext, useEffect, useState } from 'react'; +// next/apollo-link bug: https://github.com/dotansimha/graphql-yoga/issues/2194 +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { YogaLink } = require('@graphql-yoga/apollo-link'); + +const cache = new InMemoryCache({ + resultCaching: true, +}); export interface INetwork { name: string; networkId: string; host: string; chainId: ChainId; + graphUrl: string; } export interface INetworkContext { @@ -22,6 +41,7 @@ const defaultContext: INetworkContext = { networkId: env.NETWORKID, host: env.NETWORKHOST, chainId: env.CHAINID, + graphUrl: env.GRAPHURL, }, networks: [ { @@ -29,33 +49,104 @@ const defaultContext: INetworkContext = { name: 'Testnet(Pact5)', host: 'https://api.testnet05.chainweb.com', chainId: '0', + graphUrl: 'https://graph.testnet05.kadena.network/graphql', }, { networkId: 'testnet04', name: 'Testnet', host: 'https://api.testnet.chainweb.com', + graphUrl: 'https://graph.testnet.kadena.network/graphql', chainId: '0', }, { networkId: 'development', name: 'development', host: 'https://localhost:8080', + graphUrl: 'http://localhost:8080/graphql', chainId: '0', }, ], }; +const getApolloClient = (network: INetwork) => { + const httpLink = new YogaLink({ + endpoint: network?.graphUrl, + }); + + const wsLink = new GraphQLWsLink( + createClient({ + url: network!.graphUrl, + }), + ); + + const splitLink = split( + ({ query }) => { + const definition = getMainDefinition(query); + return ( + definition.kind === 'OperationDefinition' && + definition.operation === 'subscription' + ); + }, + wsLink, // Use WebSocket link for subscriptions + httpLink, // Use HTTP link for queries and mutations + ); + + const client: ApolloClient = new ApolloClient({ + link: splitLink, + cache, + assumeImmutableResults: true, + connectToDevTools: true, + defaultOptions: { + query: { errorPolicy: 'all' }, + }, + }); + + return client; +}; + export const NetworkContext = createContext(defaultContext); export const NetworkProvider: FC = ({ children }) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [activeNetwork, _] = useState(defaultContext.activeNetwork); + const [client, setClient] = useState>(); + + const stopServer = () => { + client?.stop(); + }; + + const checkIfNetworkAvailable = async (graphUrl: string) => { + try { + const result = await checkNetwork(graphUrl); + await result.json(); + + if (result.status !== 200) { + stopServer(); + } + } catch (e) { + stopServer(); + } + }; + + useEffect(() => { + if (!activeNetwork) return; + const resultClient = getApolloClient(activeNetwork); + setClient(resultClient); + }, [activeNetwork]); + + useEffect(() => { + if (!activeNetwork || !activeNetwork.graphUrl) return; + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + checkIfNetworkAvailable(activeNetwork.graphUrl); + }, [activeNetwork]); + if (!client) return null; return ( - {children} + {children} ); }; diff --git a/packages/apps/rwa-demo/src/hooks/getAgents.ts b/packages/apps/rwa-demo/src/hooks/getAgents.ts new file mode 100644 index 0000000000..60b1ab1bae --- /dev/null +++ b/packages/apps/rwa-demo/src/hooks/getAgents.ts @@ -0,0 +1,37 @@ +import type { Exact, Scalars } from '@/__generated__/sdk'; +import { useEventsQuery } from '@/__generated__/sdk'; +import { coreEvents } from '@/services/graph/agent.graph'; +import type * as Apollo from '@apollo/client'; + +export type EventQueryVariables = Exact<{ + qualifiedName: Scalars['String']['input']; +}>; + +export const getEventsDocument = ( + variables: EventQueryVariables = { + qualifiedName: '', + }, +): Apollo.DocumentNode => coreEvents; + +export const useGetAgents = () => { + 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); + + return { + blockHeight: edge.node.block.height, + chainId: edge.node.chainId, + requestKey: edge.node.requestKey, + accountName: JSON.parse(edge.node.parameters)[0], + }; + }) ?? []; + + return { loading, data: agents, error }; +}; diff --git a/packages/apps/rwa-demo/src/services/addAgent.ts b/packages/apps/rwa-demo/src/services/addAgent.ts index 1809f29b96..53b7a52007 100644 --- a/packages/apps/rwa-demo/src/services/addAgent.ts +++ b/packages/apps/rwa-demo/src/services/addAgent.ts @@ -29,7 +29,7 @@ export const addAgent = async ( network: INetwork, account: ConnectedAccount, ) => { - const transaction = Pact.builder + return Pact.builder .execution( `(RWA.agent-role.add-agent (read-string 'agent) (read-keyset 'agent_guard))`, ) @@ -49,17 +49,4 @@ export const addAgent = async ( .setNetworkId(network.networkId) .createTransaction(); - - console.log({ transaction }); - console.log(transaction.cmd); - console.log(JSON.parse(transaction.cmd)); - - // const { transactions, isReady } = await sign([transaction], [account]); - // await isReady(); - // console.log(transactions); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - // transactions.map(async (t) => { - // await doSubmit(t); - // }); }; diff --git a/packages/apps/rwa-demo/src/services/fetchAgents.ts b/packages/apps/rwa-demo/src/services/fetchAgents.ts new file mode 100644 index 0000000000..ebe79b956a --- /dev/null +++ b/packages/apps/rwa-demo/src/services/fetchAgents.ts @@ -0,0 +1 @@ +export const fetchAgents = () => {}; diff --git a/packages/apps/rwa-demo/src/services/graph/agent.graph.ts b/packages/apps/rwa-demo/src/services/graph/agent.graph.ts new file mode 100644 index 0000000000..11f0bb86ce --- /dev/null +++ b/packages/apps/rwa-demo/src/services/graph/agent.graph.ts @@ -0,0 +1,17 @@ +import { gql } from '@apollo/client'; +import type { DocumentNode } from 'graphql'; +import { CORE_EVENTS_FIELDS } from './fragments/events.graph'; + +export const coreEvents: DocumentNode = gql` + ${CORE_EVENTS_FIELDS} + + query events($qualifiedName: String!) { + events(qualifiedEventName: $qualifiedName) { + edges { + node { + ...CoreEventsFields + } + } + } + } +`; diff --git a/packages/apps/rwa-demo/src/services/graph/fragments/events.graph.ts b/packages/apps/rwa-demo/src/services/graph/fragments/events.graph.ts new file mode 100644 index 0000000000..ac1f0bd70a --- /dev/null +++ b/packages/apps/rwa-demo/src/services/graph/fragments/events.graph.ts @@ -0,0 +1,14 @@ +import { gql } from '@apollo/client'; +import type { DocumentNode } from 'graphql'; + +export const CORE_EVENTS_FIELDS: DocumentNode = gql` + fragment CoreEventsFields on Event { + chainId + block { + height + minerAccount + } + requestKey + parameters + } +`; diff --git a/packages/apps/rwa-demo/src/utils/checkNetwork.ts b/packages/apps/rwa-demo/src/utils/checkNetwork.ts new file mode 100644 index 0000000000..4c01c08ccd --- /dev/null +++ b/packages/apps/rwa-demo/src/utils/checkNetwork.ts @@ -0,0 +1,24 @@ +export const checkNetwork = (graphUrl: string): Promise => + fetch(graphUrl, { + method: 'POST', + headers: { + accept: + 'application/graphql-response+json, application/json, multipart/mixed', + 'cache-control': 'no-cache', + 'content-type': 'application/json', + pragma: 'no-cache', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'cross-site', + }, + body: JSON.stringify({ + query: `query networkInfo { + networkInfo { + totalDifficulty + networkId + } + }`, + variables: {}, + operationName: 'networkInfo', + extensions: {}, + }), + }); diff --git a/packages/apps/rwa-demo/src/utils/env.ts b/packages/apps/rwa-demo/src/utils/env.ts index 52ad5e0931..30e86a4425 100644 --- a/packages/apps/rwa-demo/src/utils/env.ts +++ b/packages/apps/rwa-demo/src/utils/env.ts @@ -8,6 +8,7 @@ const NETWORKNAME = process.env.NEXT_PUBLIC_NETWORKNAME; const NETWORKHOST = process.env.NEXT_PUBLIC_NETWORKHOST; const CHAINWEBAPIURL = process.env.NEXT_PUBLIC_CHAINWEBAPIURL; const ACCOUNT = process.env.NEXT_PUBLIC_ACCOUNT; +const GRAPHURL = process.env.NEXT_PUBLIC_GRAPHURL; if (!WALLET_URL) console.error('NEXT_PUBLIC_WALLET_URL is not set'); if (!URL) console.error('NEXT_PUBLIC_URL is not set'); @@ -17,6 +18,7 @@ if (!NETWORKNAME) console.error('NEXT_PUBLIC_NETWORKNAME is not set'); if (!NETWORKHOST) console.error('NEXT_PUBLIC_NETWORKHOST is not set'); if (!CHAINWEBAPIURL) console.error('NEXT_PUBLIC_CHAINWEBAPIURL is not set'); if (!ACCOUNT) console.error('NEXT_PUBLIC_ACCOUNT is not set'); +if (!GRAPHURL) console.error('NEXT_PUBLIC_GRAPHURL is not set'); export const env = { WALLET_URL, @@ -27,4 +29,5 @@ export const env = { NETWORKHOST: NETWORKHOST ?? '', CHAINWEBAPIURL: CHAINWEBAPIURL ?? '', ACCOUNTID: ACCOUNT, + GRAPHURL: GRAPHURL ?? '', } as const; diff --git a/packages/libs/kode-ui/src/components/Modal/Modal.css.ts b/packages/libs/kode-ui/src/components/Modal/Modal.css.ts index 8d235a0968..9e48791ca1 100644 --- a/packages/libs/kode-ui/src/components/Modal/Modal.css.ts +++ b/packages/libs/kode-ui/src/components/Modal/Modal.css.ts @@ -1,4 +1,4 @@ -import { style } from '../../styles'; +import { style, token } from '../../styles'; export const underlayClass = style([ { @@ -9,7 +9,7 @@ export const underlayClass = style([ inset: 0, // TODO: Update to use token: please check docs search dialog to align // backgroundColor: 'rgba(34, 33, 33, 0.8)', - + zIndex: token('zIndex.modal'), backdropFilter: 'blur(12px)', }, ]); diff --git a/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBarLayout.stories.tsx b/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBarLayout.stories.tsx index b78b8d8f4d..2f143c8c11 100644 --- a/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBarLayout.stories.tsx +++ b/packages/libs/kode-ui/src/patterns/SideBarLayout/SideBarLayout.stories.tsx @@ -10,7 +10,7 @@ import { import type { Meta, StoryObj } from '@storybook/react'; import type { FC, PropsWithChildren } from 'react'; import React, { useState } from 'react'; -import { Button, Stack } from './../../components'; +import { Button, Dialog, DialogHeader, Stack } from './../../components'; import { SideBarBreadcrumbs } from './components/Breadcrumbs/SideBarBreadcrumbs'; import { SideBarBreadcrumbsItem } from './components/Breadcrumbs/SideBarBreadcrumbsItem'; import { LayoutProvider, useLayout } from './components/LayoutProvider'; @@ -68,9 +68,20 @@ const InnerLayout = () => { useLayout(); const [hasOpenSidebar, setHasOpenSidebar] = useState(false); const [hasOpenOtherSidebar, setHasOpenOtherSidebar] = useState(false); + const [openDialog, setOpenDialog] = useState(false); return ( <> + {openDialog && ( + { + setOpenDialog(false); + }} + > + Header of dialog + + )} {isRightAsideExpanded && hasOpenSidebar && ( { }} > - content + + content + + )}