From e5ea67ed721f5fbcbf31e3562539bdb331038cc4 Mon Sep 17 00:00:00 2001 From: Jonas Daniels Date: Sat, 20 Jan 2024 14:02:57 +1300 Subject: [PATCH] add readonly ethers adapters --- packages/thirdweb/package.json | 18 ++++++- packages/thirdweb/src/adapters/ethers5.ts | 66 +++++++++++++++++++++++ packages/thirdweb/src/adapters/ethers6.ts | 63 ++++++++++++++++++++++ pnpm-lock.yaml | 53 +++++++++++++++++- 4 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 packages/thirdweb/src/adapters/ethers5.ts create mode 100644 packages/thirdweb/src/adapters/ethers6.ts diff --git a/packages/thirdweb/package.json b/packages/thirdweb/package.json index 80cf6e906bb..ed0427d206e 100644 --- a/packages/thirdweb/package.json +++ b/packages/thirdweb/package.json @@ -39,6 +39,11 @@ "import": "./esm/extensions/*.js", "default": "./cjs/extensions/*.js" }, + "./adapters/*": { + "types": "./types/adapters/*.d.ts", + "import": "./esm/adapters/*.js", + "default": "./cjs/adapters/*.js" + }, "./package.json": "./package.json" }, "typesVersions": { @@ -51,6 +56,9 @@ ], "extensions/*": [ "./types/extensions/*.d.ts" + ], + "adapters/*": [ + "./types/adapters/*.d.ts" ] } }, @@ -83,14 +91,20 @@ "eslint-plugin-tsdoc": "^0.2.16", "react": "^18.2.0", "rimraf": "^3.0.2", - "typescript": "^5.1.6" + "typescript": "^5.1.6", + "ethers5": "npm:ethers@^5.0.0", + "ethers6": "npm:ethers@^6.0.0" }, "peerDependencies": { - "react": ">=18" + "react": ">=18", + "ethers": "^5 || ^6" }, "peerDependenciesMeta": { "react": { "optional": true + }, + "ethers": { + "optional": true } }, "scripts": { diff --git a/packages/thirdweb/src/adapters/ethers5.ts b/packages/thirdweb/src/adapters/ethers5.ts new file mode 100644 index 00000000000..a86acebfd1a --- /dev/null +++ b/packages/thirdweb/src/adapters/ethers5.ts @@ -0,0 +1,66 @@ +import type { ThirdwebContract } from "../contract/index.js"; +import type * as ethers5 from "ethers5"; +import type * as ethers6 from "ethers6"; +import * as universalethers from "ethers"; +import type { RawClient } from "../client/client.js"; + +type Ethers5 = typeof ethers5; + +function isEthers5( + ethers_: typeof ethers5 | typeof ethers6, +): ethers_ is typeof ethers5 { + return "providers" in ethers_; +} + +function assertEthers5( + ethers_: typeof ethers5 | typeof ethers6, +): asserts ethers_ is typeof ethers5 { + if (!isEthers5(ethers_)) { + throw new Error( + "You seem to be using ethers@6, please use the `ethers6Adapter()", + ); + } +} + +export function ethers5Adapter() { + const ethers_ = universalethers; + assertEthers5(ethers_); + return { + toProvider: (client: RawClient, chainId: number) => + toProvider(ethers_, client, chainId), + toContract: (contract: ThirdwebContract, abi?: ethers5.ContractInterface) => + toContract(ethers_, contract, abi), + }; +} + +function toProvider( + ethers: Ethers5, + client: RawClient, + chainId: number, +): ethers5.providers.Provider { + const url = `https://${chainId}.rpc.thirdweb.com/${client.clientId}`; + const headers: HeadersInit = {}; + if (client.secretKey) { + headers["x-secret-key"] = client.secretKey; + } + return new ethers.providers.JsonRpcProvider({ + url, + headers: headers, + }); +} + +async function toContract( + ethers: Ethers5, + contract: ThirdwebContract, + abi?: ethers5.ContractInterface, +): Promise { + // TODO handle signers as well + // resolve the ABI if it is not explicitly passed + if (!abi) { + const { resolveAbi } = await import("../abi/resolveContractAbi.js"); + // this is compatible with ethers5 + abi = (await resolveAbi(contract)) as ethers5.ContractInterface; + } + const provider = toProvider(ethers, contract, contract.chainId); + return new ethers.Contract(contract.address, abi, provider); +} diff --git a/packages/thirdweb/src/adapters/ethers6.ts b/packages/thirdweb/src/adapters/ethers6.ts new file mode 100644 index 00000000000..deb046359ed --- /dev/null +++ b/packages/thirdweb/src/adapters/ethers6.ts @@ -0,0 +1,63 @@ +import type { ThirdwebContract } from "../contract/index.js"; +import type * as ethers5 from "ethers5"; +import type * as ethers6 from "ethers6"; +import * as universalethers from "ethers"; +import type { RawClient } from "../client/client.js"; + +type Ethers6 = typeof ethers6; + +function isEthers5( + ethers_: typeof ethers5 | typeof ethers6, +): ethers_ is typeof ethers5 { + return "providers" in ethers_; +} + +function assertEthers6( + ethers_: typeof ethers5 | typeof ethers6, +): asserts ethers_ is typeof ethers6 { + if (isEthers5(ethers_)) { + throw new Error( + "You seem to be using ethers@5, please use the `ethers5Adapter()", + ); + } +} + +export function ethers6Adapter() { + const ethers_ = universalethers; + assertEthers6(ethers_); + return { + toProvider: (client: RawClient, chainId: number) => + toProvider(ethers_, client, chainId), + toContract: (contract: ThirdwebContract, abi?: ethers6.InterfaceAbi) => + toContract(ethers_, contract, abi), + }; +} + +function toProvider(ethers_: Ethers6, client: RawClient, chainId: number) { + const url = `https://${chainId}.rpc.thirdweb.com/${client.clientId}`; + + const fetchRequest = new ethers_.FetchRequest(url); + if (client.secretKey) { + fetchRequest.setHeader("x-secret-key", client.secretKey); + } + + return new ethers_.JsonRpcProvider(fetchRequest, chainId, { + staticNetwork: true, + }); +} + +async function toContract( + ethers_: Ethers6, + contract: ThirdwebContract, + abi?: ethers6.InterfaceAbi, +) { + // TODO handle signers as well + // resolve the ABI if it is not explicitly passed + if (!abi) { + const { resolveAbi } = await import("../abi/resolveContractAbi.js"); + // this is compatible with Ethers6 + abi = (await resolveAbi(contract)) as ethers6.InterfaceAbi; + } + const provider = toProvider(ethers_, contract, contract.chainId); + return new ethers_.Contract(contract.address, abi, provider); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 041bf28019c..837bc84d82a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1426,6 +1426,9 @@ importers: abitype: specifier: ^0.10.3 version: 0.10.3(typescript@5.3.3) + ethers: + specifier: ^5 || ^6 + version: 5.7.2 viem: specifier: ^2.0.10 version: 2.0.10(typescript@5.3.3) @@ -1445,6 +1448,12 @@ importers: eslint-plugin-tsdoc: specifier: ^0.2.16 version: 0.2.17 + ethers5: + specifier: npm:ethers@^5.0.0 + version: /ethers@5.7.2 + ethers6: + specifier: npm:ethers@^6.0.0 + version: /ethers@6.10.0 react: specifier: ^18.2.0 version: 18.2.0 @@ -2009,7 +2018,6 @@ packages: /@adraffy/ens-normalize@1.10.0: resolution: {integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==} - dev: false /@adraffy/ens-normalize@1.9.4: resolution: {integrity: sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw==} @@ -12242,7 +12250,7 @@ packages: dependencies: '@babel/runtime': 7.23.8 '@noble/ed25519': 1.7.3 - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@noble/secp256k1': 1.7.1 '@solana/buffer-layout': 4.0.1 agentkeepalive: 4.3.0 @@ -13304,6 +13312,10 @@ packages: dev: false optional: true + /@types/node@18.15.13: + resolution: {integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==} + dev: true + /@types/node@18.17.1: resolution: {integrity: sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==} @@ -14706,6 +14718,10 @@ packages: /aes-js@3.0.0: resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} + /aes-js@4.0.0-beta.5: + resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} + dev: true + /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -19405,6 +19421,22 @@ packages: - bufferutil - utf-8-validate + /ethers@6.10.0: + resolution: {integrity: sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==} + engines: {node: '>=14.0.0'} + dependencies: + '@adraffy/ens-normalize': 1.10.0 + '@noble/curves': 1.2.0 + '@noble/hashes': 1.3.2 + '@types/node': 18.15.13 + aes-js: 4.0.0-beta.5 + tslib: 2.4.0 + ws: 8.5.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /ethjs-unit@0.1.6: resolution: {integrity: sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==} engines: {node: '>=6.5.0', npm: '>=3'} @@ -30278,6 +30310,10 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + /tslib@2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + dev: true + /tslib@2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} @@ -32180,6 +32216,19 @@ packages: bufferutil: 4.0.8 utf-8-validate: 5.0.10 + /ws@8.5.0: + resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /ws@8.9.0: resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==} engines: {node: '>=10.0.0'}