diff --git a/.changeset/curly-pumas-love.md b/.changeset/curly-pumas-love.md new file mode 100644 index 0000000000..b3794e6dfc --- /dev/null +++ b/.changeset/curly-pumas-love.md @@ -0,0 +1,5 @@ +--- +'@kadena/graph': patch +--- + +Added gas estimation query and adjusted folder structure for devnet files diff --git a/.changeset/twelve-donkeys-occur.md b/.changeset/twelve-donkeys-occur.md new file mode 100644 index 0000000000..685c78d73c --- /dev/null +++ b/.changeset/twelve-donkeys-occur.md @@ -0,0 +1,6 @@ +--- +'@kadena/graph-client': patch +--- + +Created gas estimation page and ajusted header component to incorporate option +for gas. Also added necessary graph references to consume the endpoint diff --git a/packages/apps/graph-client/src/components/Common/Layout/Header/Header.tsx b/packages/apps/graph-client/src/components/Common/Layout/Header/Header.tsx index d6b89edb24..2439c01da4 100644 --- a/packages/apps/graph-client/src/components/Common/Layout/Header/Header.tsx +++ b/packages/apps/graph-client/src/components/Common/Layout/Header/Header.tsx @@ -17,7 +17,9 @@ const Header: FC = (props) => { const [searchType, setSearchType] = useState('request-key'); const [searchField, setSearchField] = useState(''); - const [moduleField, setModuleField] = useState('coin'); + const [secondSearchField, setSecondSearchField] = useState(''); + const [thirdSearchField, setThirdSearchField] = useState(''); + const [gridColumns, setGridColumns] = useState(3); const [defaultHashOption, setDefaultHashOption] = useState('request-key'); @@ -26,6 +28,7 @@ const Header: FC = (props) => { account: 'Account', event: 'Event Name', block: 'Block Hash', + gasEstimation: 'Cmd', }; const searchTypePlaceholders: Record = { @@ -33,6 +36,25 @@ const Header: FC = (props) => { account: 'k:1234...', event: 'coin.TRANSFER', block: 'CA9orP2yM...', + gasEstimation: 'cmd', + }; + + const secondSearchTypeLabels: Record = { + account: 'Module', + gasEstimation: 'Hash', + }; + + const secondSearchFieldPlaceholders: Record = { + account: 'coin', + gasEstimation: 'hash', + }; + + const thirdSeachTypeLabels: Record = { + gasEstimation: 'Signatures', + }; + + const thirdSearchFieldPlaceholders: Record = { + gasEstimation: 'sigs', }; const search = (): void => { @@ -43,7 +65,7 @@ const Header: FC = (props) => { break; case 'account': // eslint-disable-next-line @typescript-eslint/no-floating-promises - router.push(`${routes.ACCOUNT}/${moduleField}/${searchField}`); + router.push(`${routes.ACCOUNT}/${secondSearchField}/${searchField}`); break; case 'event': // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -53,6 +75,16 @@ const Header: FC = (props) => { // eslint-disable-next-line @typescript-eslint/no-floating-promises router.push(`${routes.BLOCK_OVERVIEW}/${searchField}`); break; + case 'gasEstimation': + // eslint-disable-next-line @typescript-eslint/no-floating-promises + router.push({ + pathname: `${routes.GAS_ESTIMATION}`, + query: { + cmd: searchField, + hash: secondSearchField, + sigs: thirdSearchField, + }, + }); } }; @@ -68,6 +100,10 @@ const Header: FC = (props) => { setSearchField(event.target.value); const fieldValue = event.target.value; + if (searchType === 'gasEstimation') { + return; + } + if ( fieldValue.startsWith('k:') || fieldValue.startsWith('w:') || @@ -92,9 +128,22 @@ const Header: FC = (props) => { setSearchType(event.target.value); if (event.target.value === 'request-key') { setDefaultHashOption('request-key'); + setGridColumns(3); } if (event.target.value === 'block') { setDefaultHashOption('block'); + setGridColumns(3); + } + if (event.target.value === 'event') { + setGridColumns(3); + } + if (event.target.value === 'account') { + setSecondSearchField('coin'); + setGridColumns(4); + } + if (event.target.value === 'gasEstimation') { + setSecondSearchField(''); + setGridColumns(5); } }; @@ -115,7 +164,7 @@ const Header: FC = (props) => { {title} - + + = (props) => { /> - {searchType.startsWith('account') && ( + + {(searchType.startsWith('account') || + searchType.startsWith('gas')) && ( + + + setSecondSearchField(event.target.value)} + onKeyDown={handleKeyPress} + /> + + + )} + + {searchType.startsWith('gas') && ( - + setModuleField(event.target.value)} + id="third-search-field" + value={thirdSearchField} + placeholder={thirdSearchFieldPlaceholders[searchType]} + onChange={(event) => setThirdSearchField(event.target.value)} + onKeyDown={handleKeyPress} /> diff --git a/packages/apps/graph-client/src/constants/routes.ts b/packages/apps/graph-client/src/constants/routes.ts index d29d70bc2a..0fb5af9473 100644 --- a/packages/apps/graph-client/src/constants/routes.ts +++ b/packages/apps/graph-client/src/constants/routes.ts @@ -7,4 +7,5 @@ export default { EVENT: '/event', BLOCK_OVERVIEW: '/block/overview', BLOCK_TRANSACTIONS: '/block/transactions', + GAS_ESTIMATION: '/gas/estimation', } as const; diff --git a/packages/apps/graph-client/src/graphql/queries.graph.ts b/packages/apps/graph-client/src/graphql/queries.graph.ts index 48d8dfeb81..392b5ad9db 100644 --- a/packages/apps/graph-client/src/graphql/queries.graph.ts +++ b/packages/apps/graph-client/src/graphql/queries.graph.ts @@ -53,7 +53,7 @@ export const getLastBlock: DocumentNode = gql` } `; -export const getGraphAndChainwebData: DocumentNode = gql` +export const getGraphConfiguration: DocumentNode = gql` query getGraphConfiguration { graphConfiguration { maximumConfirmationDepth @@ -224,3 +224,8 @@ export const getTransfers: DocumentNode = gql` } } `; +export const estimateGasLimit: DocumentNode = gql` + query estimateGasLimit($transaction: PactTransaction!) { + gasLimitEstimate(transaction: $transaction) + } +`; diff --git a/packages/apps/graph-client/src/pages/gas/estimation/index.tsx b/packages/apps/graph-client/src/pages/gas/estimation/index.tsx new file mode 100644 index 0000000000..e837b1f3f0 --- /dev/null +++ b/packages/apps/graph-client/src/pages/gas/estimation/index.tsx @@ -0,0 +1,66 @@ +import { useEstimateGasLimitQuery } from '@/__generated__/sdk'; +import Loader from '@/components/Common/loader/loader'; +import { mainStyle } from '@/components/Common/main/styles.css'; +import { ErrorBox } from '@/components/error-box/error-box'; +import routes from '@/constants/routes'; +import { Box, Breadcrumbs, Table } from '@kadena/react-ui'; +import { useRouter } from 'next/router'; +import React from 'react'; + +const GasEstimation: React.FC = () => { + const router = useRouter(); + const { cmd, hash, sigs } = router.query; + + const cmdString = cmd as string; + const hashString = hash as string; + const sigsString = sigs as string; + const sigsArray = sigsString ? sigsString.split(',') : []; + + const { loading, data, error } = useEstimateGasLimitQuery({ + variables: { + transaction: { cmd: cmdString, hash: hashString, sigs: sigsArray }, + }, + }); + + return ( +
+ + Home + Gas Estimation + + + + +
+
+ {loading && ( +
+ Waiting for gas estimation... +
+ )} + {error && } + + + + Label + Value + + + + + Cmd + {cmdString} + + + Gas Estimate + {data?.gasLimitEstimate} + + + +
+
+
+ ); +}; + +export default GasEstimation; diff --git a/packages/apps/graph/generated-schema.graphql b/packages/apps/graph/generated-schema.graphql index 97e0c486c6..49827c7de3 100644 --- a/packages/apps/graph/generated-schema.graphql +++ b/packages/apps/graph/generated-schema.graphql @@ -150,6 +150,12 @@ input PactQueryData { value: String! } +input PactTransaction { + cmd: String! + hash: String + sigs: [String!] +} + type PageInfo { endCursor: String hasNextPage: Boolean! @@ -166,6 +172,8 @@ type Query { blocksFromHeight(chainIds: [Int!], startHeight: Int!): [Block!]! chainAccount(accountName: String!, chainId: String!, moduleName: String!): ChainModuleAccount completedBlockHeights(chainIds: [String!], completedHeights: Boolean, heightCount: Int): [Block!]! + gasLimitEstimate(transaction: PactTransaction!): Int! + gasLimitEstimates(transactions: [PactTransaction!]!): [Int!]! graphConfiguration: GraphConfiguration! lastBlockHeight: BigInt node(id: ID!): Node diff --git a/packages/apps/graph/package.json b/packages/apps/graph/package.json index 113950700f..3c3e13ebcb 100644 --- a/packages/apps/graph/package.json +++ b/packages/apps/graph/package.json @@ -24,7 +24,7 @@ "prisma:generate": "prisma generate", "prisma:pull": "prisma db pull", "prisma:studio": "prisma studio", - "simulate": "ts-node -T src/scripts/devnet/index.ts traffic", + "simulate": "ts-node -T src/devnet/simulation/index.ts traffic", "start": "npx ts-node-dev --respawn --no-notify --exit-child src/index.ts", "start:generate": "pnpm run prisma:generate && npx ts-node-dev --respawn --no-notify --exit-child src/index.ts", "test": "echo \"no test specified\"" @@ -70,6 +70,7 @@ "_moduleAliases": { "@db": "src/db", "@services": "src/services", - "@utils": "src/utils" + "@utils": "src/utils", + "@devnet": "src/devnet" } } diff --git a/packages/apps/graph/src/scripts/devnet/config.ts b/packages/apps/graph/src/devnet/config.ts similarity index 100% rename from packages/apps/graph/src/scripts/devnet/config.ts rename to packages/apps/graph/src/devnet/config.ts diff --git a/packages/apps/graph/src/scripts/devnet/create-principal.ts b/packages/apps/graph/src/devnet/create-principal.ts similarity index 100% rename from packages/apps/graph/src/scripts/devnet/create-principal.ts rename to packages/apps/graph/src/devnet/create-principal.ts diff --git a/packages/apps/graph/src/scripts/devnet/crosschain-transfer.ts b/packages/apps/graph/src/devnet/crosschain-transfer.ts similarity index 100% rename from packages/apps/graph/src/scripts/devnet/crosschain-transfer.ts rename to packages/apps/graph/src/devnet/crosschain-transfer.ts diff --git a/packages/apps/graph/src/scripts/devnet/get-balance.ts b/packages/apps/graph/src/devnet/get-balance.ts similarity index 100% rename from packages/apps/graph/src/scripts/devnet/get-balance.ts rename to packages/apps/graph/src/devnet/get-balance.ts diff --git a/packages/apps/graph/src/scripts/devnet/helper.ts b/packages/apps/graph/src/devnet/helper.ts similarity index 96% rename from packages/apps/graph/src/scripts/devnet/helper.ts rename to packages/apps/graph/src/devnet/helper.ts index d6ba81ec45..bc9a3fc836 100644 --- a/packages/apps/graph/src/scripts/devnet/helper.ts +++ b/packages/apps/graph/src/devnet/helper.ts @@ -53,6 +53,11 @@ export const pollStatus = ( export const dirtyRead = (tx: IUnsignedCommand): Promise => getClient().dirtyRead(tx); +export const localReadForGasEstimation = ( + tx: IUnsignedCommand, +): Promise => + getClient().local(tx, { preflight: true, signatureVerification: false }); + export const signTransaction = (keyPairs: IKeyPair[]) => (tx: IUnsignedCommand): IUnsignedCommand | ICommand => { diff --git a/packages/apps/graph/src/scripts/devnet/safe-transfer.ts b/packages/apps/graph/src/devnet/safe-transfer.ts similarity index 100% rename from packages/apps/graph/src/scripts/devnet/safe-transfer.ts rename to packages/apps/graph/src/devnet/safe-transfer.ts diff --git a/packages/apps/graph/src/scripts/devnet/file.ts b/packages/apps/graph/src/devnet/simulation/file.ts similarity index 96% rename from packages/apps/graph/src/scripts/devnet/file.ts rename to packages/apps/graph/src/devnet/simulation/file.ts index e4f8ecc1b5..97fce3161c 100644 --- a/packages/apps/graph/src/scripts/devnet/file.ts +++ b/packages/apps/graph/src/devnet/simulation/file.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import { devnetConfig } from './config'; +import { devnetConfig } from '../config'; export interface IFileData { timestamp: number; diff --git a/packages/apps/graph/src/scripts/devnet/index.ts b/packages/apps/graph/src/devnet/simulation/index.ts similarity index 94% rename from packages/apps/graph/src/scripts/devnet/index.ts rename to packages/apps/graph/src/devnet/simulation/index.ts index 0c493d458c..80f5285b8a 100644 --- a/packages/apps/graph/src/scripts/devnet/index.ts +++ b/packages/apps/graph/src/devnet/simulation/index.ts @@ -1,7 +1,7 @@ import { Command, Option } from 'commander'; -import { generateKeyPair, logger } from './helper'; +import { generateKeyPair, logger } from '../helper'; +import { transfer } from '../transfer'; import { simulate } from './simulate'; -import { transfer } from './transfer'; const program: Command = new Command(); program diff --git a/packages/apps/graph/src/scripts/devnet/simulate.ts b/packages/apps/graph/src/devnet/simulation/simulate.ts similarity index 94% rename from packages/apps/graph/src/scripts/devnet/simulate.ts rename to packages/apps/graph/src/devnet/simulation/simulate.ts index 9fbe73f989..2d7290764d 100644 --- a/packages/apps/graph/src/scripts/devnet/simulate.ts +++ b/packages/apps/graph/src/devnet/simulation/simulate.ts @@ -1,10 +1,8 @@ import type { ChainId } from '@kadena/client'; -import { devnetConfig } from './config'; -import { crossChainTransfer } from './crosschain-transfer'; -import type { TransferType } from './file'; -import { appendToFile, createFile } from './file'; -import { getBalance } from './get-balance'; -import type { IAccount } from './helper'; +import { devnetConfig } from '../config'; +import { crossChainTransfer } from '../crosschain-transfer'; +import { getBalance } from '../get-balance'; +import type { IAccount } from '../helper'; import { generateKeyPair, getRandomNumber, @@ -12,9 +10,11 @@ import { isEqualChainAccounts, logger, seedRandom, -} from './helper'; -import { safeTransfer } from './safe-transfer'; -import { transfer } from './transfer'; +} from '../helper'; +import { safeTransfer } from '../safe-transfer'; +import { transfer } from '../transfer'; +import type { TransferType } from './file'; +import { appendToFile, createFile } from './file'; const simualtionTransferOptions: TransferType[] = [ 'xchaintransfer', @@ -180,8 +180,6 @@ export async function simulate({ accounts.push(nextAccount); } - logger.info(accounts); - await new Promise((resolve) => setTimeout(resolve, transferInterval)); } counter++; diff --git a/packages/apps/graph/src/scripts/devnet/transfer.ts b/packages/apps/graph/src/devnet/transfer.ts similarity index 70% rename from packages/apps/graph/src/scripts/devnet/transfer.ts rename to packages/apps/graph/src/devnet/transfer.ts index 85327e774b..b2cf7ba551 100644 --- a/packages/apps/graph/src/scripts/devnet/transfer.ts +++ b/packages/apps/graph/src/devnet/transfer.ts @@ -1,11 +1,13 @@ -import type { ChainId, ICommandResult } from '@kadena/client'; +import type { ChainId, ICommandResult, IUnsignedCommand } from '@kadena/client'; import { Pact } from '@kadena/client'; +import { hash as hashFunction } from '@kadena/cryptography-utils'; import { PactNumber } from '@kadena/pactjs'; import { devnetConfig } from './config'; import type { IAccount } from './helper'; import { inspect, listen, + localReadForGasEstimation, logger, sender00, signAndAssertTransaction, @@ -67,3 +69,32 @@ export async function transfer({ return result; } } +export const localReadTransfer = async ({ + cmd, + hash = undefined, + sigs = [], +}: { + cmd: string; + hash?: string | undefined | null; + sigs?: string[] | undefined | null; +}): Promise => { + if (!hash) { + hash = hashFunction(cmd); + } + + let existingSigs: IUnsignedCommand['sigs'] = []; + + if (sigs && sigs?.length > 0) { + existingSigs = sigs.map((sig) => ({ + sig: sig, + })); + } + + const transaction: IUnsignedCommand = { + cmd, + hash, + sigs: existingSigs, + }; + + return await localReadForGasEstimation(transaction); +}; diff --git a/packages/apps/graph/src/graph/Query/gasLimitEstimate.ts b/packages/apps/graph/src/graph/Query/gasLimitEstimate.ts new file mode 100644 index 0000000000..818abeefb6 --- /dev/null +++ b/packages/apps/graph/src/graph/Query/gasLimitEstimate.ts @@ -0,0 +1,62 @@ +import { localReadTransfer } from '@devnet/transfer'; +import { normalizeError } from '@utils/errors'; +import { builder } from '../builder'; + +const PactTransaction = builder.inputType('PactTransaction', { + fields: (t) => ({ + cmd: t.field({ type: 'String', required: true }), + hash: t.field({ type: 'String' }), + sigs: t.field({ type: ['String'] }), + }), +}); + +builder.queryField('gasLimitEstimate', (t) => { + return t.field({ + type: 'Int', + args: { + transaction: t.arg({ type: PactTransaction, required: true }), + }, + resolve: async (parent, args, context, info) => { + try { + if (args.transaction.cmd.includes(`\\`)) { + args.transaction.cmd = args.transaction.cmd.replace(/\\\\/g, '\\'); + } + + const result = await localReadTransfer({ + cmd: args.transaction.cmd, + hash: args.transaction.hash, + sigs: args.transaction.sigs, + }); + return result.gas; + } catch (error) { + throw normalizeError(error); + } + }, + }); +}); + +builder.queryField('gasLimitEstimates', (t) => { + return t.field({ + type: ['Int'], + args: { + transactions: t.arg({ type: [PactTransaction], required: true }), + }, + resolve: async (parent, args, context, info) => { + try { + return args.transactions.map(async (transaction) => { + if (transaction.cmd.includes('//')) { + transaction.cmd = transaction.cmd.replace(/\/\//g, '/'); + } + const result = await localReadTransfer({ + cmd: transaction.cmd, + hash: transaction.hash, + sigs: transaction.sigs, + }); + return result.gas; + }); + } catch (error) { + throw normalizeError(error); + } + }, + }); +}); diff --git a/packages/apps/graph/src/graph/Query/graphConfiguration.ts b/packages/apps/graph/src/graph/Query/graphConfiguration.ts index 3f030a00ea..d8f8f43ab8 100644 --- a/packages/apps/graph/src/graph/Query/graphConfiguration.ts +++ b/packages/apps/graph/src/graph/Query/graphConfiguration.ts @@ -1,7 +1,6 @@ -import { prismaClient } from '../../db/prismaClient'; -import { dotenv } from '../../utils/dotenv'; +import { prismaClient } from '@db/prismaClient'; +import { dotenv } from '@utils/dotenv'; import { builder } from '../builder'; - const getMinimumBlockHeight = async (): Promise => { const lowestBlock = await prismaClient.block.findFirst({ orderBy: { diff --git a/packages/apps/graph/src/graph/Query/transactionByPublicKey.ts b/packages/apps/graph/src/graph/Query/transactionByPublicKey.ts index 9fe533d171..e6bdba1cee 100644 --- a/packages/apps/graph/src/graph/Query/transactionByPublicKey.ts +++ b/packages/apps/graph/src/graph/Query/transactionByPublicKey.ts @@ -1,4 +1,4 @@ -import { prismaClient } from '../../db/prismaClient'; +import { prismaClient } from '@db/prismaClient'; import { builder } from '../builder'; builder.queryField('transactionsByPublicKey', (t) => { diff --git a/packages/apps/graph/src/graph/Subscription/event.ts b/packages/apps/graph/src/graph/Subscription/event.ts index 508839eeb2..e7cd7d56fb 100644 --- a/packages/apps/graph/src/graph/Subscription/event.ts +++ b/packages/apps/graph/src/graph/Subscription/event.ts @@ -1,8 +1,8 @@ +import { prismaClient } from '@db/prismaClient'; import type { Event } from '@prisma/client'; +import { nullishOrEmpty } from '@utils/nullishOrEmpty'; import type { Debugger } from 'debug'; import _debug from 'debug'; -import { prismaClient } from '../../db/prismaClient'; -import { nullishOrEmpty } from '../../utils/nullishOrEmpty'; import type { IContext } from '../builder'; import { builder } from '../builder'; diff --git a/packages/apps/graph/src/graph/Subscription/newBlocks.ts b/packages/apps/graph/src/graph/Subscription/newBlocks.ts index e4f5a82256..cd96e1d17e 100644 --- a/packages/apps/graph/src/graph/Subscription/newBlocks.ts +++ b/packages/apps/graph/src/graph/Subscription/newBlocks.ts @@ -1,9 +1,9 @@ +import { prismaClient } from '@db/prismaClient'; import type { Block } from '@prisma/client'; +import { dotenv } from '@utils/dotenv'; +import { nullishOrEmpty } from '@utils/nullishOrEmpty'; import type { Debugger } from 'debug'; import _debug from 'debug'; -import { prismaClient } from '../../db/prismaClient'; -import { dotenv } from '../../utils/dotenv'; -import { nullishOrEmpty } from '../../utils/nullishOrEmpty'; import type { IContext } from '../builder'; import { builder } from '../builder'; diff --git a/packages/apps/graph/src/graph/Subscription/transaction.ts b/packages/apps/graph/src/graph/Subscription/transaction.ts index ae2ffc4841..c8a276d3fb 100644 --- a/packages/apps/graph/src/graph/Subscription/transaction.ts +++ b/packages/apps/graph/src/graph/Subscription/transaction.ts @@ -1,7 +1,7 @@ +import { prismaClient } from '@db/prismaClient'; import type { Transaction } from '@prisma/client'; import type { Debugger } from 'debug'; import _debug from 'debug'; -import { prismaClient } from '../../db/prismaClient'; import type { IContext } from '../builder'; import { builder } from '../builder'; diff --git a/packages/apps/graph/src/graph/index.ts b/packages/apps/graph/src/graph/index.ts index a823d2d5e3..b8bee9a594 100644 --- a/packages/apps/graph/src/graph/index.ts +++ b/packages/apps/graph/src/graph/index.ts @@ -3,6 +3,7 @@ import './Query/block'; import './Query/blocksFromHeight'; import './Query/chainAccount'; import './Query/completedBlockHeights'; +import './Query/gasLimitEstimate'; import './Query/graphConfiguration'; import './Query/lastBlockHeight'; import './Query/pactQuery'; diff --git a/packages/apps/graph/src/services/BlocksService/index.ts b/packages/apps/graph/src/services/BlocksService/index.ts index 4c98d94df1..5bf5161bc0 100644 --- a/packages/apps/graph/src/services/BlocksService/index.ts +++ b/packages/apps/graph/src/services/BlocksService/index.ts @@ -1,4 +1,4 @@ -import { pubsub } from '../../utils/pubsub'; +import { pubsub } from '@utils/pubsub'; import { getBlocks } from './lastBlock/BlocksService'; const blocksProvider: ReturnType = getBlocks( diff --git a/packages/apps/graph/src/utils/errors.ts b/packages/apps/graph/src/utils/errors.ts index 717c764fbe..0d6ee5c3c7 100644 --- a/packages/apps/graph/src/utils/errors.ts +++ b/packages/apps/graph/src/utils/errors.ts @@ -1,6 +1,6 @@ import { PrismaClientInitializationError } from '@prisma/client/runtime/library'; +import { PactCommandError } from '@services/node-service'; import { GraphQLError } from 'graphql'; -import { PactCommandError } from '../services/node-service'; /** * Checks what type of error it is and returns a normalized GraphQLError with the correct type, message and a description that clearly translates to the user what the error means. diff --git a/packages/apps/graph/tsconfig.json b/packages/apps/graph/tsconfig.json index 1acc14abc7..154cab0c04 100644 --- a/packages/apps/graph/tsconfig.json +++ b/packages/apps/graph/tsconfig.json @@ -6,7 +6,8 @@ "paths": { "@db/*": ["./src/db/*"], "@services/*": ["./src/services/*"], - "@utils/*": ["./src/utils/*"] + "@utils/*": ["./src/utils/*"], + "@devnet/*": ["./src/devnet/*"] } } }