From 49205d0a44abd2082dc063f773d95cc6c6403333 Mon Sep 17 00:00:00 2001 From: Justin <328965+justinbarry@users.noreply.github.com> Date: Tue, 16 Apr 2024 20:06:02 -0700 Subject: [PATCH] Add new api file which verifies signatures --- apps/demo-app/next-env.d.ts | 1 + apps/demo-app/package.json | 5 +- apps/demo-app/pages/api/check-signature.ts | 189 +++++++++++++++++++++ apps/demo-app/tsconfig.json | 20 ++- pnpm-lock.yaml | 166 ++++++++++++++---- 5 files changed, 344 insertions(+), 37 deletions(-) create mode 100644 apps/demo-app/pages/api/check-signature.ts diff --git a/apps/demo-app/next-env.d.ts b/apps/demo-app/next-env.d.ts index 4f11a03d..fd36f949 100644 --- a/apps/demo-app/next-env.d.ts +++ b/apps/demo-app/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/demo-app/package.json b/apps/demo-app/package.json index 5c9a41e1..6db7e81f 100644 --- a/apps/demo-app/package.json +++ b/apps/demo-app/package.json @@ -10,9 +10,12 @@ }, "dependencies": { "@burnt-labs/abstraxion": "workspace:*", + "@burnt-labs/constants": "workspace:*", "@burnt-labs/signers": "workspace:*", "@burnt-labs/ui": "workspace:*", + "@cosmjs/amino": "^0.32.3", "@cosmjs/cosmwasm-stargate": "^0.32.2", + "@keplr-wallet/cosmos": "^0.12.80", "next": "^14.0.3", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -31,4 +34,4 @@ "tailwindcss": "^3.2.4", "typescript": "^5.2.2" } -} \ No newline at end of file +} diff --git a/apps/demo-app/pages/api/check-signature.ts b/apps/demo-app/pages/api/check-signature.ts new file mode 100644 index 00000000..3efa3be2 --- /dev/null +++ b/apps/demo-app/pages/api/check-signature.ts @@ -0,0 +1,189 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { verifyADR36Amino } from "@keplr-wallet/cosmos"; + +// This import will need to change based on the chain you are confirming against. +import { testnetChainInfo } from "@burnt-labs/constants"; +import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { Pubkey } from "@cosmjs/amino"; + +interface GrantsResponse { + grants: Grant[]; + pagination: Pagination; +} + +interface Grant { + granter: string; + grantee: string; + authorization: Authorization; + expiration: string; +} + +interface Authorization { + "@type": string; + grants: GrantAuthorization[]; +} + +interface GrantAuthorization { + contract: string; + limit: Limit; + filter: Filter; +} + +interface Limit { + "@type": string; + remaining: string; +} + +interface Filter { + "@type": string; +} + +interface Pagination { + next_key: null | string; + total: string; +} + +function isString(test: any): test is string { + return typeof test === "string"; +} + +async function checkGrants(granter: string, grantee: string): Promise { + const res = await fetch( + `${testnetChainInfo.rest}/cosmos/authz/v1beta1/grants/grantee/${granter}`, + { + cache: "no-store", + }, + ); + const data = (await res.json()) as GrantsResponse; + return data.grants.map((grant) => grant.grantee).includes(grantee); +} + +function checkSignature( + address: string, + pubKey: Pubkey, + messageString: string, + signature: string, +): boolean { + const msg = Buffer.from(messageString, "hex").toString(); + + const signatureBuffer = Buffer.from(signature, "base64"); + + const uint8Signature = new Uint8Array(signatureBuffer); // Convert the buffer to an Uint8Array + + const pubKeyValueBuffer = Buffer.from(pubKey, "base64"); // Decode the base64 encoded value + + const pubKeyUint8Array = new Uint8Array(pubKeyValueBuffer); // Convert the buffer to an Uint8Array + + return verifyADR36Amino( + testnetChainInfo.bech32Config.bech32PrefixAccAddr, + address, + msg, + pubKeyUint8Array, + uint8Signature, + ); +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + const { query, method } = req; + const client = await CosmWasmClient.connect(testnetChainInfo.rpc); + + if (method === "GET") { + const { userSessionAddress, metaAccountAddress, message, signature } = + query; + + const errors: string[] = []; + if (!userSessionAddress) { + errors.push("userSessionAddress is required"); + } + + if (!metaAccountAddress && typeof metaAccountAddress === "string") { + errors.push("itemId is required"); + } + + if (!message && typeof message === "string") { + errors.push("message is required"); + } + + if (!signature && typeof signature === "string") { + errors.push("signature is required"); + } + + if (errors.length > 0) { + res.status(400).json({ errors }); + return; + } + + // These aid TS type inference. You can pull in a more complex validation package to handle this like io-ts + if (!isString(userSessionAddress)) { + res + .status(400) + .json({ errors: ["userSessionAddress is required to be a string"] }); + return; + } + + if (!isString(metaAccountAddress)) { + res + .status(400) + .json({ errors: ["metaAccountAddress is required to be a string"] }); + return; + } + + if (!isString(message)) { + res.status(400).json({ errors: ["message is required to be a string"] }); + return; + } + + if (!isString(signature)) { + res + .status(400) + .json({ errors: ["signature is required to be a string"] }); + return; + } + + /* + * Confirming account "ownership" is a three-step process + * 1. Confirm the signature passed is valid for the temporary userSessionAddress + * 2. Pull any grant bestowed to the userSessionAddress on chain + * 3. Check that AT LEAST one grant exists of any type exists between the userSessionAddress and the metaAccountAddress + **/ + // Need to get the pub key from the chain. + const account = await client.getAccount(userSessionAddress); + if (!account) { + res.status(404).json({ + errors: ["account not found"], + }); + return; + } + const { pubkey } = account; + if (!pubkey) { + res.status(404).json({ + errors: ["public key not found"], + }); + return; + } + + const isValid = checkSignature( + userSessionAddress, + pubkey.value, + message, + signature, + ); + if (!isValid) { + res.status(400).json({ + errors: ["invalid signature"], + }); + return; + } + + // handle GET request + res.status(200).json({ + valid: await checkGrants(metaAccountAddress, userSessionAddress), + }); + } else { + res.setHeader("Allow", ["GET"]); + res.status(405).end(`Method ${method} Not Allowed`); + } +} diff --git a/apps/demo-app/tsconfig.json b/apps/demo-app/tsconfig.json index 10ba1595..e4d07053 100644 --- a/apps/demo-app/tsconfig.json +++ b/apps/demo-app/tsconfig.json @@ -1,9 +1,21 @@ { "extends": "@burnt-labs/tsconfig/nextjs.json", "compilerOptions": { - "plugins": [{ "name": "next" }], - "moduleResolution": "Bundler" + "plugins": [ + { + "name": "next" + } + ], + "moduleResolution": "Bundler", + "strictNullChecks": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16218b7f..c9d54ee1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 0.5.3(prettier@3.0.3) turbo: specifier: latest - version: 1.11.2 + version: 1.12.4 apps/abstraxion-dashboard: dependencies: @@ -156,15 +156,24 @@ importers: '@burnt-labs/abstraxion': specifier: workspace:* version: link:../../packages/abstraxion + '@burnt-labs/constants': + specifier: workspace:* + version: link:../../packages/constants '@burnt-labs/signers': specifier: workspace:* version: link:../../packages/signers '@burnt-labs/ui': specifier: workspace:* version: link:../../packages/ui + '@cosmjs/amino': + specifier: ^0.32.3 + version: 0.32.3 '@cosmjs/cosmwasm-stargate': specifier: ^0.32.2 version: 0.32.2 + '@keplr-wallet/cosmos': + specifier: ^0.12.80 + version: 0.12.80 next: specifier: ^14.0.3 version: 14.0.3(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0) @@ -416,7 +425,7 @@ importers: version: 5.0.5 tsup: specifier: ^6.0.1 - version: 6.7.0(postcss@8.4.28)(typescript@5.2.2) + version: 6.7.0(postcss@8.4.31)(typescript@5.2.2) typescript: specifier: ^5.2.2 version: 5.2.2 @@ -510,7 +519,7 @@ importers: version: 5.0.5 tsup: specifier: ^6.0.1 - version: 6.7.0(postcss@8.4.28)(typescript@5.2.2) + version: 6.7.0(postcss@8.4.31)(typescript@5.2.2) typescript: specifier: ^5.2.2 version: 5.2.2 @@ -2435,6 +2444,15 @@ packages: '@cosmjs/utils': 0.32.2 dev: false + /@cosmjs/amino@0.32.3: + resolution: {integrity: sha512-G4zXl+dJbqrz1sSJ56H/25l5NJEk/pAPIr8piAHgbXYw88OdAOlpA26PQvk2IbSN/rRgVbvlLTNgX2tzz1dyUA==} + dependencies: + '@cosmjs/crypto': 0.32.3 + '@cosmjs/encoding': 0.32.3 + '@cosmjs/math': 0.32.3 + '@cosmjs/utils': 0.32.3 + dev: false + /@cosmjs/cosmwasm-stargate@0.31.3: resolution: {integrity: sha512-Uv9TmCn3650gdFeZm7SEfUZF3uX3lfJfFhXOk6I2ZLr/FrKximnlb+vwAfZaZnWYvlA7qrKtHIjeRNHvT23zcw==} dependencies: @@ -2458,7 +2476,7 @@ packages: /@cosmjs/cosmwasm-stargate@0.32.2: resolution: {integrity: sha512-OwJHzIx2CoJS6AULxOpNR6m+CI0GXxy8z9svHA1ZawzNM3ZGlL0GvHdhmF0WkpX4E7UdrYlJSLpKcgg5Fo6i7Q==} dependencies: - '@cosmjs/amino': 0.32.2 + '@cosmjs/amino': 0.32.3 '@cosmjs/crypto': 0.32.2 '@cosmjs/encoding': 0.32.2 '@cosmjs/math': 0.32.2 @@ -2513,6 +2531,18 @@ packages: libsodium-wrappers-sumo: 0.7.13 dev: false + /@cosmjs/crypto@0.32.3: + resolution: {integrity: sha512-niQOWJHUtlJm2GG4F00yGT7sGPKxfUwz+2qQ30uO/E3p58gOusTcH2qjiJNVxb8vScYJhFYFqpm/OA/mVqoUGQ==} + dependencies: + '@cosmjs/encoding': 0.32.3 + '@cosmjs/math': 0.32.3 + '@cosmjs/utils': 0.32.3 + '@noble/hashes': 1.3.3 + bn.js: 5.2.1 + elliptic: 6.5.4 + libsodium-wrappers-sumo: 0.7.13 + dev: false + /@cosmjs/encoding@0.27.1: resolution: {integrity: sha512-rayLsA0ojHeniaRfWWcqSsrE/T1rl1gl0OXVNtXlPwLJifKBeLEefGbOUiAQaT0wgJ8VNGBazVtAZBpJidfDhw==} dependencies: @@ -2537,6 +2567,14 @@ packages: readonly-date: 1.0.0 dev: false + /@cosmjs/encoding@0.32.3: + resolution: {integrity: sha512-p4KF7hhv8jBQX3MkB3Defuhz/W0l3PwWVYU2vkVuBJ13bJcXyhU9nJjiMkaIv+XP+W2QgRceqNNgFUC5chNR7w==} + dependencies: + base64-js: 1.5.1 + bech32: 1.1.4 + readonly-date: 1.0.0 + dev: false + /@cosmjs/json-rpc@0.31.3: resolution: {integrity: sha512-7LVYerXjnm69qqYR3uA6LGCrBW2EO5/F7lfJxAmY+iII2C7xO3a0vAjMSt5zBBh29PXrJVS6c2qRP22W1Le2Wg==} dependencies: @@ -2583,6 +2621,12 @@ packages: bn.js: 5.2.1 dev: false + /@cosmjs/math@0.32.3: + resolution: {integrity: sha512-amumUtZs8hCCnV+lSBaJIiZkGabQm22QGg/IotYrhcmoOEOjt82n7hMNlNXRs7V6WLMidGrGYcswB5zcmp0Meg==} + dependencies: + bn.js: 5.2.1 + dev: false + /@cosmjs/proto-signing@0.31.3: resolution: {integrity: sha512-24+10/cGl6lLS4VCrGTCJeDRPQTn1K5JfknzXzDIHOx8THR31JxA7/HV5eWGHqWgAbudA7ccdSvEK08lEHHtLA==} dependencies: @@ -2732,6 +2776,10 @@ packages: resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==} dev: false + /@cosmjs/utils@0.32.3: + resolution: {integrity: sha512-WCZK4yksj2hBDz4w7xFZQTRZQ/RJhBX26uFHmmQFIcNUUVAihrLO+RerqJgk0dZqC42wstM9pEUQGtPmLcIYvg==} + dev: false + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -3556,21 +3604,21 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@keplr-wallet/common@0.12.46: - resolution: {integrity: sha512-cu/H4g/f7zgxE+4sO/M8b1HC7Sv+9WccJoS1v5TqP7MnZUyTdh6nwZ1jM8Asbu2mMOxQJv7vdV1iydPWZ5dXNg==} + /@keplr-wallet/common@0.12.67: + resolution: {integrity: sha512-S3m+PM1eKxP3uBHWjb6c+t0luFTeDF3w7nSNCvs9Hn1vXxz11EUjPVbOAjoS4r6B0IByFz4wcx8efUmgiaxbPA==} dependencies: - '@keplr-wallet/crypto': 0.12.46 - '@keplr-wallet/types': 0.12.46 + '@keplr-wallet/crypto': 0.12.67 + '@keplr-wallet/types': 0.12.67 buffer: 6.0.3 delay: 4.4.1 mobx: 6.12.0 dev: false - /@keplr-wallet/common@0.12.67: - resolution: {integrity: sha512-S3m+PM1eKxP3uBHWjb6c+t0luFTeDF3w7nSNCvs9Hn1vXxz11EUjPVbOAjoS4r6B0IByFz4wcx8efUmgiaxbPA==} + /@keplr-wallet/common@0.12.80: + resolution: {integrity: sha512-HndiWKqNtPq6uGt7aY5FQEwegSG0SPN21W2M8XgEOwryzTKAMnFm8sjfvAAIjZW0Wmvkx1fX1e/MWQV27IfxJg==} dependencies: - '@keplr-wallet/crypto': 0.12.67 - '@keplr-wallet/types': 0.12.67 + '@keplr-wallet/crypto': 0.12.80 + '@keplr-wallet/types': 0.12.80 buffer: 6.0.3 delay: 4.4.1 mobx: 6.12.0 @@ -3592,6 +3640,22 @@ packages: protobufjs: 6.11.4 dev: false + /@keplr-wallet/cosmos@0.12.80: + resolution: {integrity: sha512-MS3BGdy9Y0R8Rujr9CHkzoWpC8PmSDZOBBMRhljelChVAAfc1Dvz7nsJ66Sk9vpsiNUwCctcK+sxedtT7Mia9A==} + dependencies: + '@ethersproject/address': 5.7.0 + '@keplr-wallet/common': 0.12.80 + '@keplr-wallet/crypto': 0.12.80 + '@keplr-wallet/proto-types': 0.12.80 + '@keplr-wallet/simple-fetch': 0.12.80 + '@keplr-wallet/types': 0.12.80 + '@keplr-wallet/unit': 0.12.80 + bech32: 1.1.4 + buffer: 6.0.3 + long: 4.0.0 + protobufjs: 6.11.4 + dev: false + /@keplr-wallet/crypto@0.12.67: resolution: {integrity: sha512-ETLeRay4fUyJhN+emGz1wjA4OICct55wDTeKR4qfyYMn7WNbIKK+CSM71gaApZZAWZmXimglEYq5xAF8KSpPsg==} dependencies: @@ -3618,6 +3682,19 @@ packages: sha.js: 2.4.11 dev: false + /@keplr-wallet/crypto@0.12.80: + resolution: {integrity: sha512-iJLxy3OJV1/ZDZ3y0ZKHHN46rwtSgyvVxVp0IiDVtZQ77sYw2yiQArux6cYwi8i2d2eZPJc2I//hvZagBdeoGA==} + dependencies: + '@ethersproject/keccak256': 5.7.0 + bip32: 2.0.6 + bip39: 3.1.0 + bs58check: 2.1.2 + buffer: 6.0.3 + crypto-js: 4.2.0 + elliptic: 6.5.4 + sha.js: 2.4.11 + dev: false + /@keplr-wallet/proto-types@0.12.67: resolution: {integrity: sha512-rfsOBKH+UrkmA39mqiXYy6lz+YctWMtjaZ5Tqc1+E3qbt10z5gu220+pI+hc0XxIj481j0v5vjFhAL9yGIZMVg==} dependencies: @@ -3625,16 +3702,33 @@ packages: protobufjs: 6.11.4 dev: false + /@keplr-wallet/proto-types@0.12.80: + resolution: {integrity: sha512-ZX0/O+P2zkjr6AwrppckeJfvDnsJnaoNBMxLQ5XZldLAzKb0ECEQnC8IgFkTWYqy9jgFbu2kJwqtrPnKSqKwMQ==} + dependencies: + long: 4.0.0 + protobufjs: 6.11.4 + dev: false + /@keplr-wallet/simple-fetch@0.12.67: resolution: {integrity: sha512-xfu4S9HHqEH0AskG0eyLEAW10FaXD9C/tfSbpITMZU1FM9EnegLoHty+hXtMlIGDZbNZ8rsSpABMFLTEr2XB0g==} dev: false + /@keplr-wallet/simple-fetch@0.12.80: + resolution: {integrity: sha512-GBOONbo5npz+t47cnjRnpO1Up+XGAPxjKLRtwYopZeV99KZa7oLb73RQBXT0XooQwq73bwy/Xv0qPCSYhWKUgA==} + dev: false + /@keplr-wallet/types@0.12.67: resolution: {integrity: sha512-HDEYOezWq//ug/R0lkOQWsaOYh3b9ECA8LN+tAA+baWMo81X9m9L63ux7NYw0QmoLxWqWrRO/T9F28+q2dw6+A==} dependencies: long: 4.0.0 dev: false + /@keplr-wallet/types@0.12.80: + resolution: {integrity: sha512-GRMyOioocq2LaLAoaGegCOqgjKq4uXOPQ9QwoLpx0ZpsbccSr2wKN8PhFs/B9fwuhiJu1aroX7hXC0jlsE8XLg==} + dependencies: + long: 4.0.0 + dev: false + /@keplr-wallet/unit@0.12.67: resolution: {integrity: sha512-Mzlrh0j65i6+qoTNg1/Um4Hywpu9mmaZn8HRiX01HdPyaEcLREFkf18eJMklWuHx4nERg+EzvU5E/9J2fuRPdA==} dependencies: @@ -3643,6 +3737,14 @@ packages: utility-types: 3.10.0 dev: false + /@keplr-wallet/unit@0.12.80: + resolution: {integrity: sha512-XpZe2i0iY8jtbA1GTsK4DV4KN5C2nXnSFdBn36F0TS/Ow1DO5KSqIyYsAU86pTzZfbkUzn5OPPmJUHGAg00GJg==} + dependencies: + '@keplr-wallet/types': 0.12.80 + big-integer: 1.6.51 + utility-types: 3.10.0 + dev: false + /@lit-labs/ssr-dom-shim@1.1.2: resolution: {integrity: sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==} dev: false @@ -12668,64 +12770,64 @@ packages: yargs: 17.7.2 dev: false - /turbo-darwin-64@1.11.2: - resolution: {integrity: sha512-toFmRG/adriZY3hOps7nYCfqHAS+Ci6xqgX3fbo82kkLpC6OBzcXnleSwuPqjHVAaRNhVoB83L5njcE9Qwi2og==} + /turbo-darwin-64@1.12.4: + resolution: {integrity: sha512-dBwFxhp9isTa9RS/fz2gDVk5wWhKQsPQMozYhjM7TT4jTrnYn0ZJMzr7V3B/M/T8QF65TbniW7w1gtgxQgX5Zg==} cpu: [x64] os: [darwin] requiresBuild: true dev: false optional: true - /turbo-darwin-arm64@1.11.2: - resolution: {integrity: sha512-FCsEDZ8BUSFYEOSC3rrARQrj7x2VOrmVcfrMUIhexTxproRh4QyMxLfr6LALk4ymx6jbDCxWa6Szal8ckldFbA==} + /turbo-darwin-arm64@1.12.4: + resolution: {integrity: sha512-1Uo5iI6xsJ1j9ObsqxYRsa3W26mEbUe6fnj4rQYV6kDaqYD54oAMJ6hM53q9rB8JvFxwdrUXGp3PwTw9A0qqkA==} cpu: [arm64] os: [darwin] requiresBuild: true dev: false optional: true - /turbo-linux-64@1.11.2: - resolution: {integrity: sha512-Vzda/o/QyEske5CxLf0wcu7UUS+7zB90GgHZV4tyN+WZtoouTvbwuvZ3V6b5Wgd3OJ/JwWR0CXDK7Sf4VEMr7A==} + /turbo-linux-64@1.12.4: + resolution: {integrity: sha512-ONg2aSqKP7LAQOg7ysmU5WpEQp4DGNxSlAiR7um+LKtbmC/UxogbR5+T+Uuq6zGuQ5kJyKjWJ4NhtvUswOqBsA==} cpu: [x64] os: [linux] requiresBuild: true dev: false optional: true - /turbo-linux-arm64@1.11.2: - resolution: {integrity: sha512-bRLwovQRz0yxDZrM4tQEAYV0fBHEaTzUF0JZ8RG1UmZt/CqtpnUrJpYb1VK8hj1z46z9YehARpYCwQ2K0qU4yw==} + /turbo-linux-arm64@1.12.4: + resolution: {integrity: sha512-9FPufkwdgfIKg/9jj87Cdtftw8o36y27/S2vLN7FTR2pp9c0MQiTBOLVYadUr1FlShupddmaMbTkXEhyt9SdrA==} cpu: [arm64] os: [linux] requiresBuild: true dev: false optional: true - /turbo-windows-64@1.11.2: - resolution: {integrity: sha512-LgTWqkHAKgyVuLYcEPxZVGPInTjjeCnN5KQMdJ4uQZ+xMDROvMFS2rM93iQl4ieDJgidwHCxxCxaU9u8c3d/Kg==} + /turbo-windows-64@1.12.4: + resolution: {integrity: sha512-2mOtxHW5Vjh/5rDVu/aFwsMzI+chs8XcEuJHlY1sYOpEymYTz+u6AXbnzRvwZFMrLKr7J7fQOGl+v96sLKbNdA==} cpu: [x64] os: [win32] requiresBuild: true dev: false optional: true - /turbo-windows-arm64@1.11.2: - resolution: {integrity: sha512-829aVBU7IX0c/B4G7g1VI8KniAGutHhIupkYMgF6xPkYVev2G3MYe6DMS/vsLt9GGM9ulDtdWxWrH5P2ngK8IQ==} + /turbo-windows-arm64@1.12.4: + resolution: {integrity: sha512-nOY5wae9qnxPOpT1fRuYO0ks6dTwpKMPV6++VkDkamFDLFHUDVM/9kmD2UTeh1yyrKnrZksbb9zmShhmfj1wog==} cpu: [arm64] os: [win32] requiresBuild: true dev: false optional: true - /turbo@1.11.2: - resolution: {integrity: sha512-jPC7LVQJzebs5gWf8FmEvsvXGNyKbN+O9qpvv98xpNaM59aS0/Irhd0H0KbcqnXfsz7ETlzOC3R+xFWthC4Z8A==} + /turbo@1.12.4: + resolution: {integrity: sha512-yUJ7elEUSToiGwFZogXpYKJpQ0BvaMbkEuQECIWtkBLcmWzlMOt6bActsIm29oN83mRU0WbzGt4e8H1KHWedhg==} hasBin: true optionalDependencies: - turbo-darwin-64: 1.11.2 - turbo-darwin-arm64: 1.11.2 - turbo-linux-64: 1.11.2 - turbo-linux-arm64: 1.11.2 - turbo-windows-64: 1.11.2 - turbo-windows-arm64: 1.11.2 + turbo-darwin-64: 1.12.4 + turbo-darwin-arm64: 1.12.4 + turbo-linux-64: 1.12.4 + turbo-linux-arm64: 1.12.4 + turbo-windows-64: 1.12.4 + turbo-windows-arm64: 1.12.4 dev: false /type-check@0.4.0: