diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a1e83d0c..fef43513 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: cache: "yarn" - name: Install SDK dependencies - run: yarn + run: yarn install --frozen-lockfile - name: Build SDK run: yarn build diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index 9c557371..6bdc3eb7 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -12,7 +12,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1 with: - node-version: "16.20" + node-version: "18.13" cache: "yarn" - name: Install dependencies @@ -32,25 +32,28 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v1 with: - node-version: "16.20" + node-version: "18.13" cache: "yarn" - name: Install dependencies run: yarn install --frozen-lockfile - - name: Lint - run: yarn lint:check - - - name: Build + - name: Build SDK run: yarn build + - name: Link SDK + run: yarn --cwd packages/accounts link + + - name: Install E2E tests + run: yarn --cwd e2e-tests link @zerodev/sdk && yarn --cwd e2e-tests + - name: E2E tests env: API_URL: ${{ secrets.API_URL }} API_KEY: ${{ secrets.API_KEY }} TEAM_ID: ${{ secrets.TEAM_ID }} PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - run: yarn test:e2e + run: yarn --cwd e2e-tests test:e2e # - name: Unit Test # run: yarn test diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 21cf9646..abf91ef9 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -1,6 +1,6 @@ { "name": "@zerodev/sdk", - "version": "4.0.31", + "version": "4.0.32", "description": "A collection of ERC-4337 compliant smart contract account interfaces", "author": "ZeroDev", "license": "MIT", @@ -44,7 +44,9 @@ "test:run": "vitest run" }, "devDependencies": { - "@turnkey/ethers": "^0.16.5", + "@turnkey/api-key-stamper": "^0.1.1", + "@turnkey/http": "^1.2.0", + "@turnkey/viem": "^0.2.4", "typescript": "^5.0.4", "typescript-template": "*", "vitest": "^0.31.0" diff --git a/packages/accounts/src/kernel-zerodev/client/create-client.ts b/packages/accounts/src/kernel-zerodev/client/create-client.ts index cca9b91e..8ca2b7c0 100644 --- a/packages/accounts/src/kernel-zerodev/client/create-client.ts +++ b/packages/accounts/src/kernel-zerodev/client/create-client.ts @@ -40,6 +40,7 @@ export const createZeroDevPublicErc4337Client = ({ }: ZeroDevClientConfig): PublicErc4337Client => { const erc4337Transport = http(rpcUrl, { fetchOptions: { + // @ts-ignore headers: rpcUrl === BUNDLER_URL ? { projectId, bundlerProvider } : {}, }, name: "Connected bundler network", diff --git a/packages/accounts/src/kernel-zerodev/owner/getCustodialOwner.ts b/packages/accounts/src/kernel-zerodev/owner/getCustodialOwner.ts index d215fdae..195e3d49 100644 --- a/packages/accounts/src/kernel-zerodev/owner/getCustodialOwner.ts +++ b/packages/accounts/src/kernel-zerodev/owner/getCustodialOwner.ts @@ -1,9 +1,30 @@ -import type { SmartAccountSigner } from "@alchemy/aa-core"; +import type { SignTypedDataParams, SmartAccountSigner } from "@alchemy/aa-core"; import { API_URL } from "../constants.js"; import axios from "axios"; -import type { Hex } from "viem"; -import type { SignTypedDataParams } from "@alchemy/aa-core"; +import { TurnkeyClient } from "@turnkey/http"; +/** + * Params to get a custodial owner + * Priv / pub key combo or custodial file path should be in different types + */ +type GetCustodialOwnerParams = { + // ZeroDev api url + apiUrl?: string; + // Turnkey client (if the client want to reuse it + turnKeyClient?: TurnkeyClient; + + // Direct access to priv / pub key + privateKey?: string; + publicKey?: string; + keyId?: string; + + // Or read them from the custodial file path + custodialFilePath?: string; +}; + +/** + * Returns a signer for a custodial wallet via TurnKey + */ export async function getCustodialOwner( identifier: string, { @@ -12,14 +33,10 @@ export async function getCustodialOwner( publicKey, keyId, apiUrl = API_URL, - }: { - privateKey?: string; - publicKey?: string; - keyId?: string; - custodialFilePath?: string; - apiUrl?: string; - } + turnKeyClient, + }: GetCustodialOwnerParams ): Promise { + // Extract data from the custodial file path if provided if (custodialFilePath) { let fsModule; try { @@ -30,45 +47,86 @@ export async function getCustodialOwner( } const data = fsModule.readFileSync(custodialFilePath, "utf8"); const values = data.split("\n"); + // Override the values, if they are provided, to the one from the custodial file [privateKey, publicKey, keyId] = values; } - let TurnkeySigner; - try { - TurnkeySigner = - require.resolve("@turnkey/ethers") && - require("@turnkey/ethers").TurnkeySigner; - } catch (error) { - console.log( - "@turnkey/ethers module not available. Skipping FS operation..." - ); - return; - } + + // Ensure we have the required values if (!privateKey || !publicKey || !keyId) { throw new Error( "Must provide custodialFilePath or privateKey, publicKey, and keyId." ); } - const response = await axios.post(`${apiUrl}/wallets/${identifier}`, { - keyId, - }); + // Get our turnkey client (build it if needed) + if (!turnKeyClient) { + // Try to fetch turnkey client & api key stamper + let TurnkeyClient; + let ApiKeyStamper; + // TODO: Would be cleaner with something like radash and a tryit function? + try { + TurnkeyClient = + require.resolve("@turnkey/http") && + require("@turnkey/http").TurnkeyClient; + ApiKeyStamper = + require.resolve("@turnkey/api-key-stamper") && + require("@turnkey/api-key-stamper").ApiKeyStamper; + } catch (error) { + console.log( + "@turnkey/http or @turnkey/api-key-stamper module not available. Skipping FS operation..." + ); + return; + } - const turnkeySigner = new TurnkeySigner({ - apiPublicKey: publicKey, - apiPrivateKey: privateKey, - baseUrl: "https://api.turnkey.com", + // Build the turnkey client + turnKeyClient = new TurnkeyClient( + { + baseUrl: "https://api.turnkey.com", + }, + new ApiKeyStamper({ + apiPublicKey: publicKey, + apiPrivateKey: privateKey, + }) + ); + } + + // Get the wallet identifier from the API + const response = await axios.post( + `${apiUrl}/wallets/${identifier}`, + { + keyId, + } + ); + + // Build the turnkey viem account + let createAccount; + try { + createAccount = + require.resolve("@turnkey/viem") && + require("@turnkey/viem").createAccount; + } catch (error) { + console.log("@turnkey/viem module not available. Skipping FS operation..."); + return; + } + const turnkeySigner = await createAccount({ + client: turnKeyClient, organizationId: keyId, privateKeyId: response.data.walletId, }); + + // Return an alchemy AA signer from the turnkey signer return { - getAddress: async () => (await turnkeySigner.getAddress()) as `0x${string}`, - signMessage: async (msg: Uint8Array | string) => - (await turnkeySigner.signMessage(msg)) as `0x${string}`, + getAddress: async () => turnkeySigner.address, + signMessage: async (msg: Uint8Array | string) => { + if (typeof msg === "string") { + // If the msg is a string, sign it directly + return turnkeySigner.signMessage({ message: msg }); + } else { + // Otherwise, sign the raw data + return turnkeySigner.signMessage({ message: { raw: msg } }); + } + }, signTypedData: async (params: SignTypedDataParams) => - (await turnkeySigner.signTypedData( - params.domain, - params.types, - params.message - )) as Hex, + turnkeySigner.signTypedData(params), }; } diff --git a/packages/accounts/src/kernel-zerodev/owner/passkey/createPasskeyOwner.ts b/packages/accounts/src/kernel-zerodev/owner/passkey/createPasskeyOwner.ts index ee3f18df..c29142f7 100644 --- a/packages/accounts/src/kernel-zerodev/owner/passkey/createPasskeyOwner.ts +++ b/packages/accounts/src/kernel-zerodev/owner/passkey/createPasskeyOwner.ts @@ -27,7 +27,6 @@ export async function createPasskeyOwner({ fallback?: () => Promise; apiUrl?: string; }): Promise { - //@ts-expect-error if (typeof window !== "undefined") { const challenge = generateRandomBuffer(); let credentials = undefined; @@ -43,9 +42,7 @@ export async function createPasskeyOwner({ const attestation = await getWebAuthnAttestation({ publicKey: { rp: { - //@ts-expect-error id: window.location.hostname, - //@ts-expect-error name: window.location.hostname, }, authenticatorSelection: { @@ -100,7 +97,6 @@ export async function createPasskeyOwner({ }; return owner; } catch (e) { - //@ts-expect-error if (e instanceof DOMException && e.name === "InvalidStateError") { if (fallback) return await fallback(); } diff --git a/packages/accounts/src/kernel-zerodev/owner/passkey/getAutocompletePasskeyOwner.ts b/packages/accounts/src/kernel-zerodev/owner/passkey/getAutocompletePasskeyOwner.ts index 457ea006..a8570709 100644 --- a/packages/accounts/src/kernel-zerodev/owner/passkey/getAutocompletePasskeyOwner.ts +++ b/packages/accounts/src/kernel-zerodev/owner/passkey/getAutocompletePasskeyOwner.ts @@ -28,7 +28,6 @@ export async function getAutocompletePasskeyOwner({ return; } - //@ts-expect-error if (typeof window !== "undefined") { const challenge = generateRandomBuffer(); try { @@ -37,7 +36,6 @@ export async function getAutocompletePasskeyOwner({ await getWebAuthnAssertion(base64UrlEncode(challenge), { mediation: "conditional", publicKey: { - //@ts-expect-error rpId: window.location.hostname, userVerification: "required", }, diff --git a/packages/accounts/src/kernel-zerodev/owner/passkey/getPasskeyOwner.ts b/packages/accounts/src/kernel-zerodev/owner/passkey/getPasskeyOwner.ts index c4304527..90663aed 100644 --- a/packages/accounts/src/kernel-zerodev/owner/passkey/getPasskeyOwner.ts +++ b/packages/accounts/src/kernel-zerodev/owner/passkey/getPasskeyOwner.ts @@ -22,7 +22,6 @@ export async function getPasskeyOwner({ apiUrl?: string; withCredentials?: boolean; }): Promise { - //@ts-expect-error if (typeof window !== "undefined") { const challenge = generateRandomBuffer(); try { @@ -30,7 +29,6 @@ export async function getPasskeyOwner({ const assertion = JSON.parse( await getWebAuthnAssertion(base64UrlEncode(challenge), { publicKey: { - //@ts-expect-error rpId: window.location.hostname, userVerification: "required", allowCredentials: withCredentials diff --git a/packages/accounts/src/kernel-zerodev/owner/passkey/utils.ts b/packages/accounts/src/kernel-zerodev/owner/passkey/utils.ts index d2d6006a..ca212e90 100644 --- a/packages/accounts/src/kernel-zerodev/owner/passkey/utils.ts +++ b/packages/accounts/src/kernel-zerodev/owner/passkey/utils.ts @@ -23,7 +23,6 @@ export const es256 = -7; export const generateRandomBuffer = (): ArrayBuffer => { const arr = new Uint8Array(32); - //@ts-expect-error crypto.getRandomValues(arr); return arr.buffer; }; diff --git a/yarn.lock b/yarn.lock index 127c80cd..19070c94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3917,21 +3917,37 @@ "@tufjs/canonical-json" "1.0.0" minimatch "^9.0.0" -"@turnkey/ethers@^0.16.5": - version "0.16.5" - resolved "https://registry.yarnpkg.com/@turnkey/ethers/-/ethers-0.16.5.tgz#1ec887d64fcae87f67863a27587f31b044b15421" - integrity sha512-g33kCZowrh62qUCCvcG4EsDuv86sOaY0oyyeu7B39DS9xq2Oj8YDNPVh9JefKRgQaSbAmt3INz768FNAEJDCDA== - dependencies: - "@ethersproject/abstract-signer" "^5.7.0" - "@turnkey/http" "1.0.1" +"@turnkey/api-key-stamper@0.1.1", "@turnkey/api-key-stamper@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@turnkey/api-key-stamper/-/api-key-stamper-0.1.1.tgz#7a5b5cd9cee7bd05059b87ccf66bfc264dbbd8a0" + integrity sha512-qs8fmWFiAhh7fAByx7j9/2F0VRe/qD7Yq3hi4FwD+jLU0CXdE+aim1cmJpAFFPKF1PBmPs6jh4XBODE40eqEEg== -"@turnkey/http@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@turnkey/http/-/http-1.0.1.tgz#21aadcbbe67c60278ce187bbe6642ffa614e7779" - integrity sha512-Tp2SKHeZa03HYMnXDFfAUw1akS+VBwTKOqKe2GOusLVQAcOXMhLIXCjipRkAfYynD/kDXPkr8sEVhDXxdSejsw== +"@turnkey/http@1.3.0", "@turnkey/http@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@turnkey/http/-/http-1.3.0.tgz#c21389d0997a305888911b345fdcf44f16dd7878" + integrity sha512-vl/z3GSRY5ZvjHvb7gpfyq8HXj630yYViHqtstTRtGCv1QaNxgsDzZ/ECkN8fWiJJj/4SUiiKw6jSeHL0jTAew== dependencies: + "@turnkey/api-key-stamper" "0.1.1" + "@turnkey/webauthn-stamper" "0.2.0" cross-fetch "^3.1.5" +"@turnkey/viem@^0.2.4": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@turnkey/viem/-/viem-0.2.5.tgz#a4059d4cd43cbb80330cb7b78330ea7a58d42db7" + integrity sha512-HdXI0oi/7MfEEHDtIUjjG27/WotZBVLb8NzmqKzSnyiDvBC0UX4toba6pobKngxtehg/5DznJW961d7CRAx2Gw== + dependencies: + "@turnkey/api-key-stamper" "0.1.1" + "@turnkey/http" "1.3.0" + cross-fetch "^4.0.0" + typescript "5.1" + +"@turnkey/webauthn-stamper@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@turnkey/webauthn-stamper/-/webauthn-stamper-0.2.0.tgz#aa546391ee59cf6c5d4201d74e038adb261765d2" + integrity sha512-BoHucuoTTmvqJzqSZs6XsLHSu1rwelSHUFBDlUfYX/QzK9BpkaxMxamOxk92OLpRb6qRUngrCQeS/jPHlWgQow== + dependencies: + buffer "^6.0.3" + "@types/bn.js@^5.1.0": version "5.1.1" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" @@ -5981,6 +5997,13 @@ cross-fetch@^3.1.4, cross-fetch@^3.1.5: dependencies: node-fetch "^2.6.11" +cross-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -9385,6 +9408,13 @@ node-fetch@^2.6.11, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-fetch@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.1.tgz#b3eea7b54b3a48020e46f4f88b9c5a7430d20b2e" @@ -11800,6 +11830,11 @@ typescript-template@*: dependencies: sitka "^1.0.6" +typescript@5.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== + "typescript@^3 || ^4": version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"