diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8a92ca0..18b982a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -274,6 +274,9 @@ importers: viem: specifier: ^2.15.1 version: 2.15.1(bufferutil@4.0.8)(typescript@5.4.5)(utf-8-validate@5.0.10)(zod@3.23.8) + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@faker-js/faker': specifier: ^8.3.1 @@ -25821,7 +25824,6 @@ snapshots: yocto-queue@1.0.0: {} - zod@3.23.8: - optional: true + zod@3.23.8: {} zwitch@2.0.4: {} diff --git a/sdk/package.json b/sdk/package.json index 6dc2c7a2..82fa6151 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@hypercerts-org/sdk", - "version": "2.0.0-alpha.21", + "version": "2.0.0-alpha.22", "description": "SDK for hypercerts protocol", "repository": "git@github.com:hypercerts-org/hypercerts.git", "author": "Hypercerts team", @@ -35,7 +35,8 @@ "graphql": "^16.8.1", "rollup-plugin-swc3": "^0.11.2", "urql": "^4.1.0", - "viem": "^2.15.1" + "viem": "^2.15.1", + "zod": "^3.23.8" }, "devDependencies": { "@faker-js/faker": "^8.3.1", diff --git a/sdk/rollup.config.mjs b/sdk/rollup.config.mjs index b2a4ec52..1eac73fa 100644 --- a/sdk/rollup.config.mjs +++ b/sdk/rollup.config.mjs @@ -11,11 +11,11 @@ export default [ { input: `src/index.ts`, plugins: [ - // autoExternal(), + autoExternal(), // nodePolyfills(), json(), // commonjs(), - // nodeResolve({ jsnext: true, preferBuiltins: false, browser: true }), + nodeResolve({ jsnext: true, preferBuiltins: false, browser: true }), // esbuild(), swc(), ], @@ -31,12 +31,12 @@ export default [ }, ], }, - // { - // input: `src/index.ts`, - // plugins: [dts()], - // output: { - // file: `dist/index.d.ts`, - // format: "es", - // }, - // }, + { + input: `src/index.ts`, + plugins: [dts()], + output: { + file: `dist/index.d.ts`, + format: "es", + }, + }, ]; diff --git a/sdk/src/client.ts b/sdk/src/client.ts index 9e039b8f..9afb39ff 100644 --- a/sdk/src/client.ts +++ b/sdk/src/client.ts @@ -1,15 +1,5 @@ import { HypercertMinterAbi } from "@hypercerts-org/contracts"; -import { - Account, - ByteArray, - Hex, - PublicClient, - WalletClient, - getAddress, - getContract, - http, - createPublicClient, -} from "viem"; +import { Account, ByteArray, Hex, PublicClient, WalletClient, getAddress, getContract } from "viem"; import { HypercertEvaluator } from "./evaluations"; import { HypercertIndexer } from "./indexer"; import { getStorage } from "./storage"; @@ -70,8 +60,8 @@ export class HypercertClient implements HypercertClientInterface { */ constructor(config: Partial) { this._config = getConfig({ config }); - this._publicClient = this._config?.publicClient; this._walletClient = this._config?.walletClient; + this._publicClient = this._config?.publicClient; this._storage = getStorage({ environment: this._config.environment }); this._indexer = new HypercertIndexer(this._config); this.readOnly = this._config.readOnly; @@ -175,12 +165,7 @@ export class HypercertClient implements HypercertClientInterface { * @returns a Promise that resolves to the applicable transfer restrictions. */ getTransferRestrictions = async (fractionId: bigint): Promise => { - const { publicClient } = this.getConnected(); - - const readContract = getContract({ - ...this.getContractConfig(), - client: { public: publicClient }, - }); + const readContract = this._getContract(); return readContract.read.readTransferRestriction([fractionId]) as Promise; }; @@ -318,12 +303,9 @@ export class HypercertClient implements HypercertClientInterface { fractions: bigint[], overrides?: SupportedOverrides, ): Promise<`0x${string}` | undefined> => { - const { account, publicClient } = this.getConnected(); + const { account } = this.getConnected(); - const readContract = getContract({ - ...this.getContractConfig(), - client: { public: publicClient }, - }); + const readContract = this._getContract(); const fractionOwner = (await readContract.read.ownerOf([fractionId])) as `0x${string}`; const totalUnits = (await readContract.read.unitsOf([fractionId])) as bigint; @@ -362,12 +344,9 @@ export class HypercertClient implements HypercertClientInterface { fractionIds: bigint[], overrides?: SupportedOverrides, ): Promise<`0x${string}` | undefined> => { - const { account, publicClient } = this.getConnected(); + const { account } = this.getConnected(); - const readContract = getContract({ - ...this.getContractConfig(), - client: { public: publicClient }, - }); + const readContract = this._getContract(); const fractions = await Promise.all( fractionIds.map(async (id) => ({ id, owner: (await readContract.read.ownerOf([id])) as `0x${string}` })), @@ -400,12 +379,9 @@ export class HypercertClient implements HypercertClientInterface { * @throws {ClientError} Will throw a `ClientError` if the claim is not owned by the account. */ burnClaimFraction = async (claimId: bigint, overrides?: SupportedOverrides): Promise<`0x${string}` | undefined> => { - const { account, publicClient } = this.getConnected(); + const { account } = this.getConnected(); - const readContract = getContract({ - ...this.getContractConfig(), - client: { public: publicClient }, - }); + const readContract = this._getContract(); const claimOwner = (await readContract.read.ownerOf([claimId])) as `0x${string}`; @@ -515,7 +491,7 @@ export class HypercertClient implements HypercertClientInterface { return { data, errors, success }; }; - private getContractConfig = () => { + private _getContract = () => { const { walletClient, publicClient } = this.getConnected(); const chainId = walletClient.chain?.id as SupportedChainIds; @@ -548,18 +524,12 @@ export class HypercertClient implements HypercertClientInterface { } if (this.readOnly) throw new ClientError("Client is readonly", { client: this }); if (!this._walletClient.account) throw new ClientError("No account found", { client: this }); - if (!this._publicClient) - logger.warn("No public client found; substituting with default public client from viem", "client"); + if (!this._publicClient) throw new ClientError("No public client found", { client: this }); return { walletClient: this._walletClient, account: this._walletClient.account, - publicClient: - this._publicClient ?? - createPublicClient({ - chain: this._walletClient.chain, - transport: http(), - }), + publicClient: this._publicClient, }; }; @@ -572,14 +542,14 @@ export class HypercertClient implements HypercertClientInterface { const { publicClient } = this.getConnected(); try { // Need to get the contract config before passing it to the simulateContract method - const config = this.getContractConfig(); + const readContract = this._getContract(); const { request } = await publicClient.simulateContract({ functionName, - account, + account: account.address, args, abi: HypercertMinterAbi, - address: config.address, + address: readContract.address, ...this.getCleanedOverrides(overrides), }); diff --git a/sdk/src/indexer.ts b/sdk/src/indexer.ts index c1bf89b1..bbe2d41a 100644 --- a/sdk/src/indexer.ts +++ b/sdk/src/indexer.ts @@ -65,7 +65,7 @@ export class HypercertIndexer implements HypercertIndexerInterface { return this.graphClient .query(query, variables) .toPromise() - .then((res) => { + .then((res: any) => { if (res.error) { throw res.error; } diff --git a/sdk/src/utils/config.ts b/sdk/src/utils/config.ts index cb3b7cb5..278934f6 100644 --- a/sdk/src/utils/config.ts +++ b/sdk/src/utils/config.ts @@ -1,6 +1,7 @@ import { Environment, HypercertClientConfig, SupportedChainIds } from "../types"; import { logger } from "./logger"; import { DEFAULT_ENVIRONMENT, DEPLOYMENTS, ENDPOINTS } from "../constants"; +import { createPublicClient, http } from "viem"; /** * Returns a configuration object for the Hypercert client. @@ -60,6 +61,12 @@ export const getConfig = ({ _config.readOnly = false; } + if (_config.walletClient && !_config.publicClient) { + logger.warn("No public client found; substituting with default public client from viem", "client"); + const chain = _config.walletClient.chain; + _config.publicClient = createPublicClient({ chain, transport: http() }); + } + return _config; }; diff --git a/sdk/src/utils/txParser.ts b/sdk/src/utils/txParser.ts index 6cf26b9c..2710341d 100644 --- a/sdk/src/utils/txParser.ts +++ b/sdk/src/utils/txParser.ts @@ -1,5 +1,12 @@ -import { Hash, PublicClient, decodeEventLog } from "viem"; +import { DecodeEventLogReturnType, Hash, PublicClient, decodeEventLog } from "viem"; import { HypercertMinterAbi } from "@hypercerts-org/contracts"; +import { z } from "zod"; + +const ClaimStoredLog = z.object({ + claimId: z.bigint(), + uri: z.string(), + totalUnits: z.bigint(), +}); export type ClaimStoredEvent = { claimId: bigint; @@ -28,59 +35,67 @@ export const getClaimStoredDataFromTxHash = async (client: PublicClient, hash: H hash, }); - const events = receipt.logs.map((log) => - decodeEventLog({ - abi: HypercertMinterAbi, - data: log.data, - topics: log.topics, - }), - ); - - if (!events) { - return { - errors: { - noEvents: "No events found for this transaction", - }, - success: false, - }; - } - - const claimEvent = events.find((e) => e.eventName === "ClaimStored"); + const claimEvent = receipt.logs + .map((log): DecodeEventLogReturnType | null => { + const decodedLog = decodeEventLog({ + abi: HypercertMinterAbi, + data: log.data, + topics: log?.topics, + }); + // Ensure that the decoded log matches the EventLog interface + // TODO fix hacky typing + if (decodedLog) { + return decodedLog as unknown as DecodeEventLogReturnType; + } + return null; + }) + .find((e): e is DecodeEventLogReturnType => e !== null && e.eventName === "ClaimStored"); if (!claimEvent) { return { errors: { - noClaimStoredEvent: "No ClaimStored event found", + noEvents: "No ClaimStored event found for this transaction", }, success: false, }; } - if (isClaimStoredLog(claimEvent.args)) { + try { + const parsedData = ClaimStoredLog.parse(claimEvent.args); + return { - data: claimEvent.args, + data: parsedData, success: true, }; - } else { - return { - errors: { - couldNotParseLog: "Log arguments could not be mapped to ClaimStoredEvent", - dataToParse: JSON.stringify(claimEvent.args), - }, - success: false, - }; + } catch (e: unknown) { + console.error(e); + if (typeof e === "string") { + return { + errors: { + error: e, + couldNotParseLog: "Log arguments could not be mapped to ClaimStoredEvent", + dataToParse: JSON.stringify(claimEvent.args), + }, + success: false, + }; + } else if (e instanceof Error) { + return { + errors: { + error: e.message, + couldNotParseLog: "Log arguments could not be mapped to ClaimStoredEvent", + dataToParse: JSON.stringify(claimEvent.args), + }, + success: false, + }; + } else { + return { + errors: { + error: "Unknown error, check logs for more information", + couldNotParseLog: "Log arguments could not be mapped to ClaimStoredEvent", + dataToParse: JSON.stringify(claimEvent.args), + }, + success: false, + }; + } } }; - -const isClaimStoredLog = (args: unknown): args is ClaimStoredEvent => { - return ( - typeof args === "object" && - args !== null && - "claimId" in args && - typeof args.claimId === "bigint" && - "uri" in args && - typeof args.uri === "string" && - "totalUnits" in args && - typeof args.totalUnits === "bigint" - ); -};