diff --git a/package.json b/package.json index 2dd025f9..31a82a62 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "ethers": "^6.1.0", "express": "~4.18.2", "graphql": "^16.5.0", + "klona": "^2.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hooks-global-state": "^2.1.0", diff --git a/packages/apps/examples/sdk-api/src/client.ts b/packages/apps/examples/sdk-api/src/client.ts index eabb6939..1324773c 100644 --- a/packages/apps/examples/sdk-api/src/client.ts +++ b/packages/apps/examples/sdk-api/src/client.ts @@ -11,6 +11,10 @@ if (process.env.ADDITIONAL_SDK_HEADER_KEY) { }; } export const api = new QuickNode.API(opts); +export const apiPolygon = new QuickNode.API({ + ...opts, + defaultChain: 'polygon', +}); export const nfts = api.nfts; export const tokens = api.tokens; export const utils = api.utils; diff --git a/packages/apps/examples/sdk-api/src/controllers/nft.controller.ts b/packages/apps/examples/sdk-api/src/controllers/nft.controller.ts index 9ce4e074..60e6d22e 100644 --- a/packages/apps/examples/sdk-api/src/controllers/nft.controller.ts +++ b/packages/apps/examples/sdk-api/src/controllers/nft.controller.ts @@ -1,5 +1,5 @@ import { Request, Response } from 'express'; -import { nfts } from '../client'; +import { nfts, api, apiPolygon } from '../client'; export default { getNFTsByWallet: async (req: Request, res: Response) => { @@ -79,4 +79,53 @@ export default { return res.status(500).send({}); } }, + + twoChainQuery: async (req: Request, res: Response) => { + function fetchNFTs( + qn: any, + chain: any, + address: any, + contractAddress: any + ) { + console.log(`Querying NFTs from ${chain.toUpperCase()}...`); + return qn.nfts.getByWallet({ + address: address, + first: 5, + filter: { + contractTokens: [{ contractAddress: contractAddress }], + }, + }); + } + + async function getNFTsByWallet() { + try { + // Define addresses and contract addresses for each chain + const polygonAddress = '0xA23FcB4645cc618549Da1b61b8564429C2C32Ff9'; + const polygonContract = '0xaa8c6b9d67149439680b67ce395c4ac2d233b6de'; + const ethereumAddress = '0x01cFd327D29d9C6a07b7613494797A67E89570a0'; + const ethereumContract = '0xd77e17ecc3942b6e83f67c56999c5230c70a85a4'; + + const polygonResults = await fetchNFTs( + apiPolygon, + 'polygon', + polygonAddress, + polygonContract + ); + const ethereumResults = await fetchNFTs( + api, + 'ethereum', + ethereumAddress, + ethereumContract + ); + + console.log('Polygon Results:', polygonResults.results); + console.log('Ethereum Results:', ethereumResults.results); + return res.status(200).send({}); + } catch (error) { + console.error('An error occurred:', error); + } + } + + getNFTsByWallet(); + }, }; diff --git a/packages/apps/examples/sdk-api/src/router.ts b/packages/apps/examples/sdk-api/src/router.ts index 6ee2c756..ab50b669 100644 --- a/packages/apps/examples/sdk-api/src/router.ts +++ b/packages/apps/examples/sdk-api/src/router.ts @@ -39,6 +39,7 @@ router.get( '/api/verifyOwnership/:walletAddress/:contractAddress', nftController.verifyOwnershipByAddress ); +router.get('/api/twoChainQuery', nftController.twoChainQuery); router.get('/api/graphQuery', graphController.graphQuery); router.get( '/api/getBalancesByWallet/:address', diff --git a/packages/libs/sdk/package.json b/packages/libs/sdk/package.json index ce909e89..7436cb74 100644 --- a/packages/libs/sdk/package.json +++ b/packages/libs/sdk/package.json @@ -6,7 +6,7 @@ "directory": "packages/libs/sdk" }, "license": "MIT", - "version": "1.1.3", + "version": "1.1.4", "main": "./cjs/index.js", "module": "./esm/src/index.js", "types": "./index.d.ts", @@ -16,6 +16,7 @@ "@urql/exchange-graphcache": "^6.0.4", "cross-fetch": "^3.1.6", "graphql": "^16.6.0", + "klona": "^2.0.6", "tslib": "^2.5.3", "viem": "^1.2.0", "zod": "^3.21.4" diff --git a/packages/libs/sdk/spec/recordings/query-getTrendingCollections-noResults_106515278/recording.har b/packages/libs/sdk/spec/recordings/query-getTrendingCollections-noResults_106515278/recording.har index cf90b847..5bf56095 100644 --- a/packages/libs/sdk/spec/recordings/query-getTrendingCollections-noResults_106515278/recording.har +++ b/packages/libs/sdk/spec/recordings/query-getTrendingCollections-noResults_106515278/recording.har @@ -8,11 +8,11 @@ }, "entries": [ { - "_id": "20a50a9e08e6db7d1e71609537b2b4c7", + "_id": "1b8d0d75774c19e27cdab0fb50113d68", "_order": 0, "cache": {}, "request": { - "bodySize": 1109, + "bodySize": 1116, "cookies": [], "headers": [ { @@ -28,7 +28,7 @@ { "_fromType": "array", "name": "x-quicknode-sdk-version", - "value": "1.1.1" + "value": "1.1.3" }, { "_fromType": "array", @@ -38,7 +38,7 @@ { "_fromType": "array", "name": "content-length", - "value": "1109" + "value": "1116" }, { "_fromType": "array", @@ -66,24 +66,24 @@ "postData": { "mimeType": "application/json", "params": [], - "text": "{\"operationName\":\"EthMainnetTrendingCollections\",\"query\":\"query EthMainnetTrendingCollections($first: Int, $before: String, $after: String) {\\n ethereum {\\n ...NftTrendingCollections\\n __typename\\n }\\n}\\nfragment Pagination on PageInfo {\\n endCursor\\n hasNextPage\\n hasPreviousPage\\n startCursor\\n __typename\\n}\\nfragment TrendingCollectionInfo on Collection {\\n address\\n baseTokenUri\\n circulatingSupply\\n description\\n externalUrl\\n image {\\n height\\n mimeType\\n url\\n width\\n __typename\\n }\\n name\\n openseaMetadata {\\n isHidden\\n isVerified\\n unsafeSlug\\n __typename\\n }\\n symbol\\n totalSupply\\n twitterUsername\\n __typename\\n}\\nfragment NftTrendingCollections on EVMSchemaType {\\n trendingCollections(first: $first, before: $before, after: $after) {\\n pageInfo {\\n ...Pagination\\n __typename\\n }\\n edges {\\n node {\\n collection {\\n ...TrendingCollectionInfo\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n __typename\\n}\",\"variables\":{\"first\":2}}" + "text": "{\"operationName\":\"EthMainnetTrendingCollections\",\"query\":\"query EthMainnetTrendingCollections($first: Int, $before: String, $after: String) {\\n ethereumSepolia {\\n ...NftTrendingCollections\\n __typename\\n }\\n}\\nfragment Pagination on PageInfo {\\n endCursor\\n hasNextPage\\n hasPreviousPage\\n startCursor\\n __typename\\n}\\nfragment TrendingCollectionInfo on Collection {\\n address\\n baseTokenUri\\n circulatingSupply\\n description\\n externalUrl\\n image {\\n height\\n mimeType\\n url\\n width\\n __typename\\n }\\n name\\n openseaMetadata {\\n isHidden\\n isVerified\\n unsafeSlug\\n __typename\\n }\\n symbol\\n totalSupply\\n twitterUsername\\n __typename\\n}\\nfragment NftTrendingCollections on EVMSchemaType {\\n trendingCollections(first: $first, before: $before, after: $after) {\\n pageInfo {\\n ...Pagination\\n __typename\\n }\\n edges {\\n node {\\n collection {\\n ...TrendingCollectionInfo\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n __typename\\n }\\n __typename\\n}\",\"variables\":{\"first\":2}}" }, "queryString": [], "url": "https://api.quicknode.com/graphql" }, "response": { - "bodySize": 1007, + "bodySize": 276, "content": { "encoding": "base64", "mimeType": "application/json", - "size": 1007, - "text": "[\"H4sIAAAAAAAA/8RWXW/bRhD8K8Q+0+KnSIpvhWCkBeLWrWQ/NAiCJW9Jnn0f7N3RlmDovxfUV6wYbRTDcN6o1Wh2Zm9Oyydg6BDKJyDXkaFBjs/OkGJctXMtBNWOa2XHco8t/aYavYUrNh+M1QZKWMZ/yzq+Df+MZ0Mlb+/YB/FQ3ekV+NCh/Z1W7hpbgtKZgbala0MPXA92V25QWPLBOjTue5yP4MOXL27dk0JJUML1QdPGB2ItWSg/PYHSjEaV9dHA+AkZM2QtlBCukqRhaZzNwmkTpbOmSPKM4jhnYT1jSZKGiCzKsYnAhwotLfU9qRvDoVSDED7U3NSDQMdVuxj6XqyhjJLMB0a2NrzfddxBaeXIKBQ3RkAJnXO9LYMgm8azCdfBV4U2cB1dSJJkA/CBy+1wdhx7s8uOvKsR4FVrb2QAH3RPyhJekcPDUXL7K2eM1HG03N6S4Q0ndiwNymJDCzG0UILraNt3T3ky4D96Uotn/Bsf7FpWWhy0Oe1QHIawLz1y58jc2NG4PLo44b38ax5F0+nXiI3MJ4jlixS+gFzeXi3qjiQu1z29xNu5Vmr3eMlago1/TjTyrKKkyuIiT9IsZnGUxmk4TZNZnuc0y6N8mlJBef4D\",\"0Qiz4ptswDUaFIKEx62H3qLmFw33DtIrQd4cDfM+oCQP/itE2PcTg2aET2otn4Up+CEb+6x9eoKOeNu5UXHog+SSxsFCuUMEdz214MNwImL7lZ38M/D6fhzu5FGbezJ2wughWNlgTE3QaUnWEbKAK0arYFC90TVZS+yZ6osdV5BHrCaWplEeRzEWOM2ajCIMpywOWRE1VTGjBHG8nI+cuW6v9zQ922PphUa2PfeDs/iNnFn5Ps7is52lb+RMsvdxlp7trHgjZ6J9H2fF2c6it4rjSrzTRftOHj8fl9XxD+4X0Xf4ik21e104WVT9nhP3lOduKvj4Ec7bVMcWqnHfNvgpK+vzq3///61hs9n8CwAA//8=\"]" + "size": 276, + "text": "[\"H4sIAAAAAAAA/4TPQUvDQBAF4P8y5xwkNxd6CiIeKikNOShSptnXJGV3J+xOqqXsf5dGPKhIb8PHvMfMhSwrk7kQdEDE7LeYxI0LaUSwY+grcQ6djhLSlSfu8RQOsqSCreaYJJKhpnzxXdnebcr7ee/bo310p/1RdL1ZraiggdMzPrTmHmQO7BIWqyNOo8zphyflqLeK36mg3U7PEwJ7kKH6+7BcEGyPROb17dfOQ7vedgM8N+cJzd8HKwnha7y2/B+lnPMnAAAA//8=\"]" }, "cookies": [], "headers": [ { "name": "date", - "value": "Mon, 28 Aug 2023 15:36:14 GMT" + "value": "Thu, 21 Sep 2023 18:24:12 GMT" }, { "name": "content-type", @@ -127,7 +127,7 @@ }, { "name": "x-ratelimit-rpmreset", - "value": "1693237033" + "value": "1695320712" }, { "name": "x-ratelimit-rpslimit", @@ -139,7 +139,11 @@ }, { "name": "x-ratelimit-rpsreset", - "value": "1693236974" + "value": "1695320653" + }, + { + "name": "x-request-cost", + "value": "0.000000" }, { "name": "cf-cache-status", @@ -167,21 +171,21 @@ }, { "name": "cf-ray", - "value": "7fdda0ecc8f90f4b-EWR" + "value": "80a458003c5643f9-EWR" }, { "name": "alt-svc", "value": "h3=\":443\"; ma=86400" } ], - "headersSize": 873, + "headersSize": 899, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2023-08-28T15:36:13.264Z", - "time": 986, + "startedDateTime": "2023-09-21T18:24:12.581Z", + "time": 310, "timings": { "blocked": -1, "connect": -1, @@ -189,7 +193,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 986 + "wait": 310 } } ], diff --git a/packages/libs/sdk/src/api/graphql/modifyQueryForChain.ts b/packages/libs/sdk/src/api/graphql/modifyQueryForChain.ts index ee20ff98..19af6374 100644 --- a/packages/libs/sdk/src/api/graphql/modifyQueryForChain.ts +++ b/packages/libs/sdk/src/api/graphql/modifyQueryForChain.ts @@ -1,6 +1,7 @@ import { DefinitionNode, FieldNode, Kind, DocumentNode } from 'graphql'; import { TypedDocumentNode } from '@urql/core'; import { ChainName } from '../types/chains'; +import { klona } from 'klona'; type Mutable = { -readonly [k in keyof T]: T[k]; @@ -8,11 +9,14 @@ type Mutable = { type MutableDocumentNode = Mutable; -// Takes the generated query document and modifies the chain name to the one passed in export function modifyQueryForChain( chainName: ChainName, - documentNode: MutableDocumentNode + originalDocumentNode: MutableDocumentNode ): TypedDocumentNode { + // We need to deep clone the document node in order to not mutate the query so it is consistent + // across multiple calls to the same query with different chains + const documentNode = klona(originalDocumentNode); + documentNode.definitions = documentNode.definitions.map( (doc: DefinitionNode) => { if (doc.kind === Kind.OPERATION_DEFINITION) { @@ -25,9 +29,7 @@ export function modifyQueryForChain( ) { const updatedChainSelection: FieldNode = { ...selection, - ...{ - name: { ...selection.name, value: chainName }, - }, + name: { ...selection.name, value: chainName }, }; return updatedChainSelection; } @@ -38,5 +40,6 @@ export function modifyQueryForChain( return doc; } ); + return documentNode; } diff --git a/packages/libs/sdk/yarn.lock b/packages/libs/sdk/yarn.lock index a4cd1829..bf6e8c96 100644 --- a/packages/libs/sdk/yarn.lock +++ b/packages/libs/sdk/yarn.lock @@ -572,15 +572,6 @@ "@graphql-codegen/plugin-helpers" "^2.6.2" tslib "~2.4.0" -"@graphql-codegen/introspection@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@graphql-codegen/introspection/-/introspection-3.0.1.tgz#403c9bb12abf998a3bd37a519eb0fd01b537d64d" - integrity sha512-D6vJQTEL/np4EmeUHm5spLK59cr+AMXEoLRoTI+dagFzlHYDTfXZH6F7uhKaakxcj0SAQhIWKvGMggotUdEtyg== - dependencies: - "@graphql-codegen/plugin-helpers" "^4.1.0" - "@graphql-codegen/visitor-plugin-common" "^3.0.1" - tslib "~2.5.0" - "@graphql-codegen/plugin-helpers@^2.6.2": version "2.7.1" resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.7.1.tgz#de999bdf8a1485f6f3c4c5f21cfb038e99d67e3e" @@ -593,7 +584,7 @@ lodash "~4.17.0" tslib "~2.4.0" -"@graphql-codegen/plugin-helpers@^4.1.0", "@graphql-codegen/plugin-helpers@^4.2.0": +"@graphql-codegen/plugin-helpers@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-4.2.0.tgz#8324914d0f99162a223cfa01796cdd6be972d2ae" integrity sha512-THFTCfg+46PXlXobYJ/OoCX6pzjI+9woQqCjdyKtgoI0tn3Xq2HUUCiidndxUpEYVrXb5pRiRXb7b/ZbMQqD0A== @@ -675,7 +666,7 @@ parse-filepath "^1.0.2" tslib "~2.4.0" -"@graphql-codegen/visitor-plugin-common@3.1.1", "@graphql-codegen/visitor-plugin-common@^3.0.1": +"@graphql-codegen/visitor-plugin-common@3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-3.1.1.tgz#50c2aa3c537a805ce68d2f115d0a9811b151428c" integrity sha512-uAfp+zu/009R3HUAuTK2AamR1bxIltM6rrYYI6EXSmkM3rFtFsLTuJhjUDj98HcUCszJZrADppz8KKLGRUVlNg== @@ -2798,6 +2789,11 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== +klona@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" diff --git a/yarn.lock b/yarn.lock index 32a2143d..6ac6b050 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6924,7 +6924,7 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -klona@^2.0.4, klona@^2.0.5: +klona@^2.0.4, klona@^2.0.5, klona@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==